![](/img/trans.png)
[英]Selenium Webdriver - wait for page to completely load in Java&JavaScript(ajax/jquery/animation etc.)
[英]Selenium WebDriver: Wait for complex page with JavaScript to load
我有一個 web 應用程序要用 Selenium 進行測試。頁面加載時有很多 JavaScript 正在運行。
這段 JavaScript 代碼寫得不太好,但我無法更改任何內容。 因此,使用findElement()
方法等待元素出現在 DOM 中不是一種選擇。
我想在 Java 中創建一個通用的 function 來等待頁面加載,一個可能的解決方案是:
document.body.innerHTML
的結果存儲在字符串變量body
中。body
變量與以前版本的body
進行比較。 如果它們相同則設置遞增計數器notChangedCount
否則將notChangedCount
設置為零。notChangedCount >= 10
然后退出循環,否則循環到第一步。你認為這是一個有效的解決方案嗎?
如果有人真的知道一個通用且始終適用的答案,那么很久以前它就會在任何地方實施,並且會讓我們的生活變得更加輕松。
你可以做很多事情,但每一件事都有一個問題:
正如 Ashwin Prabhu 所說,如果你很了解這個腳本,你可以觀察它的行為並在window
或document
等上跟蹤它的一些變量。但是,這個解決方案並不適合所有人,只能由你使用,並且只能在有限的情況下使用頁集。
通過觀察 HTML 代碼以及它是否在一段時間內沒有更改,您的解決方案還不錯(還有一種方法可以通過WebDriver
直接獲取原始且未編輯的 HTML),但是:
setTimeout()
)並一次又一次地工作,並且每次運行時都可能更改 HTML。 說真的,每個“Web 2.0”頁面都這樣做。 甚至堆棧溢出。 您可以覆蓋最常用的方法並將使用它們的腳本視為已完成,但是……您不能確定。innerHTML
樂趣。有一些工具可以幫助您解決這個問題。 即Progress Listeners與nsIWebProgressListener和其他一些。 然而,瀏覽器對此的支持非常糟糕。 Firefox 從 FF4 開始嘗試支持它(仍在發展中),IE 在 IE9 中有基本支持。
我想我很快就會想出另一個有缺陷的解決方案。 事實是 - 關於何時說“現在頁面已完成”沒有明確的答案,因為永恆的腳本在做他們的工作。 選擇最適合您的那個,但要注意它的缺點。
謝謝阿什溫!
在我的情況下,我應該等待某個元素中的 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 庫是否在窗口上定義/初始化了任何眾所周知的變量?
如果是這樣你可以等待變量出現。 您可以使用
((JavascriptExecutor)driver).executeScript(String script, Object... args)
以測試這種情況(是這樣的: window.SomeClass && window.SomeClass.variable != null
),並返回一個布爾true
/ false
。
在包裝這個WebDriverWait
,並等待腳本返回true
。
如果您需要做的就是在嘗試與元素交互之前等待頁面上的 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;
}
}
}
我有同樣的問題。 這個解決方案適用於 WebDriverDoku:
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));
以下代碼在我的情況下完美運行 - 我的頁面包含復雜的 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;
}
}
}
在找到頁面上的任何元素之前,可以使用兩個條件來檢查頁面是否已加載:
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));
我要求我的開發人員創建一個我可以訪問的 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.");
}
要正確執行此操作,您需要處理異常。
這是我等待 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") ) );
}
....
注意:您可以在此處查看我的整個工作示例。
對於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);
});
對於動態呈現的網站,這對我很有效:
WebDriverWait wait = new WebDriverWait(driver, 50); wait.until((ExpectedCondition<Boolean>) wd -> ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));
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) { }
String script = "return document.getElementsByTagName(\\"html\\")[0].outerHTML;"; content = ((JavascriptExecutor) driver).executeScript(script).toString();
您可以編寫一些邏輯來處理此問題。 我編寫了一個返回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;
}
}
這是我自己的代碼:
Window.setTimeout 僅在瀏覽器空閑時執行。
因此,如果瀏覽器中沒有活動,遞歸調用該函數(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 調用或任何 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");
}
};
}
我喜歡你輪詢 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 之間使用像您的 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");
}
有沒有人發現如何在React Application中獲得活動的Ajax調用計數
不知道該怎么做,但就我而言,頁面加載結束和渲染與 Firefox 選項卡中顯示的 FAVICON 匹配。
所以如果我們能在瀏覽器中獲取到 favicon 圖像,那么網頁就完全加載了。
但是如何執行這個......
使用隱式等待對我有用。
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10);
對於復雜/繁重的網頁,始終建議在執行任何 selenium 測試操作之前檢查javascript和jQuery 是否完成。
當您測試 jQuery 完成時,不要忘記添加對 jQuery 未定義的檢查,否則您將以ReferenceError: jQuery is not defined
錯誤結束。
所以你需要添加以下 2 個檢查:
return ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete")
(Boolean)((JavascriptExecutor) wd).executeScript("return window.jQuery.= undefined && jQuery.active == 0")
現在,當您編寫方法時,我建議您在selenium 代碼中使用Fluent Wait ,而不是隱式或顯式等待。 Fluent wait 方法將幫助您在輪詢間隔等待之間進行操作,這與其他等待不同,非常有用或相當強大。
以下是您可以直接使用的工作方法:
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"); }); }
在上面的方法中:
這里有一個python版本給有需要的人
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def wait_for_js_to_load(driver):
wait = WebDriverWait(driver, 30)
# Wait for jQuery to load
jQuery_load = EC.presence_of_element_located((By.XPATH, "//script[contains(@src,'jquery')]"))
wait.until(jQuery_load)
# Wait for Javascript to load
js_load = EC.presence_of_element_located((By.XPATH, "//script[contains(@src,'javascript')]"))
wait.until(js_load)
這是我的方法:
new WebDriverWait(driver, 20).until(
ExpectedConditions.jsReturnsValue(
"return document.readyState === 'complete' ? true : false"));
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.