112

我有一个使用 Selenium 测试的 Web 应用程序。页面加载时有很多 JavaScript 运行。
这段 JavaScript 代码写得不太好,但我无法更改任何内容。因此,等待元素出现在 DOM 中的findElement()方法不是一种选择。
我想在 Java 中创建一个通用函数来等待页面加载,一个可能的解决方案是:

  • 从 WebDriver 运行 JavaScript 脚本并将结果存储document.body.innerHTML在字符串变量body中。
  • body变量与以前版本的body. 如果它们相同,则设置递增计数器,notChangedCount否则设置notChangedCount为零。
  • 等待一小段时间(例如 50 毫秒)。
  • 如果页面在一段时间内(例如 500 毫秒)没有改变,notChangedCount >= 10那么退出循环,否则循环到第一步。

你认为这是一个有效的解决方案吗?

4

18 回答 18

78

如果有人真的知道一个通​​用且始终适用的答案,那么它会在很久以前就已经在任何地方实施,并且会让我们的生活变得更加轻松。

你可以做很多事情,但每一件事都有一个问题:

  1. 正如 Ashwin Prabhu 所说,如果您对脚本非常了解,您可以观察它的行为并跟踪它的一些变量window等等document。但是,此解决方案并不适合所有人,并且只能由您使用,并且只能在有限的一组页。

  2. 通过观察 HTML 代码以及它是否有一段时间被更改,您的解决方案还不错(还有一种方法可以直接通过 获取原始和未编辑的 HTML WebDriver),但是:

    • 实际断言页面需要很长时间,并且可能会显着延长测试时间。
    • 你永远不知道什么是正确的间隔。该脚本可能正在下载需要超过 500 毫秒的大文件。我们公司的内部页面上有几个脚本在IE中需要几秒钟。您的计算机可能会暂时缺乏资源 - 假设防病毒软件将使您的 CPU 充分工作,那么即使对于不复杂的脚本来说,500 毫秒也可能太短了。
    • 有些脚本永远不会完成。它们会延迟调用自己 ( setTimeout()) 并一次又一次地工作,并且每次运行时都可能更改 HTML。说真的,每个“Web 2.0”页面都会这样做。甚至堆栈溢出。您可以覆盖最常用的方法,并将使用它们的脚本视为已完成,但是……您不能确定。
    • 如果脚本除了更改 HTML 之外还做了其他事情怎么办?它可以做成千上万的事情,而不仅仅是一些innerHTML乐趣。
  3. 有一些工具可以帮助您解决这个问题。即进度侦听器以及nsIWebProgressListener和其他一些。然而,浏览器对此的支持是可怕的。Firefox 从 FF4 开始尝试支持它(仍在发展中),IE 在 IE9 中有基本支持。

我想我很快就会想出另一个有缺陷的解决方案。事实是——因为永远的脚本在做他们的工作,什么时候说“现在页面已经完成”没有明确的答案。选择最适合您的,但要注意它的缺点。

于 2012-05-23T19:12:22.553 回答
34

谢谢阿什温!

在我的情况下,我需要等待某个元素中的 jquery 插件执行。特别是“qtip”

根据您的提示,它对我来说非常有效:

wait.until( new Predicate<WebDriver>() {
            public boolean apply(WebDriver driver) {
                return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
            }
        }
    );

注意:我正在使用 Webdriver 2

于 2013-10-17T17:25:01.713 回答
28

您需要等待 Javascript 和 jQuery 完成加载。执行 Javascript 检查 if jQuery.activeis0document.readyStateis complete,表示 JS 和 jQuery 加载完成。

public boolean waitForJStoLoad() {

    WebDriverWait wait = new WebDriverWait(driver, 30);

    // wait for jQuery to load
    ExpectedCondition<Boolean> jQueryLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        try {
          return ((Long)executeJavaScript("return jQuery.active") == 0);
        }
        catch (Exception e) {
          return true;
        }
      }
    };

    // wait for Javascript to load
    ExpectedCondition<Boolean> jsLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        return executeJavaScript("return document.readyState")
            .toString().equals("complete");
      }
    };

  return wait.until(jQueryLoad) && wait.until(jsLoad);
}
于 2015-05-05T16:59:14.440 回答
11

