如何让selenium等待ajax响应?

问题描述 投票:0回答:16

如何让 selenium 等待诸如日历小部件之类的内容加载?现在我只是在将测试用例导出到junit程序后做一个

Thread.sleep(2500)

ajax unit-testing selenium
16个回答
46
投票

我会用

waitForElementPresent(locator)

这将等待该元素出现在 DOM 中。

如果您需要检查元素是否可见,您可能更好地使用

waitForElementHeight(locator)

28
投票

比等待元素更通用的解决方案是等待与服务器的所有连接关闭。这将允许您等待所有 ajax 调用完成,即使它们没有任何回调,因此不会影响页面。更多详情请参阅这里

使用 C# 和 jQuery,我创建了以下方法来等待所有 AJax 调用完成(如果有人有更直接的方法从 C# 访问 JS 变量,请评论):

internal void WaitForAjax(int timeOut = 15)
{
    var value = "";
    RepeatUntil(
        () => value = GetJavascriptValue("jQuery.active"), 
        () => value == "0", 
        "Ajax calls did not complete before timeout"
    );
}

internal void RepeatUntil(Action repeat, Func<bool> until, string errorMessage, int timeout = 15)
{
    var end = DateTime.Now + TimeSpan.FromSeconds(timeout);
    var complete = false;

    while (DateTime.Now < end)
    {
        repeat();
        try
        {
            if (until())
            {
                complete = true;
                break;
            }
        }
        catch (Exception)
        { }
        Thread.Sleep(500);
    }
    if (!complete)
        throw new TimeoutException(errorMessage);
}

internal string GetJavascriptValue(string variableName)
{
    var id = Guid.NewGuid().ToString();
    _selenium.RunScript(String.Format(@"window.$('body').append(""<input type='text' value='""+{0}+""' id='{1}'/>"");", variableName, id));
    return _selenium.GetValue(id);
}

16
投票

如果使用Python,你可以使用这个函数,点击按钮并等待DOM变化:

def click_n_wait(driver, button, timeout=5):
    source = driver.page_source
    button.click()
    def compare_source(driver):
        try:
            return source != driver.page_source
        except WebDriverException:
            pass
    WebDriverWait(driver, timeout).until(compare_source)

