C# Selenium WebDriver:等待带有 JavaScript 的复杂页面加载

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10720325/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 14:59:25  来源:igfitidea点击:

Selenium WebDriver: Wait for complex page with JavaScript to load

javac#seleniumselenium-webdriverwebdriver

提问by psadac

I have a web application to test with Selenium. There is a lot of JavaScript running on page load.
This JavaScript code is not so well written but I can't change anything. So waiting for an element to appear in the DOM with findElement()method is not an option.
I want to create a generic function in Java to wait for a page to load, a possible solution would be:

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

  • run a JavaScript script form WebDriver and store the result of document.body.innerHTMLin a string variable body.
  • compare the bodyvariable to the previous version of body. if they are the same then set increment a counter notChangedCountotherwise set notChangedCountto zero.
  • wait for a litte time (50 ms for example).
  • if the page has not changed for some time (500 ms for example) so notChangedCount >= 10then exit the loop otherwise loop to the first step.
  • 运行 JavaScript 脚本表单 WebDriver 并将结果存储document.body.innerHTML在字符串变量中body
  • body变量与body. 如果它们相同,则设置递增计数器,notChangedCount否则设置notChangedCount为零。
  • 等待一点时间(例如 50 毫秒)。
  • 如果页面在一段时间内没有改变(例如 500 毫秒),notChangedCount >= 10那么退出循环,否则循环到第一步。

Do you think it's a valid solution?

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

采纳答案by Petr Jane?ek

If anyone actually knew a general and always-applicable answer, it would have been implemented everywhereages ago and would make our lives SO much easier.

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

There are many things you can do, but every single one of them has a problem:

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

  1. As Ashwin Prabhu said, if you know the script well, you can observe its behaviour and track some of its variables on windowor documentetc. This solution, however, is not for everyone and can be used only by you and only on a limited set of pages.

  2. Your solution by observing the HTML code and whether it has or hasn't been changed for some time is not bad (also, there is a methodto get the original and not-edited HTML directly by WebDriver), but:

    • It takes a long time to actually assert a page and could prolong the test significantly.
    • You never know what the right interval is. The script mightbe downloading something big that takes more than 500 ms. There are several scripts on our company's internal page that take several seconds in IE. Your computer may be temporarily short on resources - say that an antivirus will make your CPU work fully, then 500 ms may be too short even for a noncomplex scripts.
    • Some scripts are never done. They call themselves with some delay (setTimeout()) and work again and again and could possibly change the HTML every time they run. Seriously, every "Web 2.0" page does it. Even Stack Overflow. You could overwrite the most common methods used and consider the scripts that use them as completed, but ... you can't be sure.
    • What if the script does something other than changing the HTML? It could do thousands of things, not just some innerHTMLfun.
  3. There are tools to help you on this. Namely Progress Listenerstogether with nsIWebProgressListenerand some others. The browser support for this, however, is horrible. Firefox began to try to support it from FF4 onwards (still evolving), IE has basic support in IE9.

  1. 正如阿什温帕布说,如果你知道剧本好,你可以观察它的行为,并跟踪它的一些变量对windowdocument等此解决方案,但是,是不是对每个人都可以通过你,只有在有限的一组只用页。

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

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

And I guess I could come up with another flawed solution soon. The fact is - there's no definite answer on when to say "now the page is complete" because of the everlasting scripts doing their work. Pick the one that serves you best, but beware of its shortcomings.

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

回答by Ashwin Prabhu

Does the JS library define/initialize any well known variable on the window?

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

If so you could wait for the variable to appear. You can use

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

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

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

to test for this condition (something like: window.SomeClass && window.SomeClass.variable != null) and return a boolean true/ false.

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

Wrap this in a WebDriverWait, and wait until the script returns true.

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

回答by djangofan

To do it properly, you need to handle the exceptions.

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

Here is how I do a wait for an iFrame. This requires that your JUnit test class pass the instance of RemoteWebDriver into the page object :

这是我如何等待 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") ) );
    }
....

NOTE: You can see my entire working example here.

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

回答by Eduardo Fabricio

Thanks Ashwin !

谢谢阿什温!

In my case I should need wait for a jquery plugin execution in some element.. specifically "qtip"

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

based in your hint, it worked perfectly for me :

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

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

Note: I'm using Webdriver 2

注意:我使用的是 Webdriver 2

回答by Vaibhav Pandey

You can write some logic to handle this. I have write a method that will return the WebElementand this method will be called three times or you can increase the time and add a null check for WebElementHere is an example

您可以编写一些逻辑来处理此问题。我已经写了一个方法,它将返回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;
        }
    }

回答by Mariusz

Here's from my own code:
Window.setTimeout executes only when browser is idle.
So calling the function recursively (42 times) will take 100ms if there is no activity in the browser and much more if the browser is busy doing something else.

这是我自己的代码:
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;

As a bonus the counter can be reset on document.readyState or on jQuery Ajax calls or if any jQuery animations are running (only if your app uses jQuery for ajax calls...)
...

作为奖励,计数器可以在 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); " +
                            " } "+ 

...

...

EDIT: I notice executeAsyncScript doesn't work well if a new page loads and the test might stop responding indefinetly, better to use this on instead.

编辑:我注意到如果新页面加载并且测试可能会不确定地停止响应,我注意到 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");
        }
    };
}

回答by LINGS

You need to wait for Javascript and jQuery to finish loading. Execute Javascript to check if jQuery.activeis 0and document.readyStateis complete, which means the JS and jQuery load is complete.

您需要等待 Javascript 和 jQuery 完成加载。执行 Javascript 来检查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);
}

回答by user790049

The below code works perfectly in my case - my page contains complex java scripts

以下代码在我的情况下完美运行 - 我的页面包含复杂的 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; 
   }   
  }
 }

Source - How To Wait For Page To Load/Ready In Selenium WebDriver

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

回答by Roma Kap

I had a same issue. This solution works for me from WebDriverDoku:

我有同样的问题。这个解决方案适用于 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

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

回答by TheFreddyKilo

If all you need to do is wait for the html on the page to become stable before trying to interact with elements, you can poll the DOM periodically and compare the results, if the DOMs are the same within the given poll time, you're golden. Something like this where you pass in the maximum wait time and the time between page polls before comparing. Simple and effective.

如果您需要做的就是在尝试与元素交互之前等待页面上的 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;
        }
    }
}