Selenium WebDriver:等待带有JavaScript(JS)的复杂页面加载
我有一个Web应用程序来testingselenium。 页面加载时有很多JavaScript运行。
这个JavaScript代码写得不是很好,但是我不能改变任何东西。 因此,使用findElement()
方法等待元素出现在DOM中不是一个选项。
我想在Java中创build一个通用函数来等待页面加载,一个可能的解决scheme是:
- 运行WebDriver的JavaScript脚本,并将
document.body.innerHTML
的结果存储在stringvariablesbody
。 - 将
body
variables与body
的先前版本进行比较。 如果他们是相同的然后设置递增计数器notChangedCount
否则设置notChangedCount
为零。 - 等待一个小时(例如50毫秒)。
- 如果页面在一段时间内没有改变(例如500毫秒),那么
notChangedCount
> = 10,否则退出循环,否则循环到第一步。
你认为这是一个有效的解决scheme?
如果有人真的知道了一个普遍适用的答案,那么这个答案在很久以前就会实现,并且会使我们的生活变得容易得多。
你可以做很多事情,但是他们每一个人都有一个问题:
-
正如Ashwin Prabhu所说,如果你很了解脚本,就可以观察它的行为,并在
window
或document
等上跟踪它的一些variables。然而,这个解决scheme并不适用于每个人,只能由你来使用,而且只能在有限的一组页面。 -
您的解决scheme通过观察HTML代码以及它是否已经改变了一段时间也不错(也有一种方法可以直接通过
WebDriver
获得原始的和未经编辑的HTML),但是:- 实际声明页面需要很长时间,并且可以显着延长testing时间。
- 你永远不知道什么是正确的时间间隔。 该脚本可能正在下载超过500毫秒的大东西。 在我们公司的内部页面上有几个脚本,在IE中需要几秒钟。 您的计算机可能暂时缺less资源 – 例如,防病毒将使您的CPU完全工作,那么即使对于非复杂脚本,500毫秒也可能太短。
- 一些脚本从来没有完成。 他们自己调用一些延迟(
setTimeout()
),并且一次又一次地工作,每次运行时都可能改变HTML。 严重的是,每个“Web 2.0”页面都可以。 即使堆栈溢出。 你可以覆盖最常用的方法,并考虑使用它们的脚本已完成,但是…你不能确定。 - 如果脚本除了更改HTML以外还做了什么? 它可以做成千上万的事情,而不仅仅是一些
innerHTML
乐趣。
-
有工具来帮助你。 即进度监听器和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
是否为0
, document.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
。
将其WebDriverWait
在WebDriverWait
,等待脚本返回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")));
如果您只需要等待页面上的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));