Selenium WebDriver: дождитесь загрузки сложной страницы с JavaScript
У меня есть веб-приложение для тестирования с Selenium. При загрузке страницы выполняется много JavaScript.
Этот код JavaScript не так хорошо написан, но я ничего не могу изменить. Поэтому ждать появления элемента в DOM с findElement()
метода findElement()
нельзя.
Я хочу создать универсальную функцию в Java, чтобы ждать загрузки страницы, возможное решение будет:
- запустите скрипт JavaScript из WebDriver и сохраните результат
document.body.innerHTML
в body
строковой переменной. - сравните переменную
body
с предыдущей версией body
. если они одинаковы, установите инкремент счетчика notChangedCount
противном случае установите notChangedCount
в ноль. - подождите немного (например, 50 мс).
- если страница не изменялась в течение некоторого времени (например, 500 мс), поэтому
notChangedCount >= 10
тогда выйдите из цикла, в противном случае notChangedCount >= 10
к первому шагу.
Как вы думаете, это правильное решение?
Ответы
Ответ 1
Если бы кто-нибудь действительно знал общий и всегда применимый ответ, это было бы реализовано повсюду давным-давно и сделало бы нашу жизнь намного проще.
Есть много вещей, которые вы можете сделать, но каждый из них имеет проблему:
-
Как сказал Ашвин Прабху, если вы знаете колодец script, вы можете наблюдать его поведение и отслеживать некоторые его переменные на window
или document
и т.д. Это решение, однако, не для всех и может использоваться только вами и только на ограниченном наборе страниц.
-
Ваше решение, наблюдая за HTML-кодом и независимо от того, было или не изменено какое-то время, неплохо (также есть a метод, чтобы получить исходный и не отредактированный HTML напрямую WebDriver
), но:
- Требуется много времени для фактического утверждения страницы и может значительно продлить тест.
- Вы никогда не знаете, что такое правильный интервал. script может загружать что-то большое, что занимает более 500 мс. На внутренней странице нашей компании есть несколько сценариев, которые занимают несколько секунд в IE. Возможно, ваш компьютер временно недоступен ресурсам - скажем, что антивирус сделает ваш процессор полностью, тогда 500 мс могут быть слишком короткими даже для некомплексных скриптов.
- Некоторые скрипты никогда не выполняются. Они называют себя с некоторой задержкой (
setTimeout()
) и работают снова и снова и могут менять HTML каждый раз, когда они запускаются. Серьезно, каждая страница "Веб 2.0" делает это. Даже переполнение стека. Вы можете перезаписать наиболее распространенные используемые методы и рассмотреть сценарии, которые используют их как завершенные, но... вы не можете быть уверены.
- Что делать, если script делает что-то другое, кроме изменения HTML? Это могло бы сделать тысячи вещей, а не только удовольствие от
innerHTML
.
-
Есть инструменты, которые помогут вам в этом. А именно Прослушиватели Прогресса вместе с nsIWebProgressListener и некоторые другие. Однако поддержка браузера для этого ужасна. Firefox начал пытаться поддерживать его с FF4 вперед (все еще развивается), IE имеет базовую поддержку в IE9.
И я предполагаю, что скоро придумаю еще одно ошибочное решение. Дело в том, что нет однозначного ответа на то, когда говорить "теперь страница завершена" из-за вечных сценариев, выполняющих свою работу. Выберите тот, который вам больше всего подходит, но остерегайтесь его недостатков.
Ответ 2
Спасибо Эшвин!
В моем случае мне нужно дождаться выполнения плагина jquery в каком-то элементе. В частности, "qtip"
в вашем подсказке, он отлично работал у меня:
wait.until( new Predicate<WebDriver>() {
public boolean apply(WebDriver driver) {
return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
}
}
);
Примечание. Я использую Webdriver 2
Ответ 3
Вам нужно дождаться завершения Javascript и jQuery.
Выполните Javascript, чтобы проверить, есть ли jQuery.active
0
и document.readyState
is 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);
}
Ответ 4
Описывает ли библиотека JS/инициализирует какую-либо известную переменную в окне?
Если это так, вы можете дождаться появления переменной. Вы можете использовать
((JavascriptExecutor)driver).executeScript(String script, Object... args)
чтобы проверить это условие (что-то вроде: window.SomeClass && window.SomeClass.variable != null
) и вернуть boolean true
/false
.
Оберните это в WebDriverWait
и дождитесь, пока script вернет true
.
Ответ 5
Если все, что вам нужно сделать, это дождаться, пока 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;
}
}
}
Ответ 6
Нижеприведенный код отлично работает в моем случае - моя страница содержит сложные 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
Ответ 7
У меня была такая же проблема.
Это решение работает для меня из 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
Ответ 8
Два условия могут быть использованы для проверки загрузки страницы перед тем, как найти какой-либо элемент на странице:
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));
Ответ 9
Я попросил моих разработчиков создать переменную JavaScript "isProcessing", к которой я могу получить доступ (в объекте "ae"), которую они устанавливают, когда что-то запускается, и очищают, когда что-то делается. Затем я запускаю его в аккумуляторе, который проверяет его каждые 100 мс, пока он не получит пять подряд, то есть всего 500 мс без каких-либо изменений. Если пройдет 30 секунд, я выдаю исключение, потому что к тому времени что-то должно было случиться. Это в С#.
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.");
}
Ответ 10
Чтобы сделать это правильно, вам нужно обработать исключения.
Вот как я жду 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") ) );
}
....
ПРИМЕЧАНИЕ. Вы можете увидеть мой весь рабочий пример здесь.
Ответ 11
Для библиотеки Selenium nodejs
я использовал следующий фрагмент. В моем случае я искал два объекта, которые добавляются в окно. В этом примере это <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);
});
Ответ 12
Вы можете написать некоторую логику, чтобы справиться с этим. Я пишу метод, который вернет 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;
}
}
Ответ 13
Здесь из моего собственного кода:
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;
В качестве бонуса счетчик может быть reset на 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); " +
" } "+
...
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");
}
};
}
Ответ 14
Мне нравится ваша идея опроса HTML, пока он не станет стабильным. Я могу добавить это к моему собственному решению. Следующий подход в С# и требует jQuery.
Я разработчик тестового проекта SuccessFactors (SaaS), в котором мы не имеем никакого влияния на разработчиков или характеристики DOM, стоящие за веб-страницей. Продукт SaaS может потенциально менять свой базовый дизайн DOM 4 раза в год, так что охота постоянно идет за надежными и эффективными способами тестирования с Selenium (включая НЕ тестирование с Selenium, где это возможно!)
Вот что я использую для "готовой страницы". Это работает во всех моих собственных тестах в настоящее время. Такой же подход работал и для большого собственного веб-приложения Java пару лет назад, и к тому времени, когда я покинул проект, он работал более года.
-
Driver
- это экземпляр WebDriver, который связывается с браузером. -
DefaultPageLoadTimeout
- значение времени ожидания в тиках (100 нс на тик)
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, анимации), который имеет смысл, если подумать:
- загрузить страницу, содержащую код
- используйте код для загрузки данных откуда-то через Ajax
- представить данные, возможно, с анимацией
Нечто подобное вашему подходу сравнения DOM может использоваться между 1 и 2, чтобы добавить еще один уровень надежности.
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");
}
Ответ 15
Кто-нибудь нашел, как получить активный счетчик вызовов AJAX в приложении React
Ответ 16
Вот как я это делаю:
new WebDriverWait(driver, 20).until(
ExpectedConditions.jsReturnsValue(
"return document.readyState === 'complete' ? true : false"));