it-swarm.com.ru

Selenium WebDriver: дождитесь загрузки сложной страницы с JavaScript

У меня есть веб-приложение для тестирования с Selenium. При загрузке страницы выполняется много JavaScript.
Этот код JavaScript не так хорошо написан, но я ничего не могу изменить . Поэтому ждать появления элемента в DOM с помощью метода findElement() не вариант.
Я хочу создать универсальную функцию в Java для ожидания загрузки страницы. Возможное решение: 

  • запустите сценарий JavaScript из WebDriver и сохраните результат document.body.innerHTML в строковой переменной body.
  • сравните переменную body с предыдущей версией body. если они одинаковы, тогда установите приращение счетчика notChangedCount, иначе установите notChangedCount в ноль.
  • подождите немного (например, 50 мс).
  • если страница не изменилась в течение некоторого времени (например, 500 мс), тогда notChangedCount >= 10 выйдите из цикла, в противном случае переходите к первому шагу.

Как вы думаете, это правильное решение?

84
psadac

Если бы кто-то знал общий и всегда применимый ответ, он был бы реализован везде много лет назад и сделал бы нашу жизнь SO намного проще.

Есть много вещей, которые вы можете сделать, но у каждого из них есть проблема:

  1. Как сказал Эшвин Прабху, если вы хорошо знаете сценарий, вы можете наблюдать за его поведением и отслеживать некоторые его переменные в window или document и т.д. Это решение, однако, не для всех и может использоваться только вами и только на ограниченном уровне. набор страниц.

  2. Ваше решение, соблюдая HTML-код и узнав, был ли он изменен или нет в течение некоторого времени, неплохо (также существует метод для получения исходного и неотредактированного HTML напрямую с помощью WebDriver), но:

    • На самом деле утверждение страницы занимает много времени и может значительно продлить тест.
    • Вы никогда не знаете, что такое правильный интервал. Сценарий может загружает что-то большое, что занимает более 500 мс. На внутренней странице нашей компании есть несколько сценариев, которые в IE занимают несколько секунд. Возможно, на вашем компьютере недостаточно ресурсов - допустим, антивирус полностью загрузит ваш процессор, тогда 500 мс может быть слишком коротким даже для некомплексных сценариев.
    • Некоторые сценарии никогда не выполняются. Они вызывают себя с некоторой задержкой (setTimeout()) и работают снова и снова и могут при каждом запуске изменять HTML-код. Серьезно, каждая страница "Web 2.0" делает это. Даже переполнение стека. Вы можете переписать наиболее распространенные методы и считать сценарии, которые их используют, завершенными, но ... вы не можете быть уверены.
    • Что, если скрипт делает что-то кроме изменения HTML? Он может делать тысячи вещей, а не просто забавляться innerHTML.
  3. Есть инструменты, которые помогут вам в этом. А именно Progress Listeners вместе с nsIWebProgressListener и некоторыми другими. Поддержка браузера для этого, однако, ужасна. Firefox начал поддерживать его начиная с FF4 (все еще развивается), IE имеет базовую поддержку в IE9.

И я думаю, что скоро смогу найти другое ошибочное решение. Дело в том, что нет точного ответа, когда сказать «теперь страница готова», потому что вечные сценарии выполняют свою работу. Выберите тот, который служит вам лучше всего, но остерегайтесь его недостатков.

61
Petr Janeček

Спасибо Эшвин!

В моем случае мне нужно подождать выполнения плагина jquery в каком-то элементе .. в частности "qtip" 

исходя из твоего намека, он отлично сработал для меня:

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

Примечание: я использую Webdriver 2

27
Eduardo Fabricio

Вам нужно дождаться завершения загрузки Javascript и jQuery . Выполните Javascript, чтобы проверить, является ли jQuery.active0 и document.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);
}
22
LINGS

Библиотека JS определяет/инициализирует любую хорошо известную переменную в окне?

Если это так, вы можете ждать появления переменной. Ты можешь использовать

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

проверить это условие (что-то вроде: window.SomeClass && window.SomeClass.variable != null) и вернуть логическое значение true/false.

Оберните это в WebDriverWait и дождитесь, пока скрипт вернет true.

8
Ashwin Prabhu

Если все, что вам нужно сделать, это дождаться, пока 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;
        }
    }
}
4
TheFreddyKilo

Приведенный ниже код отлично работает в моем случае - моя страница содержит сложные сценарии 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

3
user790049

У меня была такая же проблема. Это решение работает для меня из 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
Roma Kap

Для библиотеки 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
Jason Lydon

Я попросил моих разработчиков создать переменную JavaScript «isProcessing», к которой я могу получить доступ (в объекте «ae»), которую они устанавливают, когда все запускается, и очищают, когда что-то делается. Затем я запускаю его в аккумуляторе, который проверяет его каждые 100 мс, пока он не получит пять подряд, то есть всего 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.");
}
1
Roger Cook

Вот из моего собственного кода:
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");
        }
    };
}
0
Mariusz

Два условия могут использоваться, чтобы проверить, загружена ли страница, прежде чем найти какой-либо элемент на странице:

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

После этих JavaScriptCode попробуйте найти наш webElement.

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

Вы можете написать некоторую логику, чтобы справиться с этим. Я написал метод, который будет возвращать 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;
        }
    }
0
Vaibhav Pandey

Чтобы сделать это правильно, вам нужно обработать исключения.

Вот как я делаю ожидание 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") ) );
    }
....

ПРИМЕЧАНИЕ: Вы можете увидеть весь мой рабочий пример здесь .

0
djangofan