簡體   English   中英

Selenium WebDriver:等待帶有 JavaScript 的復雜頁面加載

[英]Selenium WebDriver: Wait for complex page with JavaScript to load

我有一個 web 應用程序要用 Selenium 進行測試。頁面加載時有很多 JavaScript 正在運行。
這段 JavaScript 代碼寫得不太好,但我無法更改任何內容。 因此,使用findElement()方法等待元素出現在 DOM 中不是一種選擇。
我想在 Java 中創建一個通用的 function 來等待頁面加載,一個可能的解決方案是:

  • 從 WebDriver 運行 JavaScript 腳本並將document.body.innerHTML的結果存儲在字符串變量body中。
  • body變量與以前版本的body進行比較。 如果它們相同則設置遞增計數器notChangedCount否則將notChangedCount設置為零。
  • 稍等片刻(例如 50 毫秒)。
  • 如果頁面在一段時間內沒有改變(例如 500 毫秒),那么notChangedCount >= 10然后退出循環,否則循環到第一步。

你認為這是一個有效的解決方案嗎?

如果有人真的知道一個通​​用且始終適用的答案,那么很久以前它就會在任何地方實施,並且會讓我們的生活變得更加輕松。

你可以做很多事情,但每一件事都有一個問題:

  1. 正如 Ashwin Prabhu 所說,如果你很了解這個腳本,你可以觀察它的行為並在windowdocument等上跟蹤它的一些變量。但是,這個解決方案並不適合所有人,只能由你使用,並且只能在有限的情況下使用頁集。

  2. 通過觀察 HTML 代碼以及它是否在一段時間內沒有更改,您的解決方案還不錯(還有一種方法可以通過WebDriver直接獲取原始且未編輯的 HTML),但是:

    • 實際斷言頁面需要很長時間,並且可能會顯着延長測試時間。
    • 你永遠不知道什么是正確的間隔。 該腳本可能正在下載需要超過 500 毫秒的大文件。 我們公司的內部頁面上有幾個腳本在IE中需要幾秒鍾。 您的計算機可能暫時缺乏資源 - 假設防病毒軟件將使您的 CPU 充分工作,那么即使對於不復雜的腳本,500 毫秒也可能太短了。
    • 有些腳本永遠不會完成。 他們以一些延遲調用自己( setTimeout() )並一次又一次地工作,並且每次運行時都可能更改 HTML。 說真的,每個“Web 2.0”頁面都這樣做。 甚至堆棧溢出。 您可以覆蓋最常用的方法並將使用它們的腳本視為已完成,但是……您不能確定。
    • 如果腳本除了更改 HTML 之外還執行其他操作怎么辦? 它可以做成千上萬的事情,而不僅僅是一些innerHTML樂趣。
  3. 有一些工具可以幫助您解決這個問題。 Progress ListenersnsIWebProgressListener和其他一些。 然而,瀏覽器對此的支持非常糟糕。 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.active0document.readyStatecomplete的,這意味着所述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")));

http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

以下代碼在我的情況下完美運行 - 我的頁面包含復雜的 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 中等待頁面加載/就緒

在找到頁面上的任何元素之前,可以使用兩個條件來檢查頁面是否已加載:

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

對於動態呈現的網站,這對我很有效:

  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 標簽的外層 html

 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. 使用代碼通過 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");
}

有沒有人發現如何在React Application中獲得活動的Ajax調用計數

不知道該怎么做,但就我而言,頁面加載結束和渲染與 Firefox 選項卡中顯示的 FAVICON 匹配。

所以如果我們能在瀏覽器中獲取到 favicon 圖像,那么網頁就完全加載了。

但是如何執行這個......

使用隱式等待對我有用。

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

請參閱此答案Selenium c# Webdriver: Wait until Element is Present

對於復雜/繁重的網頁,始終建議在執行任何 selenium 測試操作之前檢查javascriptjQuery 是否完成。

當您測試 jQuery 完成時,不要忘記添加對 jQuery 未定義的檢查,否則您將以ReferenceError: jQuery is not defined錯誤結束。

所以你需要添加以下 2 個檢查:

  1. Javascript 查詢

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

  1. jQuery 查詢

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

在上面的方法中:

  1. 您需要將驅動程序和 waitTimeout 持續時間作為參數傳遞給此方法。 例如: pageJavaScriptAndJqueryLoad(driver, Duration.ofSeconds(120)) ;
  2. 我已將輪詢間隔定義為 500 毫秒。 您可以根據需要進行修改。
  3. 每次完成輪詢時,它都會打印 log.info 下給出的第 3 條語句。
  4. 使用它,您可以輕松添加代碼以確定在執行測試操作之前完全呈現頁面的確切時間。

這里有一個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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM