【问题标题】:Selenium WebDriver: Wait for complex page with JavaScript to loadSelenium WebDriver:等待带有 JavaScript 的复杂页面加载
【发布时间】:2012-05-30 00:34:25
【问题描述】:

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

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

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

【问题讨论】:

  • findElement() 不等待 - 你是什么意思?
  • findElement 等待一个元素可用,但有时该元素在 javascript 代码完全初始化之前可用,这就是为什么它不是一个选项。
  • 我忘记了 - 我习惯使用 WebDriverWait 和 ExpectedCondition 它更灵活。

标签: java c# selenium selenium-webdriver webdriver


【解决方案1】:

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

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

    1234563仅供您使用,并且只能在有限的一组页面上使用。
  1. 你的解决方案通过观察 HTML 代码以及它是否有一段时间没有被更改还不错(还有a method 可以直接通过WebDriver 获取原始和未编辑的 HTML),但是:

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

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

【讨论】:

    【解决方案2】:

    谢谢阿什温!

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

    根据您的提示,它非常适合我:

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

    注意:我使用的是 Webdriver 2

    【讨论】:

    • 它对我也很有效。在我的例子中,一个元素在页面加载完成后被 Javascript 修改,而 Selenium 并没有隐式等待。
    • 谓词从何而来?哪个进口??
    • @AmadoSaladino "import com.google.common.base.Predicate;"来自 google lib guava-15.0 链接:mvnrepository.com/artifact/com.google.guava/guava/15.0 有更新的 27.0 版本你可以试试!
    【解决方案3】:

    您需要等待 Javascript 和 jQuery 完成加载。 执行Javascript检查jQuery.active是否为0document.readyState是否为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);
    }
    

    【讨论】:

    • 不确定这是否适用于每个情况,但它对我的用例很有效
    • 它也适用于我,但有一个棘手的部分:假设您的页面已经加载。如果您与元素交互 [例如发送 .click() 或 .send some keys to it] 然后您想检查页面何时完成处理,您需要添加延迟:例如click(), sleep(0.5 sec), 等到 (readyState='complete' & jQuery.active==0)。如果不添加睡眠,则 iQuery 在测试时不会处于活动状态! (我花了几个小时才知道,所以我想分享一下)
    • document.readyState == 'complete' &amp;&amp; jQuery.active == 0 效果很好,但是 React 有类似的东西吗?由于这些检查不会捕获仍然加载反应数据/组件?
    【解决方案4】:

    如果您需要做的只是等待页面上的 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;
            }
        }
    }
    

    【讨论】:

    • 我想在这里指出,我记录了两次轮询中等大小页面所花费的时间,然后在 java 中比较这些字符串,它花费了 20 多毫秒。
    • 起初我发现这是一个肮脏的解决方案,但它似乎工作得很好,而且它不涉及在客户端设置变量。一个警告可能是一个不断被 javascript 更改的页面(即 javascript 时钟),但我没有,所以它可以工作!
    【解决方案5】:

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

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

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

    测试这种情况(例如:window.SomeClass &amp;&amp; window.SomeClass.variable != null)并返回布尔值true/false

    将其包装在 WebDriverWait 中,并等待脚本返回 true

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 谢谢。链接中的代码对我有用,
      【解决方案7】:

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

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

      【讨论】:

      • 你应该使用 selenium 等待而不是那些循环
      【解决方案8】:

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

      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++;
                      }
                  }
      

      在这些JavaScript代码之后尝试findOut webElement。

      WebElement we = wait.until(ExpectedConditions.presenceOfElementLocated(by));
      

      【讨论】:

      • Selenium 默认也是这样做的,这只会给你的脚本增加一些额外的代码执行时间(这可能是足够的时间来加载额外的东西,但又可能不会)
      【解决方案9】:

      我要求我的开发人员创建一个我可以访问的 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.");
      }
      

      【讨论】:

        【解决方案10】:

        要正确处理,您需要处理异常。

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

        注意:您可以see my entire working example here

        【讨论】:

          【解决方案11】:

          对于nodejs Selenium 库,我使用了以下 sn-p。就我而言,我正在寻找添加到窗口中的两个对象,在此示例中为 &lt;SOME PROPERTY&gt;10000 是超时毫秒,&lt;NEXT STEP HERE&gt; 是在窗口上找到属性后发生的情况。

          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);
          });
          

          【讨论】:

            【解决方案12】:

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

            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 标记的 outerhtml

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

            【讨论】:

              【解决方案13】:

              您可以编写一些逻辑来处理这个问题。我写了一个返回WebElement的方法,这个方法会被调用3次,或者你可以增加时间并为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;
                      }
                  }
              

              【讨论】:

                【解决方案14】:

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

                【讨论】:

                  【解决方案15】:

                  我喜欢你轮询 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");
                  }
                  

                  【讨论】:

                    【解决方案16】:

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

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

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

                    【讨论】:

                      【解决方案17】:

                      使用隐式等待对我有用。

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

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

                      【讨论】:

                        【解决方案18】:

                        我是这样做的:

                        new WebDriverWait(driver, 20).until(
                               ExpectedConditions.jsReturnsValue(
                                           "return document.readyState === 'complete' ? true : false"));
                        

                        【讨论】:

                        • Selenium 默认也是这样做的,这只会给你的脚本增加一些额外的代码执行时间(这可能是足够的时间来加载额外的东西,但又可能不会)
                        • a === b ? true : false 必须始终替换为 a === b
                        猜你喜欢
                        • 2012-07-07
                        • 1970-01-01
                        • 2019-01-31
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-07-12
                        • 2016-11-14
                        • 2014-09-26
                        • 2013-09-09
                        相关资源
                        最近更新 更多