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

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

  • 运行WebDriver的JavaScript脚本,并将document.body.innerHTML的结果存储在stringvariablesbody
  • bodyvariables与body的先前版本进行比较。 如果他们是相同的然后设置递增计数器notChangedCount否则设置notChangedCount为零。
  • 等待一个小时(例如50毫秒)。
  • 如果页面在一段时间内没有改变(例如500毫秒),那么notChangedCount > = 10,否则退出循环,否则循环到第一步。

你认为这是一个有效的解决scheme?

如果有人真的知道了一个普遍适用的答案,那么这个答案很久以前就会实现,并且会使我们的生活变得容易得多。

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

  1. 正如Ashwin Prabhu所说,如果你很了解脚本,就可以观察它的行为,并在windowdocument等上跟踪它的一些variables。然而,这个解决scheme并不适用于每个人,只能由你来使用,而且只能在有限的一组页面。

  2. 您的解决scheme通过观察HTML代码以及它是否已经改变了一段时间也不错(也有一种方法可以直接通过WebDriver获得原始的和未经编辑的HTML),但是:

    • 实际声明页面需要很长时间,并且可以显着延长testing时间。
    • 你永远不知道什么是正确的时间间隔。 该脚本可能正在下载超过500毫秒的大东西。 在我们公司的内部页面上有几个脚本,在IE中需要几秒钟。 您的计算机可能暂时缺less资源 – 例如,防病毒将使您的CPU完全工作,那么即使对于非复杂脚本,500毫秒也可能太短。
    • 一些脚本从来没有完成。 他们自己调用一些延迟( setTimeout() ),并且一次又一次地工作,每次运行时都可能改变HTML。 严重的是,每个“Web 2.0”页面都可以。 即使堆栈溢出。 你可以覆盖最常用的方法,并考虑使用它们的脚本已完成,但是…你不能确定。
    • 如果脚本除了更改HTML以外还做了什么? 它可以做成千上万的事情,而不仅仅是一些innerHTML乐趣。
  3. 有工具来帮助你。 即进度监听器和nsIWebProgressListener一起。 浏览器对此的支持是可怕的。 Firefox从FF4开始尝试支持它(仍在不断发展),IE在IE9中有基本的支持。

而且我想我很快就能想出另一个有缺陷的解决scheme。 事实是,关于何时说“现在页面已经完成”了,因为永远的脚本在工作,所以没有明确的答案。 select一个最好的服务,但要小心它的缺点。

感谢Ashwin!

在我的情况下,我应该需要等待一些jQuery插件执行一些元素..特别是“qtip”

根据你的提示,它对我来说非常合适:

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

注意:我正在使用Webdriver 2

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

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

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

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

来testing这个条件(如: window.SomeClass && window.SomeClass.variable != null )并返回布尔值true / false

将其WebDriverWaitWebDriverWait ,等待脚本返回true

下面的代码完全适用于我的情况 – 我的页面包含复杂的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

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

要做到这一点,你需要处理例外。

这里是我如何等待一个iFrame。 这要求您的JUnittesting类将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") ) ); } .... 

注意:您可以在这里看到我的整个工作示例 。

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

这是来自我自己的代码:
Window.setTimeout仅在浏览器空闲时执行。
因此,如果浏览器中没有任何活动,并且浏览器忙于做其他事情,那么recursion地调用函数(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调用或如果有任何jQueryanimation正在运行(只有当您的应用程序使用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不能很好地工作,如果一个新的页面加载和testing可能会停止响应indefinetly,更好地使用此代替。

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

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

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

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

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