Рассказывает Валерия, QA Automation Engineer в Noveo
Ожидания — не самый приятный процесс, где бы он ни происходил. Ожидания в автотестах — отдельная боль. Избавиться от них невозможно, но есть способы организовать их работу в более удобной форме, нежели Thread.sleep()
.
Чтобы собрать вместе и систематизировать информацию по этой теме, рассмотрю все варианты написания ожиданий для автоматизированного тестирования UI посредством Java и Selenium, с которыми мне приходилось работать, а именно ожидания, предоставляемые Selenium, возможность написания собственных ожиданий и библиотека Awaitility.
Selenium waiting methods
Implicit Wait
Implicit Wait, или неявное ожидание, — пожалуй, самый популярный способ ожидания в Selenium благодаря своей легкости в использовании.
Чтобы использовать Implicit Wait в автотестестах, достаточно:
- установить его всего 1 раз,
- указать вручную лимит ожидания.
После того, как команда исполнится, Implicit Wait будет действовать на протяжении всего пробега автотестов и ожидать указанное время прежде, чем выбросить NoSuchElementException
(или не выбрасывать, если необходимый элемент на странице найден). Не устанавливать Implicit Wait равносильно нулевому лимиту времени, и исключение пробросится сразу.
Чтобы установить Implicit Wait, необходимо написать всего одну строку после установки драйвера, и таким образом мы установим лимит ожидания 10 секунд:
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
Implicit Wait можно использовать для:
- ожидания полной загрузки страницы —
pageLoadTimeout()
; - ожидания появления элемента на странице —
implicitlyWait()
; - ожидания выполнения асинхронного запроса —
setScriptTimeout()
;
Установка использования неявного ожидания будет выглядеть следующим образом:
@BeforeMethod (alwaysRun = true)
public void setUpDriver() {
// Set up driver
ChromeOptions options = new ChromeOptions();
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver(options);
// Set implicit wait:
// wait for WebElement
driver.manage().timeouts().implicitlyWait(5000, TimeUnit.MILLISECONDS);
// wait for loading page
driver.manage().timeouts().pageLoadTimeout(10000, TimeUnit.MILLISECONDS);
// wait for an asynchronous script to finish execution
driver.manage().timeouts().setScriptTimeout(5000, TimeUnit.MILLISECONDS);
}
@Test (description = "Open url")
public void openUrl() {
// Open browser
driver.manage().window().setSize(new Dimension(1280, 970));
// Get url
driver.get("https://some_site.com");
// Search an element
WebElement element = driver.findElement(By.id("some-id"));
Assert.assertTrue(element.isDisplayed());
}
@AfterMethod (alwaysRun = true)
public void closeBrowser() {
// Close browser
driver.quit();
}
Explicit Wait
Explicit wait, или явное ожидание, чаще используется для ожидания определенного условия, которое должно быть выполнено прежде, чем тест пойдет дальше.
О явном ожидании стоит помнить следующие вещи:
- ожидание сработает именно там, где оно указано;
- как и неявному ожиданию, ему необходимо указать лимит времени;
- ожидает выполнения необходимого условия;
- ждет завершения Ajax request.
Использовать явное ожидание можно через WebDriverWait
. Инициализация будет происходить следующим образом:
WebDriverWait wait = new WebDriverWait(driver,10);
где driver
является референсом к нашему используемому WebDriver
, а число 10 — TimeOut
в секундах.
В тесте само ожидание уже будет выглядеть примерно так:
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector("some_element"));
Класс ExpectedConditions
предоставляет нам ряд вещей, которых мы можем дожидаться во время пробега теста. Они все достаточно легко читаемы, что облегчит жизнь как вам, так и людям, которым, возможно, придется поддерживать ваши автотесты.
Вот полный список всего, что вам предлагает подождать ExpectedConditions
:
alertIsPresent()
elementSelectionStateToBe()
elementToBeClickable()
elementToBeSelected()
frameToBeAvaliableAndSwitchToIt()
invisibilityOfTheElementLocated()
invisibilityOfElementWithText()
presenceOfAllElementsLocatedBy()
presenceOfElementLocated()
textToBePresentInElement()
textToBePresentInElementLocated()
textToBePresentInElementValue()
titleIs()
titleContains()
visibilityOf()
visibilityOfAllElements()
visibilityOfAllElementsLocatedBy()
visibilityOfElementLocated()
В тесте создание и использование неявного ожидания будут выглядеть следующим образом:
private WebDriver driver;
private WebDriverWait wait;
private WebElement exampleElement;
@BeforeMethod(alwaysRun = true)
public void setUpDriver() {
// Set up driver
ChromeOptions options = new ChromeOptions();
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver(options);
// Set explicit wait
wait = new WebDriverWait(driver, 10);
}
@Test(description = "Open url")
public void openUrl() {
// Open browser
driver.manage().window().setSize(new Dimension(1280, 970));
// Get url
driver.get("https://some-site.com");
// Wait until blockUI disappear
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector("some_element")));
wait.until(ExpectedConditions.invisibilityOf(exampleElement));
// Search an element
WebElement element = driver.findElement(By.id("some-id"));
Assert.assertTrue(element.isDisplayed());
// Click element
element.click();
// Wait until alert is present
wait.until(ExpectedConditions.alertIsPresent());
driver.switchTo().alert().accept();
}
@AfterMethod(alwaysRun = true)
public void closeBrowser() {
// Close browser
driver.quit();
}
Разница между Implicit и Explicit Wait
Написание собственных ожиданий
Ожидания Selenium не всегда способны удовлетворить потребности тестировщика. В таких случаях мы можем сами написать методы, которые удержат автотесты от падения. Работать кастомные ожидания будут по тому же принципу, что и Explicit Wait, т.е. срабатывать в той части теста, в которой указаны, а условие, которого необходимо дождаться, нужно написать самим как некий аналог класса ExpectedConditions
. Хранить ли свои условия в отдельном классе или в том же, где и написанный способ ожидания, и нужно ли разделять метод ожидания и условие или достаточно будет оставить их прописанными в едином методе, зависит от условий проекта и предпочтений тестировщика.
Чтобы написать ожидание самим, потребуется не так много: старый добрый цикл while и System.currentTimeMillis()
.
Выглядеть это может примерно таким образом:
public static void waitForInvisibilityOfElement(WebElement element) {
float waitingTime = 0;
// Start loading time of waiting for invisibility of element
startLoadingTime = System.currentTimeMillis();
while (element.isDisplayed()){
// If element is still presented, verify if the
// maximum of waiting time is reached
if(waitingTime <= MAX_WAITING_TIME) {
// Update waiting time
waitingTime = System.currentTimeMillis() - startLoadingTime;
}else{
System.out.println("Condition was not executed with time limit");
break;
}
}
// If the element disappeared, log the loading time of the
if(!element.isDisplayed()) {
System.out.println("Condition was executed in " + waitingTime + " seconds");
}
}
Условие, которое мы ждем в этом примере — element.isDisplayed()
, его можно вынести в отдельный метод, возвращающий boolean
, и таким образом сам метод ожидания можно прописать единожды и просто передавать туда разные условия в формате boolean
.
Awaitility library
Пожалуй, лучшее, ну или, по крайней мере, мое любимое, решение проблемы ожиданий. Awaitility применима не только в автоматизации тестирования, однако я ограничусь рассмотрением ее использования на примерах тестов. Эта библиотека упростит вам жизнь при написании собственных ожиданий; все, что сказано про аналог кастомных ожиданий с Explicit Wait и ExpectedConditions
, будет справедливо и здесь. То же касается и того, как вы предпочтете прописать условия, которых хотите дождаться. В рамках этой статьи рассмотрена только часть возможностей данной библиотеки. Если вам интересно почитать обо всех возможностях, можете ознакомиться с официальной документацией. Здесь я хочу обратить внимание на те моменты, которые мне показались максимально полезными, и просто поделиться тем, что есть такая замечательная штука, которая помогает стабилизировать автотесты.
Awaitility library позволит:
- дождаться выполнения асинхронных запросов;
- установить как максимум, так и минимум ожидаемого времени;
- проигнорировать выпадающие исключения (в таком случае по истечению лимита времени будет выброшен
ConditionTimeoutException
); - использовать
Assertion
в качестве ожидаемого условия; - использовать polling.
Синтаксис довольно читабельный; Awaitility позволяет понять, что и куда вызывается для определения лимита времени, игнорирования исключений, где устанавливаем polling и чего ожидаем в финале нашего метода. Внутреннее написание ожидания может выглядеть так — остается только вызвать его в тесте. Хранить подобные методы стоит все же отдельно от тестов.
public static void waitVisibleElement(WebElement we) {
with().pollDelay(100, TimeUnit.MILLISECONDS).await().atMost(10, TimeUnit.SECONDS).until(we::isDisplayed);
}
Отдельным примером хочу показать ожидание Assertion, именно этот метод кажется мне особенно удобным, так как убивает сразу двух зайцев и позволяет не прописывать лишних строк кода:
/**
* Wait until two lists became equal
* @param actualValues - actual values
* @param requiredValues - expected values
*/
public static void waitOfAssertionForLists(List<WebElement> actualValues, List<string> requiredValues) {
given().ignoreExceptions().await().atMost(10, TimeUnit.SECONDS)
.untilAsserted(() -> Assert.assertEquals(getValuesFromDropDown(actualValues), requiredValues));
}
// Get text values from list of WebElements
private static List<string> getValuesFromDropDown(List wes){
List actualValuesInDropDown = new ArrayList<>();
for (WebElement we : wes) {
if(we.isDisplayed() && we.getText().trim().length() > 0) {
actualValuesInDropDown.add(we.getText().trim());
}
}
return actualValuesInDropDown;
}
Это та информация, которой я хотела поделиться об ожиданиях.
Более подробно вы можете ознакомиться с Awaitility по ссылке.