(信用:基于这个堆栈溢出答案


4
投票

这对我有用

public  void waitForAjax(WebDriver driver) {
    new WebDriverWait(driver, 180).until(new ExpectedCondition<Boolean>(){
        public Boolean apply(WebDriver driver) {
            JavascriptExecutor js = (JavascriptExecutor) driver;
            return (Boolean) js.executeScript("return jQuery.active == 0");
        }
    });
}

4
投票

使用 webdriver 又名 selenium2,您可以使用隐式等待配置,如上提到的 https://www.selenium.dev/documentation/en/webdriver/waits/#implicit-wait

使用Java:

WebDriver driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = driver.findElement(By.id("myDynamicElement"));

或者使用Python:

from selenium import webdriver

ff = webdriver.Firefox()
ff.implicitly_wait(10) # seconds
ff.get("http://somedomain/url_that_delays_loading")
myDynamicElement = ff.find_element_by_id("myDynamicElement")

2
投票

下面的代码(C#)确保显示目标元素:

        internal static bool ElementIsDisplayed()
        {
          IWebDriver driver = new ChromeDriver();
          driver.Url = "http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp";
          WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
          By locator = By.CssSelector("input[value='csharp']:first-child");
          IWebElement myDynamicElement = wait.Until<IWebElement>((d) =>
          {
            return d.FindElement(locator);
          });
          return myDynamicElement.Displayed;
        }

如果页面支持 jQuery,则可以使用 jQuery.active 函数来确保在所有 ajax 调用完成后检索目标元素:

 public static bool ElementIsDisplayed()
    {
        IWebDriver driver = new ChromeDriver();
        driver.Url = "http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp";
        WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
        By locator = By.CssSelector("input[value='csharp']:first-child");
        return wait.Until(d => ElementIsDisplayed(d, locator));
    }

    public static bool ElementIsDisplayed(IWebDriver driver, By by)
    {
        try
        {
            if (driver.FindElement(by).Displayed)
            {
                //jQuery is supported.
                if ((bool)((IJavaScriptExecutor)driver).ExecuteScript("return window.$ != undefined"))
                {
                    return (bool)((IJavaScriptExecutor)driver).ExecuteScript("return $.active == 0");
                }
                else
                {
                    return true;
                }
            }
            else
            {
                return false;
            }
        }
        catch (Exception)
        {
            return false;
        }
    }

2
投票

我写了下一个方法作为我的解决方案(我没有任何负载指示器):

public static void waitForAjax(WebDriver driver, String action) {
       driver.manage().timeouts().setScriptTimeout(5, TimeUnit.SECONDS);
       ((JavascriptExecutor) driver).executeAsyncScript(
               "var callback = arguments[arguments.length - 1];" +
                       "var xhr = new XMLHttpRequest();" +
                       "xhr.open('POST', '/" + action + "', true);" +
                       "xhr.onreadystatechange = function() {" +
                       "  if (xhr.readyState == 4) {" +
                       "    callback(xhr.responseText);" +
                       "  }" +
                       "};" +
                       "xhr.send();");
}

然后我就用实际的驱动程序调用了这个方法。 更多描述请参见这篇post


2
投票

这对我来说就像一个魅力:

public void waitForAjax() {

    try {
        WebDriverWait driverWait = new WebDriverWait(driver, 10);

        ExpectedCondition<Boolean> expectation;   
        expectation = new ExpectedCondition<Boolean>() {

            public Boolean apply(WebDriver driverjs) {

                JavascriptExecutor js = (JavascriptExecutor) driverjs;
                return js.executeScript("return((window.jQuery != null) && (jQuery.active === 0))").equals("true");
            }
        };
        driverWait.until(expectation);
    }       
    catch (TimeoutException exTimeout) {

       // fail code
    }
    catch (WebDriverException exWebDriverException) {

       // fail code
    }
    return this;
}

1
投票

我也有类似的情况,我想等待ajax请求,这样加载面板就会消失,我检查了请求前后的html,发现有一个ajax加载面板的div,显示了dix在ajax请求期间,并在请求结束后隐藏。我创建了一个函数来等待面板显示,然后等待它隐藏

public void WaitForModalPanel() { string element_xpath = ".//*[@id='ajaxLoadingModalPanelContainer' and not(contains(@style,'display: none'))]"; WebDriverWait wait = new WebDriverWait(driver, new TimeSpan(0, 2, 0)); wait.Until(ExpectedConditions.ElementIsVisible(By.XPath(element_xpath))); element_xpath = ".//*[@id='ajaxLoadingModalPanelContainer' and contains(@style,'DISPLAY: none')]"; wait.Until(ExpectedConditions.ElementExists(By.XPath(element_xpath))); }

查看这个了解更多详情


1
投票

这是基于 Morten Christiansen 的回答的绝妙版本。

void waitForAjaxCallsToComplete() {
    repeatUntil(
            { return getJavaScriptFunction(driver, "return (window.jQuery || {active : false}).active") },
            "Ajax calls did not complete before timeout."
    )
}

static void repeatUntil(Closure runUntilTrue, String errorMessage, int pollFrequencyMS = 250, int timeOutSeconds = 10) {
    def today = new Date()
    def end = today.time + timeOutSeconds
    def complete = false;

    while (today.time < end) {
        if (runUntilTrue()) {
            complete = true;
            break;
        }

        sleep(pollFrequencyMS);
    }
    if (!complete)
        throw new TimeoutException(errorMessage);
}

static String getJavaScriptFunction(WebDriver driver, String jsFunction) {
    def jsDriver = driver as JavascriptExecutor
    jsDriver.executeScript(jsFunction)
}

1
投票

如上所述,您可以等待活动连接关闭:

private static void WaitForReady() {
    WebDriverWait wait = new WebDriverWait(webDriver, waitForElement);
    wait.Until(driver => (bool)((IJavaScriptExecutor)driver).ExecuteScript("return jQuery.active == 0"));
}

我的观察是这并不可靠,因为数据传输发生得非常快。更多的时间消耗在页面上的数据处理和渲染上,甚至

jQuery.active == 0
数据可能还没有出现在页面上。

更明智的做法是使用显式等待来显示页面上的元素,请参阅与此相关的一些答案。

最好的情况是您的 Web 应用程序有一些自定义加载程序或指示正在处理数据。在这种情况下,您只需等待该指示隐藏即可。


0
投票

如果您正在等待的控件是“Ajax”Web 元素,则以下代码将等待它或任何其他 Ajax Web 元素完成加载或执行它需要执行的任何操作,以便您可以更安全地继续您的操作步骤。

    public static void waitForAjaxToFinish() {

    WebDriverWait wait = new WebDriverWait(driver, 10);

    wait.until(new ExpectedCondition<Boolean>() { 
        public Boolean apply(WebDriver wdriver) { 
            return ((JavascriptExecutor) driver).executeScript(
                    "return jQuery.active == 0").equals(true);
        }
    }); 

}

0
投票

下面是我的获取代码。带着我进行研究,因为 jQuery.active 不能与 fetch 一起使用。这是帮助我代理获取的答案,但它仅适用于ajax而不是获取selenium的模拟

public static void customPatchXMLHttpRequest(WebDriver driver) {
    try {
        if (driver instanceof JavascriptExecutor) {
            JavascriptExecutor jsDriver = (JavascriptExecutor) driver;
            Object numberOfAjaxConnections = jsDriver.executeScript("return window.openHTTPs");
            if (numberOfAjaxConnections instanceof Long) {
                return;
            }
            String script = "  (function() {" + "var oldFetch = fetch;"
                    + "window.openHTTPs = 0; console.log('starting xhttps');" + "fetch = function(input,init ){ "
                    + "window.openHTTPs++; "

                    + "return oldFetch(input,init).then( function (response) {"
                    + "      if (response.status >= 200 && response.status < 300) {"
                    + "          window.openHTTPs--;  console.log('Call completed. Remaining active calls: '+ window.openHTTPs); return response;"
                    + "      } else {"
                    + "          window.openHTTPs--; console.log('Call fails. Remaining active calls: ' + window.openHTTPs);  return response;"
                    + "      };})" + "};" + "var oldOpen = XMLHttpRequest.prototype.open;"
                    + "XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {"
                    + "window.openHTTPs++; console.log('xml ajax called');"
                    + "this.addEventListener('readystatechange', function() {" + "if(this.readyState == 4) {"
                    + "window.openHTTPs--; console.log('xml ajax complete');" + "}" + "}, false);"
                    + "oldOpen.call(this, method, url, async, user, pass);" + "}" +

                    "})();";
            jsDriver.executeScript(script);
        } else {
            System.out.println("Web driver: " + driver + " cannot execute javascript");
        }
    } catch (Exception e) {
        System.out.println(e);
    }
}

0
投票

就我而言,问题似乎是由于 ajax 延迟造成的,但与主页内的内部 iframe 有关。 在 seleminum 中,可以使用以下命令切换到内部框架:

    driver.switchTo().frame("body");
    driver.switchTo().frame("bodytab");

我使用java。之后我就能够找到该元素

    driver.findElement(By.id("e_46")).click();

0
投票

以下解决方案可用于 jQuery 不可用的情况。

通过主动监控日志,我们可以确保所有文件和响应都已确定完成。

我已经针对 Chrome 测试了此解决方案,但您还应该确保其他浏览器支持它。例如Safari不支持此方法。

另外,不要忘记将这些日志的功能设置为可见。

 LoggingPreferences preferences = new LoggingPreferences();
    preferences.enable(LogType.PERFORMANCE, Level.ALL);
    ChromeOptions chromeOptions = new ChromeOptions();
    chromeOptions.setCapability("goog:loggingPrefs", preferences);

您应该与 WebDriverWait 实例一起使用。之后,只需调用 waitUntilPageIsReady() 方法即可。

public class IsPageReady implements ExpectedCondition<Boolean> {
private Logger log = LogManager.getLogger(IsPageReady.class);

@NullableDecl
@Override
public Boolean apply(@NullableDecl WebDriver webDriver) {
    try {
        if (webDriver == null)
            return false;
        LogEntries logs = WebDriverManager.getInstance().getWebDriver().manage().logs().get(LogType.PERFORMANCE);
        boolean ready = logs.getAll().isEmpty();
        return ((JavascriptExecutor) WebDriverManager.getInstance().getWebDriver()).executeScript("return document.readyState").equals("complete") && ready;
    } catch (Exception ex) {
        log.warn("There was a problem checking if the page was ready!");
    }
    return false;
}
}

只需致电:

public void waitUntilPageIsReady() {
    getWebDriverWait().until(new IsPageReady());
}

-2
投票

对于那些使用 primefaces 的人,只需这样做:

selenium.waitForCondition("selenium.browserbot.getCurrentWindow().$.active==0", defaultWaitingPeriod);
© www.soinside.com 2019 - 2024. All rights reserved.