Обложка статьи «Способы ожидания в Java и Selenium»

Способы ожидания в Java и Selenium

Рассказывает Валерия, 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 по ссылке.