Selenium WebDriver: дождитесь загрузки сложной страницы с JavaScript
у меня есть веб-приложение для тестирования с Selenium. Существует много JavaScript работает на странице загрузки.
Этот код JavaScript не так хорошо написан, но я ничего не могу изменить.
Поэтому ждем, когда элемент появится в DOM с findElement()
метод не является опцией.
Я хочу создать универсальную функцию в Java, чтобы дождаться загрузки страницы, возможным решением будет:
- запустите скрипт JavaScript form WebDriver и сохраните результат
document.body.innerHTML
в a строковая переменнаяbody
. - сравнить
body
переменная к предыдущей версииbody
. если они одинаковы, то установите инкремент счетчикаnotChangedCount
в противном случае установите для параметраnotChangedCount
к нулю. - подождите немного времени (например, 50 мс).
- если страница не изменилась в течение некоторого времени (500 мс например) так
notChangedCount >= 10
затем выйдите из цикла в противном случае цикл к первому шагу.
как вы думаете, это правильное решение?
14 ответов:
если бы кто-нибудь действительно знал общий и всегда применимый ответ, он был бы реализован везде давным-давно и сделал бы нашу жизнь намного проще.
есть много вещей вы можете сделать, но каждый из них имеет проблему:
как сказал Ашвин Прабху, если вы хорошо знаете сценарий, вы можете наблюдать его поведение и отслеживать некоторые из его переменных на
window
илиdocument
etc. Это решение, однако, не для все и может быть использовано только вами и только на ограниченном наборе страниц.ваше решение, наблюдая за HTML-кодом и независимо от того, было ли оно изменено или не было изменено в течение некоторого времени, не плохо (кроме того, есть метод чтобы получить исходный и не отредактированный HTML напрямую
WebDriver
), но:
- требуется много времени, чтобы фактически утверждать страницу и может значительно продлить тест.
- вы никогда не знаете, что право интервал. Сценарий может загружайте что-то большое, что занимает более 500 мс. на внутренней странице нашей компании есть несколько сценариев, которые занимают несколько секунд в IE. Ваш компьютер может быть временно не хватает ресурсов-скажем, что антивирус сделает ваш процессор работать полностью, то 500 мс может быть слишком коротким даже для несложных сценариев.
- некоторые скрипты не делал. Они называют себя с некоторой задержкой (
setTimeout()
) и работать снова и снова и может возможно, изменить HTML каждый раз, когда они работают. Серьезно, каждая страница "Web 2.0" делает это. Даже Переполнение Стека. Вы можете перезаписать наиболее распространенные используемые методы и считать сценарии, которые их используют, завершенными, но ... вы не можете быть уверены.- что делать, если скрипт делает что-то другое, чем изменение HTML? Он может делать тысячи вещей, а не только некоторые
innerHTML
удовольствие.есть инструменты, которые помогут вам в этом. А именно прогресс Слушатели вместе с 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
.
ниже код прекрасно работает в моем случае - моя страница содержит сложные 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
У меня была такая же проблема. Это решение работает для меня от WebDriverDoku:
WebDriverWait wait = new WebDriverWait(driver, 10); WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));
Если все, что вам нужно сделать, это подождать, пока html на странице станет стабильным, прежде чем пытаться взаимодействовать с элементами, вы можете периодически опрашивать DOM и сравнивать результаты, если DOMs одинаковы в течение данного времени опроса, вы Золотой. Что-то вроде этого, где вы проходите в максимальное время ожидания и время между опросами страниц перед сравнением. Просто и эффективно.
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; } } }
на
nodejs
библиотека Селена, я использовал следующий фрагмент. В моем случае я искал два объекта, которые добавляются в окно, которые в этом примере являются<SOME PROPERTY>
,10000
- это timeout миллисекунд,<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); });
Я попросил своих разработчиков создать переменную 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."); }
чтобы сделать это правильно, вам нужно обрабатывать исключения.
вот как я делаю ожидание iFrame. Для этого требуется, чтобы ваш тестовый класс JUnit передал экземпляр RemoteWebDriver в объект page:
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
и этот метод будет вызван три раза или можно увеличить время и добавить проверку на null для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; } }
вот из моего собственного кода:
Окно.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;
в качестве бонуса счетчик может быть сброшен на документ.в зависимости или на 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); " + " } "+
...
EDIT: я замечаю, что 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"); } }; }
два условия могут быть использованы, чтобы проверить, если страница загружается, прежде чем найти какой-либо элемент на странице:
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));