首页 文章

Selenium WebDriver:等待加载JavaScript的复杂页面

提问于
浏览
83

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

  • 运行JavaScript脚本表单WebDriver并将 document.body.innerHTML 的结果存储在字符串变量 body 中 .

  • body 变量与之前版本的 body 进行比较 . 如果它们相同则设置增量计数器 notChangedCount 否则将 notChangedCount 设置为零 .

  • 等待一个小时(例如50毫秒) .

  • 如果页面没有改变一段时间(例如500毫秒),那么 notChangedCount >= 10 然后退出循环,否则循环到第一步 .

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

14 回答

  • 0

    如果有人真的知道一般的,总是适用的答案,它会在很久以前的任何地方实施,并且会让我们的生活变得更加容易 .

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

    • 正如Ashwin Prabhu所说,如果你对脚本有所了解,你可以观察它的行为,并在 windowdocument 等上跟踪它的一些变量 . 但是,这个解决方案并不适合所有人,只能由你使用,而且只能用于有限的一组页面 .

    • 通过观察HTML代码及其是否已经有一段时间没有被更改的解决方案也不错(同样,a method直接通过 WebDriver 获取原始和未经编辑的HTML),但是:

    • 实际断言页面需要很长时间才能显着延长测试时间 .

    • 你永远不知道正确的间隔是什么 . 该脚本可能正在下载超过500毫秒的大型内容 . 我们公司的内部页面上有几个脚本在IE中需要几秒钟 . 您的计算机可能暂时缺少资源 - 比如防病毒软件会让您的CPU完全运行,那么即使对于非复杂的脚本,500毫秒也可能太短 .

    • 有些脚本永远不会完成 . 他们称自己有一些延迟( setTimeout() )并且一次又一次地工作,并且每次运行时都可能改变HTML . 说真的,每个"Web 2.0"页都做到了 . 甚至Stack Overflow . 您可以覆盖最常用的方法并考虑使用它们完成的脚本,但是......您无法确定 .

    • 如果脚本执行除更改HTML之外的其他操作,该怎么办?它可以做成千上万的事情,而不仅仅是一些乐趣 .

    • 有一些工具可以帮助您 . 即Progress ListenersnsIWebProgressListener以及其他一些人 . 然而,浏览器对此的支持非常糟糕 . Firefox开始尝试从FF4开始支持它(仍在不断发展),IE在IE9中有基本支持 .

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

  • 8

    谢谢Ashwin!

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

    根据你的提示,它对我来说非常有用:

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

    注意:我正在使用Webdriver 2

  • 0

    你需要等待Javascript和jQuery才能完成加载 . 执行Javascript以检查 jQuery.active0document.readyStatecomplete ,这意味着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);
    }
    
  • 21

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

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

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

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

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

  • 59

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

  • 1

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

  • 2

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

    对于 nodejs Selenium库,我使用了以下代码段 . 在我的情况下,我正在寻找添加到窗口的两个对象,在这个示例中是 <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);
    });
    
  • 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.");
    }
    
  • 26

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

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

  • 1

    你可以编写一些逻辑来处理这个问题 . 我已经编写了一个返回 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;
            }
        }
    
  • 0

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

    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");
            }
        };
    }
    
  • -1

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

    WebDriverWait wait = new WebDriverWait(driver, 50);
    

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

    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));
    
  • 3

    我是这样做的:

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

相关问题