如果您需要做的只是等待页面上的 html 在尝试与元素交互之前变得稳定,您可以定期轮询 DOM 并比较结果,如果在给定的轮询时间内 DOM 相同,您就是金的。像这样的东西,你在比较之前传递了最大等待时间和页面轮询之间的时间。简单有效。

public void waitForJavascript(int maxWaitMillis, int pollDelimiter) {
    double startTime = System.currentTimeMillis();
    while (System.currentTimeMillis() < startTime + maxWaitMillis) {
        String prevState = webDriver.getPageSource();
        Thread.sleep(pollDelimiter); // <-- would need to wrap in a try catch
        if (prevState.equals(webDriver.getPageSource())) {
            return;
        }
    }
}
于 2017-05-04T23:01:45.583 回答
10

JS 库是否定义/初始化窗口上的任何众所周知的变量?

如果是这样,您可以等待变量出现。您可以使用

((JavascriptExecutor)driver).executeScript(String script, Object... args)

测试这种情况(类似于window.SomeClass && window.SomeClass.variable != null:)并返回一个布尔值true/ false

将其包装在一个 中WebDriverWait,然后等待脚本返回true

于 2012-05-23T15:51:41.330 回答
9

我有同样的问题。此解决方案适用于 WebDriverDoku:

WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));

http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

于 2016-12-19T12:23:17.587 回答
4

下面的代码在我的情况下完美运行 - 我的页面包含复杂的 java 脚本

public void checkPageIsReady() {

  JavascriptExecutor js = (JavascriptExecutor)driver;


  //Initially bellow given if condition will check ready state of page.
  if (js.executeScript("return document.readyState").toString().equals("complete")){ 
   System.out.println("Page Is loaded.");
   return; 
  } 

  //This loop will rotate for 25 times to check If page Is ready after every 1 second.
  //You can replace your value with 25 If you wants to Increase or decrease wait time.
  for (int i=0; i<25; i++){ 
   try {
    Thread.sleep(1000);
    }catch (InterruptedException e) {} 
   //To check page ready state.
   if (js.executeScript("return document.readyState").toString().equals("complete")){ 
    break; 
   }   
  }
 }

源代码 -如何在 Selenium WebDriver 中等待页面加载/准备就绪

于 2016-07-27T02:32:09.193 回答
4

在找到页面上的任何元素之前,可以使用两个条件来检查页面是否已加载:

WebDriverWait wait = new WebDriverWait(driver, 50);

使用下面的 readyState 将等到页面加载

wait.until((ExpectedCondition<Boolean>) wd ->
   ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));

下面的 JQuery 将等到数据尚未加载

  int count =0;
            if((Boolean) executor.executeScript("return window.jQuery != undefined")){
                while(!(Boolean) executor.executeScript("return jQuery.active == 0")){
                    Thread.sleep(4000);
                    if(count>4)
                        break;
                    count++;
                }
            }

在这些 JavaScriptCode 之后尝试 findOut webElement。

WebElement we = wait.until(ExpectedConditions.presenceOfElementLocated(by));
于 2017-11-15T14:24:53.480 回答
2

我要求我的开发人员创建一个我可以访问的 JavaScript 变量“isProcessing”(在“ae”对象中),他们在事情开始运行时设置并在事情完成时清除。然后我在一个累加器中运行它,该累加器每 100 毫秒检查一次,直到它连续五个,总共 500 毫秒,没有任何变化。如果 30 秒过去了,我会抛出一个异常,因为那时应该已经发生了一些事情。这是在 C# 中。

