Как ожидать элементы или определенные состояния на странице? Наверное, один из самых интересных вопросов, которые могут возникать при первом использовании WebDriver. Разработчики инструмента потрудились на славу, чтобы сделать этот процесс максимально комфортным и гибким.
В статье рассмотрены самые важные типы ожиданий, используя WebDriver API.
Ожидание времени загрузки страницы
Как известно WebDriver работает с DOM. Потому ожидание загрузки страницы происходит через ожидание состояния document.readyState == complete. Это происходит автоматически после открытия страницы driver.get(), перезагрузки driver.navigate.refresh(), перехода на другие страницы посредством нажатия на веб элементы и т.д. WebDriver ожидает загрузку DOM-а страницы автоматически, потому уже не нужно вызывать waitForPageToLoad() после каждого действия, как это было в Selenium RC. Если же загрузка страницы длится очень долго и нужно прекратить ожидание, предусмотрена конфигурация pageLoadTimeout. Но стоит принимать во внимание, что если DOM не загрузился к тому моменту, вы получите TimeoutException.
driver.manage().timeouts().pageLoadTimeout(60, TimeUnit.SECONDS);
На момент написания статьи работает только для Firefox и InternetExplorer.
Ожидание выполнения JavaScript
Используя JavascriptExecutor, есть возможность выполнять любой JS на Web странице. Для функции executeAsyncScript мы можем задать граничное значение времени ожидания завершения запроса:
driver.manage().timeouts().setScriptTimeout(30, TimeUnit.SECONDS);
Неявные или скрытые(Implicit) ожидания.
Их особенность в том, что ожидания указаны глобально на уровне объекта driver. И все вызовы элементов driver.findElement() будут продолжаться то тех пор, пока элемент не будет найден или будет достигунта граница времени ожидания.
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
В данном примере, при вызове функции driver.findElement(), если элемент не присутствует в DOM страницы, на протяжении 10 секунд будет происходить опрос браузера на наличие элемента. Это очень помогает при работе с AJAX компонентами.
Явные(Explicit) ожидания
Самая интересная тема – конфигурируемые ожидания.
Ведь очень сложно угодить всем компонентам на странице используя только стандартные средства неявных ожиданий. Особенно в тяжелых AJAX приложениях.
Создание объекта WebDriverWait
private final Wait<WebDriver> wait = new WebDriverWait(driver, 5, 1000);
Второй параметр принимает время timeout в секундах, третий параметр принимает время в milliseconds, которое нужно ожидать перед очередным вызовом проверки наличия элемента (500 milliseconds by default).
Вывод сообщения в случае неуспешного завершения ожидания
private final Wait<WebDriver> wait = new WebDriverWait(driver, 5).withMessage("Element was not found");
Игнорирование определенных событий во время поиска
Во время процесса поиска WebDriver регулярно опрашивает браузер на наличие элемента в DOM модели. При этом существует ряд исключительных ситуаций
- Если элемент доступен в DOM на момент поиска, но спустя время, в момент его вызова, DOM может измениться. Тогда мы получим StaleElementReferenceException.
- Если элемент отсутствует в DOM на момент вызова – получим NoSuchElementException.
- Если элемент был найдем в DOM, но не видим на странице – получим ElementNotVisibleException.
- Если элемент изменил координаты – получим MoveTargetOutOfBoundsException.
Когда пойман один из таких случаев, то цикл остановится и выбросит исключение.
Для того чтобы игнорировать исключения в объекте Wait предусмотрен метод ignoring:
private final Wait<WebDriver> wait = new WebDriverWait(driver, 5).ignoring(StaleElementReferenceException.class, ElementNotVisibleException.class);
Еще один вариант создания объекта Wait
private final Wait<WebDriver> wait = new FluentWait<WebDriver>(driver).withMessage("Element was not found").withTimeout(10, TimeUnit.SECONDS).pollingEvery(1, TimeUnit.SECONDS);
Вызов объекта Wait
Объект содержит в себе всего одну функцию until, которая представляет собой правило, по которому стоит ожидать элемент. Существует набор уже определенных правил для ожиданий. Находятся они в классе ExpectedConditions:
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.id("table"))));
Часто используемые правила:
- titleContains(String title)
- presenceOfElementLocated(By locator)
- presenceOfAllElementsLocatedBy(By locator)
- visibilityOfElementLocated(By locator)
- visibilityOf(WebElement element)
- textToBePresentInElement(By locator, String text)
- invisibilityOfElementLocated(By locator)
- invisibilityOfElementWithText(By locator, String text)
- elementToBeClickable(By locator)
- stalenessOf(WebElement element)
- alertIsPresent()
Есть возможность использовать негативные правила:
wait.until(ExpectedConditions.not(ExpectedConditions.presenceOfElementLocated(By.id("link"))));
Список всех функций доступен по ссылке.
Создание индивидуальных ожиданий
В тех случаях, когда ни одно из готовых правил не подходит, нужно создавать свое:
Function<? super WebDriver, Object> isTextPresent = new ExpectedCondition<Object>() {
@Override
public Boolean apply(WebDriver webDriver) {
return webDriver.findElement(By.tagName("body")).getText().contains("New topic");
}
};
Если использовать объект типа WebDriverWait:
//Link
private final WebDriverWait wait;
//Initialization
wait = new WebDriverWait(driver, 1);
Можно создавать правило используя класс Predicate:
Predicate<WebDriver> isTableLoaded = new Predicate<WebDriver>() {
@Override
public boolean apply(WebDriver webDriver) {
List<WebElement> rows = webDriver.findElement(By.id("table")).findElements(By.tagName("tr"));
return rows.size() > 1;
}
};
WebDriver API очень богат разного рода ожиданиями, для часто-встречающих ситуаций в автоматизации тестирования Web приложений. Чтобы упростить конфигурацию значений времени для ожиданий, их обычно выносятся в параметры запуска или сохраняют в property файл.
Используйте эти примеры для своих функциональных тестов с целью повысить их производительность и стабильность. Если какая-то из возможностей WebDriver API осталась не раскрытой, пожалуйста, напишите пример в комментариях.
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Спасибо! Отличный материал.
Ожидание выполнения Javascript широкий вопрос, ответ на который зависит от объекта ожидания, используемого Javascript framework и используемого браузера.
На эту тему посмотрите
Selenium WebDriver : Wait for complex page with JavaScript(JS) to load
http://stackoverflow.com/questions/10720325/selenium-webdriver-wait-for-complex-page-with-javascriptjs-to-load
Более стабильной альтернативой может быть ожидание появления или исчезновения элементов, управляемых через Javascript логику.
Как дождаться выполнения Javascript на странице
stalenessOf(WebElement) обычно используется для ожидания исчезновения элемента из DOM объекта страницы. Например ajax loader http://preloaders.net/preloaders/75/Flower.gif
Больше информации по ссылке
Selenium API – ExpectedConditions
http://selenium.googlecode.com/git/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html
Stale Element Reference Exception
http://docs.seleniumhq.org/exceptions/stale_element_reference.jsp
Можно по подробнее про:
stalenessOf(WebElement element) ?
Отличная статья. Всё коротко и ясно.
Спасибо.
Мне интересно реализация Wait при работе с Педжобжектом. Вейты принимают By, а поле пейджобжекта туда не запихнуть. Такое впечатление, что Вейты так и шепчут: используй Sleep, используй Sleep.
Вариант с анонимными классами/функциями – это куча громоздкого кода, вот предлагают так писать:
Predicate isTableLoaded = new Predicate() {
@Override
public boolean apply(WebDriver webDriver) {
List rows = webDriver.findElement(By.id(“table”)).findElements(By.tagName(“tr”));
return rows.size() > 1;
}
};
Вместо красивого
WaitUntilVisible(MyPage.loginButton)
Не спорю. Но я решил собрать это все в одном месте и писал из личного опыта
В документации и так все хорошо с примерами описано.