2012-05-23 28 views
73

Tôi có một ứng dụng web để kiểm tra bằng selen. Có rất nhiều JavaScript chạy trên tải trang.
Mã JavaScript này không được viết tốt nhưng tôi không thể thay đổi bất cứ điều gì. Vì vậy, việc đợi một phần tử xuất hiện trong DOM với phương pháp findElement() không phải là một tùy chọn.
Tôi muốn tạo ra một hàm tổng quát trong Java để chờ đợi cho một trang để tải, một giải pháp khả thi sẽ là:Selenium WebDriver: Đợi trang phức tạp có JavaScript (JS) để tải

  • chạy JavaScript hình thức kịch bản WebDriver và lưu trữ kết quả của document.body.innerHTML trong một chuỗi biến body.
  • so sánh biến số body với phiên bản trước của body. nếu chúng giống nhau thì đặt tăng notChangedCount nếu không đặt notChangedCount thành 0.
  • đợi thời gian litte (ví dụ 50 ms).
  • nếu trang không thay đổi trong một thời gian (ví dụ 500 ms) sao cho notChangedCount> = 10 rồi thoát vòng lặp nếu không lặp lại bước đầu tiên.

Bạn có nghĩ đó là giải pháp hợp lệ không?

+0

findElement() không chờ - bạn có ý gì với điều đó? –

+1

'findElement' chờ một phần tử có sẵn, nhưng đôi khi phần tử có sẵn trước khi mã javascript được khởi tạo hoàn toàn, đó là lý do tại sao nó không phải là một tùy chọn. – psadac

+0

Tôi quên nó - Tôi đã quen với việc sử dụng WebDriverWait và ExpectedCondition nó linh hoạt hơn. –

Trả lời

51

Nếu bất kỳ ai thực sự biết câu trả lời chung và luôn áp dụng, nó sẽ được triển khai ở mọi nơi lứa tuổi trước và sẽ làm cho cuộc sống của chúng ta dễ dàng hơn nhiều.

Có rất nhiều điều bạn có thể làm, nhưng mỗi đơn một trong số họ có một vấn đề:

  1. Như Ashwin Prabhu nói, nếu bạn biết kịch bản tốt, bạn có thể quan sát hành vi của nó và theo dõi một số các biến của nó trên window hoặc document vv Tuy nhiên, giải pháp này không dành cho tất cả mọi người và chỉ có thể được bạn sử dụng và chỉ trên một nhóm trang giới hạn.

  2. Giải pháp của bạn bằng cách quan sát mã HTML và liệu nó có hay không bị thay đổi trong một thời gian không phải là xấu (ngoài ra, còn có a method để nhận HTML gốc và không chỉnh sửa trực tiếp bởi WebDriver), nhưng:

    • Mất nhiều thời gian để thực sự khẳng định trang và có thể kéo dài thử nghiệm đáng kể.
    • Bạn không bao giờ biết khoảng thời gian phù hợp là gì. Tập lệnh có thể tải xuống nội dung nào đó lớn hơn 500 mili giây.Có một số tập lệnh trên trang nội bộ của công ty chúng tôi mất vài giây trong IE. Máy tính của bạn có thể tạm thời thiếu tài nguyên - giả sử rằng một phần mềm chống vi-rút sẽ làm cho CPU của bạn hoạt động đầy đủ, sau đó 500 ms có thể quá ngắn ngay cả đối với một tập lệnh không phức tạp.
    • Một số tập lệnh không bao giờ được thực hiện. Họ tự gọi mình với một số chậm trễ (setTimeout()) và làm việc một lần nữa và một lần nữa và có thể có thể thay đổi HTML mỗi khi họ chạy. Nghiêm túc, mọi trang "Web 2.0" đều làm điều đó. Ngay cả Stack Overflow. Bạn có thể ghi đè lên các phương pháp phổ biến nhất được sử dụng và xem xét các tập lệnh sử dụng chúng như đã hoàn thành, nhưng ... bạn không thể chắc chắn.
    • Điều gì xảy ra nếu tập lệnh thực hiện điều gì khác ngoài việc thay đổi HTML? Nó có thể làm hàng ngàn thứ, không chỉ là một số niềm vui innerHTML.
  3. Có các công cụ giúp bạn thực hiện việc này. Cụ thể là Progress Listeners cùng với nsIWebProgressListener và một số loại khác. Tuy nhiên, trình duyệt hỗ trợ cho điều này là khủng khiếp. Firefox bắt đầu cố gắng hỗ trợ nó từ FF4 trở đi (vẫn đang phát triển), IE có hỗ trợ cơ bản trong IE9.

Và tôi đoán tôi có thể sớm đưa ra một giải pháp hoàn hảo khác. Thực tế là - không có câu trả lời rõ ràng khi nói "bây giờ trang đã hoàn tất" vì các tập lệnh vĩnh cửu đang thực hiện công việc của họ. Chọn một trong những phục vụ bạn tốt nhất, nhưng hãy cẩn thận của những thiếu sót của nó.

8

Thư viện JS có định nghĩa/khởi tạo bất kỳ biến nổi tiếng nào trên cửa sổ không?

Nếu bạn có thể đợi biến xuất hiện. Bạn có thể sử dụng

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

để kiểm tra tình trạng này (cái gì đó như: window.SomeClass && window.SomeClass.variable != null) và trả về một boolean true/false.

Đặt mã này vào số WebDriverWait và đợi cho đến khi tập lệnh trả về true.

0

Để làm điều đó đúng cách, bạn cần xử lý các ngoại lệ.