public static void WaitForDocumentReady(this IWebDriver driver)
{
    Console.WriteLine("Waiting for five instances of document.readyState returning 'complete' at 100ms intervals.");
    IJavaScriptExecutor jse = (IJavaScriptExecutor)driver;
    int i = 0; // Count of (document.readyState === complete) && (ae.isProcessing === false)
    int j = 0; // Count of iterations in the while() loop.
    int k = 0; // Count of times i was reset to 0.
    bool readyState = false;
    while (i < 5)
    {
        System.Threading.Thread.Sleep(100);
        readyState = (bool)jse.ExecuteScript("return ((document.readyState === 'complete') && (ae.isProcessing === false))");
        if (readyState) { i++; }
        else
        {
            i = 0;
            k++;
        }
        j++;
        if (j > 300) { throw new TimeoutException("Timeout waiting for document.readyState to be complete."); }
    }
    j *= 100;
    Console.WriteLine("Waited " + j.ToString() + " milliseconds. There were " + k + " resets.");
}
于 2018-05-03T19:57:44.520 回答
1

要正确执行此操作,您需要处理异常。

这是我等待 iFrame 的方法。这要求您的 JUnit 测试类将 RemoteWebDriver 的实例传递给页面对象:

public class IFrame1 extends LoadableComponent<IFrame1> {

    private RemoteWebDriver driver;

    @FindBy(id = "iFrame1TextFieldTestInputControlID" )
    public WebElement iFrame1TextFieldInput;

    @FindBy(id = "iFrame1TextFieldTestProcessButtonID" )
    public WebElement copyButton;

    public IFrame1( RemoteWebDriver drv ) {
        super();
        this.driver = drv;
        this.driver.switchTo().defaultContent();
        waitTimer(1, 1000);
        this.driver.switchTo().frame("BodyFrame1");
        LOGGER.info("IFrame1 constructor...");
    }

    @Override
    protected void isLoaded() throws Error {        
        LOGGER.info("IFrame1.isLoaded()...");
        PageFactory.initElements( driver, this );
        try {
            assertTrue( "Page visible title is not yet available.", driver
     .findElementByCssSelector("body form#webDriverUnitiFrame1TestFormID h1")
                    .getText().equals("iFrame1 Test") );
        } catch ( NoSuchElementException e) {
            LOGGER.info("No such element." );
            assertTrue("No such element.", false);
        }
    }

    @Override
    protected void load() {
        LOGGER.info("IFrame1.load()...");
        Wait<WebDriver> wait = new FluentWait<WebDriver>( driver )
                .withTimeout(30, TimeUnit.SECONDS)
                .pollingEvery(5, TimeUnit.SECONDS)
                .ignoring( NoSuchElementException.class ) 
                .ignoring( StaleElementReferenceException.class ) ;
            wait.until( ExpectedConditions.presenceOfElementLocated( 
            By.cssSelector("body form#webDriverUnitiFrame1TestFormID h1") ) );
    }
....

注意:您可以在此处查看我的整个工作示例

于 2013-04-06T19:22:20.237 回答
1

对于nodejsSelenium 库,我使用了以下代码段。就我而言,我正在寻找添加到窗口中的两个对象,在此示例中<SOME PROPERTY>10000超时毫秒数,<NEXT STEP HERE>是在窗口上找到属性后发生的情况。

driver.wait( driver => {
    return driver.executeScript( 'if(window.hasOwnProperty(<SOME PROPERTY>) && window.hasOwnProperty(<SOME PROPERTY>)) return true;' ); }, 10000).then( ()=>{
        <NEXT STEP HERE>
}).catch(err => { 
    console.log("looking for window properties", err);
});
于 2018-04-20T03:36:58.910 回答
1

这对我来说很适合动态呈现的网站:

  1. 等待完整页面加载

WebDriverWait wait = new WebDriverWait(driver, 50); 
wait.until((ExpectedCondition<Boolean>) wd -> ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));

  1. 使用总是失败的虚拟条件进行另一个隐式等待

  try {          
 wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(text(),'" + "This text will always fail :)" + "')]"))); // condition you are certain won't be true 
  } 
  catch (TimeoutException te) {
  }

  1. 最后,不要获取 html 源代码(在大多数单页应用程序中会给您带来不同的结果),而是拉取第一个 html 标记的 externalhtml

String script = "return document.getElementsByTagName(\"html\")[0].outerHTML;"; 
content = ((JavascriptExecutor) driver).executeScript(script).toString();

