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


102

我有一个Web应用程序可以使用Selenium进行测试。页面加载时运行着许多JavaScript。
该JavaScript代码编写得不太好,但是我什么也不能更改。因此,使用findElement()方法等待元素出现在DOM 中不是一个选择。
我想在Java中创建一个通用函数以等待页面加载,可能的解决方案是:

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

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


findElement()不等待-这是什么意思?
罗斯蒂斯拉夫·马特

1
findElement等待一个元素可用,但是有时该元素在javascript代码完全初始化之前可用,这就是为什么它不是一个选择。
psadac 2012年

我忘了-我习惯使用WebDriverWait和ExpectedCondition更加灵活。
罗斯蒂斯拉夫·马特

Answers:


73

如果有人真的知道一个通​​用且始终适用的答案,那么它将很久以前的所有地方得到实施,这将使我们的生活变得如此轻松。

您可以做很多事情,但是其中每一个都有一个问题:

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

  2. 通过观察HTML代码以及在一段时间内是否进行过更改,您的解决方案也不错(同样,有一种方法可以通过来直接获取原始HTML和未经编辑的HTML WebDriver),但是:

    • 实际声明页面需要花费很长时间,并且可能会大大延长测试时间。
    • 您永远不知道正确的间隔是多少。该脚本可能正在下载耗时超过500毫秒的大型内容。我们公司内部页面上的几个脚本在IE中需要花费几秒钟的时间。您的计算机可能暂时缺乏资源-说防病毒软件可以使您的CPU正常工作,那么即使对于非复杂脚本,500 ms也可能太短。
    • 有些脚本永远不会完成。它们会以一定的延迟(setTimeout())调用自己,并一次又一次地工作,并且有可能在每次运行时更改HTML。认真地说,每个“ Web 2.0”页面都可以做到。甚至堆栈溢出。您可以覆盖最常用的方法,并考虑使用它们的脚本是否完整,但是...您不确定。
    • 如果脚本除了更改HTML以外还执行其他操作,该怎么办?它可以做成千上万的事情,而不仅仅是一些innerHTML乐趣。
  3. 有一些工具可以帮助您。即Progress ListenernsIWebProgressListener等。但是,浏览器对此的支持是可怕的。Firefox开始尝试从FF4开始支持它(仍在不断发展),IE 9对IE9具有基本的支持。

而且我想我很快会想出另一个有缺陷的解决方案。事实是-由于永恒的脚本正在执行工作,因此何时说“现在页面已完成”没有确切的答案。选择一个最能为您服务的产品,但要提防它的缺点。


31

谢谢阿什温!

就我而言,我应该等待某个元素中的jquery插件执行。特别是“ qtip”

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

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

注意:我正在使用Webdriver 2


2
它对我也很好。就我而言,页面完成加载后,Javascript修改了一个元素,而Selenium并没有隐式等待它。
Noxxys

2
谓词从哪里来?哪个进口?
Amado Saladino

@AmadoSaladino“ import com.google.common.base.Predicate;” 来自google lib guava-15.0的链接:mvnrepository.com/artifact/com.google.guava/guava/15.0,您可以尝试使用更高版本的27.0!
Eduardo Fabricio

26

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

2
不知道这是否在每种情况下都行得通,但是对于我的用例来说,它是一种享受
DaveH 2015年

1
它也适用于我,但是有一个棘手的部分:假设您的页面已经加载。如果您与某个元素进行交互(例如,向其发送.click()或向其发送一些键),然后要检查页面处理完成的时间,则需要添加一个延迟:例如click(),sleep(0.5秒) ),直到(readyState ='complete'&jQuery.active == 0)。如果您不添加睡眠,则iQuery在测试时将不会激活!(花了我几个小时才找到
答案

document.readyState == 'complete' && jQuery.active == 0工作很棒,但是React有类似的东西吗?由于这些检查仍无法捕获仍在加载的反应数据/组件?
Rain9333



7

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

1
我想在这里指出,我记录了两次轮询中等大小页面所花费的时间,然后在Java中比较这些字符串,这花费了20毫秒多一点的时间。
TheFreddyKilo

2
起初,我发现这是一个肮脏的解决方案,但它似乎工作得很好,并且不涉及在客户端设置变量。一个警告可能是一个页面,该页面经常被javascript(即javascript时钟)更改,但是我没有,所以它可以工作!
彼得

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中等待页面加载/就绪


2
您应该使用硒等待而不是这些循环
cocorossello

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

1
Selenium默认情况下也会执行此操作,这只会在脚本中添加一些额外的代码执行时间(这可能是足够的时间来加载其他内容,但随后可能不是)
Ardesco

2

我要求开发人员创建一个JavaScript变量“ isProcessing”,我可以在事情开始运行时访问它们(在“ ae”对象中),并在事情完成时清除它们。然后,我在一个累加器中运行该累加器,该累加器每100 ms对其进行一次检查,直到连续五次获得500 ms为止,而没有任何更改。如果过了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.");
}

1

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

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

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

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


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

0

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

0

这是来自我自己的代码:
Window.setTimeout仅在浏览器空闲时执行。
因此,如果浏览器中没有活动,则递归调用该函数(42次)将花费100毫秒,如果浏览器正忙于执行其他操作,则将花费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");
        }
    };
}

0

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

因此,如果我们可以在Web浏览器中获得收藏夹图像,则表明该网页已完全加载。

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



-1

这是我的方法:

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

Selenium在默认情况下也执行此操作,这只会在脚本中增加一些代码执行时间(这可能是足够的时间来加载其他内容,但随后可能不是)
Ardesco

-1

我喜欢您轮询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");
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.