Đây là cách tôi chờ một iFrame. Điều này yêu cầu lớp kiểm tra JUnit của bạn chuyển thể hiện của RemoteWebDriver vào đối tượng trang:

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

LƯU Ý: Bạn có thể see my entire working example here.

23

Cảm ơn Ashwin!

Trong trường hợp của tôi, tôi nên cần phải chờ một jQuery plugin thực hiện ở một số yếu tố đặc biệt .. "qtip"

có trụ sở tại gợi ý của bạn, nó làm việc một cách hoàn hảo đối với tôi:

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

Lưu ý: Tôi đang sử dụng Webdriver 2

+1

Khối mã này hoạt động tuyệt vời cho tôi. – emery

+1

Nó hoạt động rất tốt cho tôi. Trong trường hợp của tôi, một phần tử đã được sửa đổi bởi Javascript ngay sau khi trang tải xong, và Selenium không chờ đợi điều đó một cách ngầm định. – Noxxys

0

Bạn có thể viết một số logic để xử lý việc này. Tôi có viết một phương pháp mà sẽ trả lại WebElement và phương pháp này sẽ được gọi là ba lần hoặc bạn có thể tăng thời gian và thêm một kiểm tra null cho WebElement Dưới đây là một ví dụ

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

Dưới đây là từ mã của riêng tôi:
Window.setTimeout chỉ thực thi khi trình duyệt không hoạt động.
Vì vậy, gọi hàm đệ quy (42 lần) sẽ mất 100ms nếu không có hoạt động nào trong trình duyệt và nhiều hơn nữa nếu trình duyệt đang bận làm việc khác.

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; 

Như một phần thưởng quầy có thể được thiết lập lại trên document.readyState hoặc trên jQuery Ajax gọi hoặc nếu bất kỳ hình ảnh động jQuery đang chạy (chỉ khi ứng dụng của bạn sử dụng jQuery cho các cuộc gọi 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); " + 
          " } "+ 

...

EDIT: Tôi nhận thấy executeAsyncScript không hoạt động tốt nếu một tải trang mới và thử nghiệm có thể ngừng đáp ứng indefinetly, tốt hơn để sử dụng này trên để thay thế.

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

Bạn cần đợi Javascript và jQuery hoàn tất tải. Thực thi Javascript để kiểm tra xem jQuery.active0document.readyStatecomplete, có nghĩa là tải JS và jQuery đã hoàn tất.

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

Không chắc chắn nếu điều này sẽ làm việc trong _every_ hoàn cảnh, nhưng nó hoạt động một điều trị cho trường hợp sử dụng của tôi – DaveH

+0

Nó làm việc cho tôi quá nhưng có một phần phức tạp: Hãy nói rằng trang của bạn đã được tải. Nếu bạn tương tác với một phần tử [ví dụ: gửi một .click() hoặc .send một số khóa tới nó] và sau đó bạn muốn kiểm tra khi trang của bạn xử lý xong, bạn cần phải thêm thời gian trễ: ví dụ: click(), ngủ (0,5 giây), đợi đến khi (readyState = 'complete' & jQuery.active == 0).Nếu bạn không thêm giấc ngủ, iQuery sẽ không hoạt động tại thời điểm thử nghiệm! (Tôi đã mất vài giờ để tìm hiểu, vì vậy tôi nghĩ để chia sẻ nó) –

3

Mã dưới đây hoạt động hoàn hảo trong trường hợp của tôi - trang web của tôi có chứa script java phức tạp

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

Nguồn - How To Wait For Page To Load/Ready In Selenium WebDriver

+0

Bạn nên sử dụng selenium waits thay vì các vòng – cocorossello

2

Tôi đã có một vấn đề tương tự. Giải pháp này làm việc cho tôi từ 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

2

Nếu tất cả các bạn cần làm là chờ cho html trên trang để trở nên ổn định trước khi cố gắng để tương tác với các yếu tố, bạn có thể thăm dò ý kiến ​​DOM định kỳ và so sánh kết quả, nếu các DOM là giống nhau trong thời gian thăm dò nhất định, bạn là vàng. Một cái gì đó như thế này, nơi bạn vượt qua trong thời gian chờ đợi tối đa và thời gian giữa các cuộc thăm dò trang trước khi so sánh. Đơn giản và hiệu quả.

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

Tôi muốn lưu ý ở đây rằng tôi đã ghi lại thời gian cần thiết để thăm dò ý kiến ​​một trang kích thước trung bình hai lần, sau đó so sánh các chuỗi đó trong java và mất hơn 20ms. – TheFreddyKilo

+1

Lúc đầu, tôi thấy đây là một giải pháp dơ bẩn, nhưng có vẻ như nó hoạt động tốt và nó không liên quan đến việc thiết lập các biến phía máy khách. Một hang động có thể là một trang liên tục bị thay đổi bởi javascript (tức là một đồng hồ javascript) nhưng tôi không có nó để nó hoạt động! – Peter

0

Hai điều kiện có thể được sử dụng để kiểm tra xem trang web được tải trước khi tìm bất kỳ yếu tố trên trang:

WebDriverWait wait = new WebDriverWait(driver, 50); 

Sử dụng dưới đây redayState sẽ đợi cho đến khi tải trang

wait.until((ExpectedCondition<Boolean>) wd -> 
    ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete")); 

Dưới JQuery sẽ đợi cho đến khi dữ liệu chưa được tải

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

Sau khi các mã JavaScript này tìm cáchOut webElement.

WebElement we = wait.until(ExpectedConditions.presenceOfElementLocated(by)); 
Các vấn đề liên quan