Selenium WebDriver: Wait for complex page with JavaScript to load

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:

  • run a JavaScript script form WebDriver and store the result of document.body.innerHTML in a string variable body.
  • compare the body variable to the previous version of body. if they are the same then set increment a counter notChangedCount otherwise set notChangedCount to 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 >= 10 then exit the loop otherwise loop to the first step.

Do you think it's a valid solution?

213222 次浏览

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

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

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

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

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

如果有人真的知道一个通用的,总是适用的答案,它会实施 everywhere年前,并会使我们的生活如此容易。

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

  1. 正如 Ashwin Prabhu 所说,如果你熟悉这个脚本,你就可以观察它的行为,跟踪它在 window或者 document上的一些变量,等等。但是,这个解决方案并不适用于所有人,只能由您使用,并且只能在有限的页面集中使用。

  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 一种方法 to get the original and not-edited HTML directly by WebDriver), but:

    • 实际断言一个页面需要很长时间,并且可以显著地延长测试时间。
    • 你永远不知道正确的时间间隔是多少。脚本 也许吧下载一些大的东西,需要超过500毫秒有几个脚本在我们公司的内部网页,需要几秒钟的 IE。您的计算机可能暂时资源短缺-说一个反病毒程序将使您的 CPU 工作充分,然后500毫秒可能太短,即使是一个非复杂的脚本。
    • 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.
    • 如果脚本做了一些改变 HTML 以外的事情怎么办?它可以做成千上万的事情,而不仅仅是一些 innerHTML乐趣。
  3. 有些工具可以帮助你。即 进度监听器nsIWebProgressListener等。然而,浏览器对此的支持非常糟糕。Firefox 开始尝试从 FF4开始支持它(仍在发展中) ,IE 在 IE9中有基本的支持。

我想我很快就能想出另一个有缺陷的解决方案。事实是——没有明确的答案时说“现在页面是完整的”,因为永恒的脚本做他们的工作。选择一个最适合你的,但要小心它的缺点。

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

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

注意: 你可以 看看我的整个工作范例

谢谢 Ashwin!

In my case I should need wait for a jquery plugin execution in some element.. specifically "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");
}
}
);

注意: 我正在使用 WebDriver 2

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

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

这是我自己的代码:
SetTimeout 仅在浏览器空闲时执行。
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.

    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 调用时... ...) < br > ... ..。

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

您需要等待 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);
}

下面的代码在我的情况下工作得很完美-我的页面包含复杂的 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 中等待页面加载/就绪

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

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

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

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 之后,尝试查找 webElement。

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

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

我要求我的开发人员创建一个 JavaScript 变量“ isProcessing”,我可以访问(在“ ae”对象中) ,他们在事情开始运行时设置该变量,在事情完成时清除该变量。然后我在一个累加器中运行它,每100毫秒检查一次,直到它在一行中得到5个,总共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.");
}

Here's how I do it:

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

我喜欢你轮询 HTML 直到它稳定的想法。我可以把这个加到我自己的解决方案里。下面的方法是用 C # 编写的,需要 jQuery。

我是一个成功因素(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 document ready,Ajax,animations)中的等待顺序,如果您仔细想想,这是有意义的:

  1. 加载包含代码的页面
  2. 使用代码从某处通过 Ajax 加载数据
  3. present the data, possibly with animations

像 DOM 比较方法这样的东西可以在1和2之间使用,以增加另一层健壮性。


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

不知道如何做到这一点,但在我的情况下,结束页面加载和渲染匹配 FAVICON 显示在火狐标签。

因此,如果我们可以得到的图标图像在网络浏览器,网页是完全加载。

但如何执行这个... 。

使用隐式等待对我很有用。

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

请参考这个答案 Selenium c # WebDriver: 等待 Element 出现

This works for me well with dynamically rendered websites:

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

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

对于复杂/沉重的网页,在执行任何硒测试操作之前,总是建议检查 JavascriptjQuery完成情况。

当您测试 jQuery 完成时,不要忘记添加一个 jQuery 未定义的检查,否则您将得到 ReferenceError: jQuery is not defined错误

因此,您需要添加以下两个检查:

  1. Javascript 检查 :

return ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete")

  1. jQuery check :

(Boolean)((JavascriptExecutor) wd).executeScript("return window.jQuery != undefined && jQuery.active == 0")

Now when you write method then I would suggest to use 流利,等等 in your selenium code rather than implicit or explicit wait. Fluent wait method will help you do operation in between the polling interval wait unlike other waits and is very useful or rather powerful.

以下是你可以直接使用的工作方法:

public static void pageJavaScriptAndJqueryLoad(WebDriver driver, Duration waitTimeout) {
Wait<WebDriver> wait = new FluentWait<>(driver)
.withTimeout(waitTimeout)
.pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class);
wait.until((ExpectedCondition<Boolean>) wd -> {
log.info("Waiting for Page Javascript to load completely");
log.info("document.readyState value is : " + ((JavascriptExecutor) wd).executeScript("return document.readyState"));
log.info("jQuery.active value is : " + ((JavascriptExecutor) wd).executeScript("return window.jQuery != undefined && jQuery.active"));
return ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete") &&
(Boolean)((JavascriptExecutor) wd).executeScript("return window.jQuery != undefined && jQuery.active == 0");
});
}

在上述方法中:

  1. You need to pass your driver and waitTimeout duration as argument to this method. For ex: PageJavaScriptAndJqueryLoad (驱动程序,Duration.ofSecond (120));
  2. 我已经定义了轮询间隔为500毫秒,您可以根据自己的需要进行修改。
  3. 每次轮询完成后,它都会打印 log.info 下给出的3条语句。
  4. 使用这种方法,您可以轻松地添加代码,以确定在执行测试操作之前完全呈现页面的确切时间。