于 2021-03-01T15:08:01.880 回答
0

您可以编写一些逻辑来处理这个问题。我写了一个返回的方法,WebElement这个方法将被调用三次,或者你可以增加时间并添加一个空检查WebElement这是一个例子

public static void main(String[] args) {
        WebDriver driver = new FirefoxDriver();
        driver.get("https://www.crowdanalytix.com/#home");
        WebElement webElement = getWebElement(driver, "homekkkkkkkkkkkk");
        int i = 1;
        while (webElement == null && i < 4) {
            webElement = getWebElement(driver, "homessssssssssss");
            System.out.println("calling");
            i++;
        }
        System.out.println(webElement.getTagName());
        System.out.println("End");
        driver.close();
    }

    public static WebElement getWebElement(WebDriver driver, String id) {
        WebElement myDynamicElement = null;
        try {
            myDynamicElement = (new WebDriverWait(driver, 10))
                    .until(ExpectedConditions.presenceOfElementLocated(By
                            .id(id)));
            return myDynamicElement;
        } catch (TimeoutException ex) {
            return null;
        }
    }
于 2014-03-13T11:55:11.007 回答
0

这是我自己的代码:
Window.setTimeout 仅在浏览器空闲时执行。
因此,如果浏览器中没有活动,递归调用该函数(42 次)将花费 100 毫秒,如果浏览器忙于做其他事情,则需要更多时间。

    ExpectedCondition<Boolean> javascriptDone = new ExpectedCondition<Boolean>() {
        public Boolean apply(WebDriver d) {
            try{//window.setTimeout executes only when browser is idle,
                //introduces needed wait time when javascript is running in browser
                return  ((Boolean) ((JavascriptExecutor) d).executeAsyncScript( 

                        " var callback =arguments[arguments.length - 1]; " +
                        " var count=42; " +
                        " setTimeout( collect, 0);" +
                        " function collect() { " +
                            " if(count-->0) { "+
                                " setTimeout( collect, 0); " +
                            " } "+
                            " else {callback(" +
                            "    true" +                            
                            " );}"+                             
                        " } "
                    ));
            }catch (Exception e) {
                return Boolean.FALSE;
            }
        }
    };
    WebDriverWait w = new WebDriverWait(driver,timeOut);  
    w.until(javascriptDone);
    w=null;

作为奖励,计数器可以在 document.readyState 或 jQuery Ajax 调用或任何 jQuery 动画正在运行时重置(仅当您的应用程序使用 jQuery 进行 ajax 调用...)
...

" function collect() { " +
                            " if(!((typeof jQuery === 'undefined') || ((jQuery.active === 0) && ($(\":animated\").length === 0))) && (document.readyState === 'complete')){" +
                            "    count=42;" +
                            "    setTimeout( collect, 0); " +
                            " }" +
                            " else if(count-->0) { "+
                                " setTimeout( collect, 0); " +
                            " } "+ 

...

编辑:我注意到如果加载新页面并且测试可能会停止响应不确定,则 executeAsyncScript 无法正常工作,最好改为使用它。

public static ExpectedCondition<Boolean> documentNotActive(final int counter){ 
    return new ExpectedCondition<Boolean>() {
        boolean resetCount=true;
        @Override
        public Boolean apply(WebDriver d) {

            if(resetCount){
                ((JavascriptExecutor) d).executeScript(
                        "   window.mssCount="+counter+";\r\n" + 
                        "   window.mssJSDelay=function mssJSDelay(){\r\n" + 
                        "       if((typeof jQuery != 'undefined') && (jQuery.active !== 0 || $(\":animated\").length !== 0))\r\n" + 
                        "           window.mssCount="+counter+";\r\n" + 
                        "       window.mssCount-->0 &&\r\n" + 
                        "       setTimeout(window.mssJSDelay,window.mssCount+1);\r\n" + 
                        "   }\r\n" + 
                        "   window.mssJSDelay();");
                resetCount=false;
            }

            boolean ready=false;
            try{
                ready=-1==((Long) ((JavascriptExecutor) d).executeScript(
                        "if(typeof window.mssJSDelay!=\"function\"){\r\n" + 
                        "   window.mssCount="+counter+";\r\n" + 
                        "   window.mssJSDelay=function mssJSDelay(){\r\n" + 
                        "       if((typeof jQuery != 'undefined') && (jQuery.active !== 0 || $(\":animated\").length !== 0))\r\n" + 
                        "           window.mssCount="+counter+";\r\n" + 
                        "       window.mssCount-->0 &&\r\n" + 
                        "       setTimeout(window.mssJSDelay,window.mssCount+1);\r\n" + 
                        "   }\r\n" + 
                        "   window.mssJSDelay();\r\n" + 
                        "}\r\n" + 
                        "return window.mssCount;"));
            }
            catch (NoSuchWindowException a){
                a.printStackTrace();
                return true;
            }
            catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return ready;
        }
        @Override
        public String toString() {
            return String.format("Timeout waiting for documentNotActive script");
        }
    };
}
于 2014-09-10T20:37:11.497 回答
0

我喜欢你轮询 HTML 直到它稳定的想法。我可以将其添加到我自己的解决方案中。以下方法使用 C# 并需要 jQuery。

我是 SuccessFactors (SaaS) 测试项目的开发人员,我们对开发人员或网页背后的 DOM 特性完全没有影响。SaaS 产品每年可能会更改其底层 DOM 设计 4 次,因此一直在寻找使用 Selenium 进行测试的强大、高性能的方法(包括在可能的情况下不使用 Selenium 进行测试!)

这是我用于“页面准备就绪”的内容。它目前适用于我自己的所有测试。几年前,同样的方法也适用于一个大型的内部 Java Web 应用程序,并且在我离开该项目时已经运行了一年多。

  • Driver是与浏览器通信的 WebDriver 实例
  • DefaultPageLoadTimeout是以滴答声为单位的超时值(每滴答声 100ns)

public IWebDriver Driver { get; private set; }

// ...

const int GlobalPageLoadTimeOutSecs = 10;
static readonly TimeSpan DefaultPageLoadTimeout =
    new TimeSpan((long) (10_000_000 * GlobalPageLoadTimeOutSecs));
Driver = new FirefoxDriver();

在接下来的内容中,请注意方法中的等待顺序PageReady(Selenium 文档就绪、Ajax、动画),如果您考虑一下,这是有道理的:

  1. 加载包含代码的页面
  2. 使用代码通过 Ajax 从某处加载数据
  3. 呈现数据,可能带有动画

可以在 1 和 2 之间使用 DOM 比较方法之类的方法来增加另一层稳健性。


public void PageReady()
{
    DocumentReady();
    AjaxReady();
    AnimationsReady();
}


private void DocumentReady()
{
    WaitForJavascript(script: "return document.readyState", result: "complete");
}

private void WaitForJavascript(string script, string result)
{
    new WebDriverWait(Driver, DefaultPageLoadTimeout).Until(
        d => ((IJavaScriptExecutor) d).ExecuteScript(script).Equals(result));
}

private void AjaxReady()
{
    WaitForJavascript(script: "return jQuery.active.toString()", result: "0");
}

private void AnimationsReady()
{
    WaitForJavascript(script: "return $(\"animated\").length.toString()", result: "0");
}
于 2019-05-23T05:21:02.453 回答
0

不知道该怎么做,但在我的情况下,页面加载和渲染结束与 Firefox 选项卡中显示的 FAVICON 匹配。

因此,如果我们可以在 webbrowser 中获取 favicon 图像,则网页已完全加载。

但是如何执行这个....

于 2019-11-07T21:20:22.353 回答
0

使用隐式等待对我有用。

driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);

参考这个答案Selenium c# Webdriver: Wait Until Element is Present

于 2020-03-02T00:37:37.073 回答
-1

这是我的做法:

new WebDriverWait(driver, 20).until(
       ExpectedConditions.jsReturnsValue(
                   "return document.readyState === 'complete' ? true : false"));
于 2018-07-05T23:44:41.317 回答