Как FluentWait работает с ExpectedConditions в Selenium WebDriver на языке Java

Существует несколько способов, которыми можно реализовать явное ожидание при разработке автоматизированных тестов на Selenium в языке Java. Способ, который предлагается нам разработчиками самого Selenium, заключается в имплементации специального интерфейса Wait, который определяет только один метод until, принимающий в качестве параметра объект, содержащий логику проверки условия ожидания. Такое условие должно быть описано в форме функции (Function). К счастью, Selenium предлагает нам ряд реализаций упомянутого интерфейса. Одной из таких реализация является класс FluentWait, о котором мы планируем поговорить в данной статье.

Концепция FluentWait

FluentWait реализует интерфейс Wait<F>, показанный ниже с моими комментариями.

wait interface ru

Класс, который имплементирует указанный интерфейс, должен реализовать логику метода until, принимающего объект типа Function<? super F, T> в качестве параметра. В соответствии с дизайном, метод until возвращает объект того же типа, что и условная функция.

Одной из реализаций интерфейса Wait является класс FluentWait, чья логика разработана таким образом, что событие, которое мы ожидаем, проверяется в течение некоторого времени с заданными интервалами. Ожидание считается успешным если до истечения таймаута метод apply в нашей условной функции возвращает либо Boolean.TRUE либо непустой объект типа T. Логика этого метода применяется к ? super F, что означает, что параметр условной функции может быть либо типа F либо его типа-родителя.

Проще говоря, когда мы хотим использовать метод until, в качестве параметра для этого метода мы должны передать некоторый объект класса, имплементирующего интерфейс Function, реализация метода apply в котором должна взаимодействовать с параметром типа F и возвращать объект типа T.

Такой дизайн позволяет использовать описанный механизм ожидания для любого вида события, которое вы можете определить, а не только к событиям, возникающим внутри фреймворка Selenium. Однако, здесь мы планируем говорить в том числе о классе ExpectedConditions, который разработан как раз для ожиданий событий, связанных с пользовательским интерфейсом тестируемой страницы.

Как FluentWait работает с ExpectedConditions

В версии библиотеки Selenium для языка Java существует заранее подготовленный набор условий для работы с вейтерами, в том числе и для FluentWait. Каждое такое условие - это объект класса, реализующего интерфейс

public interface ExpectedCondition<T> extends Function<WebDriver, T> {}

В принципе, это та же самая функция (Function), только с зафиксированным типом того, что мы передаём в эту функцию в качестве параметра. А именно - WebDriver.

Любая из этих заранее подготовленных функция доступна через вызов соответствующего статического метода в классе ExpectedConditions. Для примера давайте рассмотрим что происходит вот здесь:

public void testExpectedConditions(){
    Wait<WebDriver> waiter = new FluentWait<>(driver)
            .withTimeout(Duration.ofSeconds(10))
            .pollingEvery(Duration.ofSeconds(1));
    waiter.until(ExpectedConditions.titleIs("Expected Title"));
}

Сперва в строке Wait<WebDriver> waiter = new FluentWait<>(…​); мы говорим, что наш FluentWait будет работать с условной функцией, принимающей WebDriver в качестве параметра. Затем мы передаем наш драйвер в конструктор FluentWait чтобы он сохранил ссылку на этот объект внутри себя. В дальнейшем к этому объекту будет применена выбранная нами условная функция.

Мы, также, настраиваем таймаут и интервал проверки условия. Заметьте, что мы могли бы написать всё это в одну линию (здесь я разделяю код на несколько строк намеренно с целью улучшения читаемости) потому что каждый метод тут делает две вещи: изменяет состояние объекта и возвращает тот же объект (самого себя). Такой дизайн имеет своё название. Он называется "билдер".

Затем мы вызываем метод until и передаём в него объект, возвращаемый статическим методом titleIs("Expected Title") класса ExpectedConditions.

Рассматриваем одно из готовых условий класса ExpectedConditions

Давайте рассмотрим содержимое этого метода:

public static ExpectedCondition<Boolean> titleIs(final String title) {
  return new ExpectedCondition<Boolean>() {
    private String currentTitle = "";

    @Override
    public Boolean apply(WebDriver driver) {
      currentTitle = driver.getTitle();
      return title.equals(currentTitle);
    }

    @Override
    public String toString() {
      return String.format("title to be \"%s\". Current title: \"%s\"", title, currentTitle);
    }
  };
}

Мы видим, что он возвращает объект типа ExpectedCondition<Boolean>, который является ничем иным как Function<WebDriver, Boolean>.

FluentWait берет этот объект и вызывает у него метод public Boolean apply(WebDriver driver), передавая в этот метод WebDriver, сохраненный в FluentWait через конструктор.

expectedconditions titleis ru

Если этот метод возвращает Boolean.TRUE либо пустой объект, FluentWait предпринимает еще одну попытку вызова метода apply до тех пор пока не истечет таймаут либо до тех пор пока метод не вернет либо Boolean.TRUE либо непустой объект.

В соответствие с этой логикой и работает FluentWait. Надеюсь моё объяснение поможет вам использовать класс ExpectedConditions более эффективно либо даже реализовать свои собственный условия ожидания. Вы также можете посетить следующий мой пост где я рассказываю о том как реализовать кастомные условия при работе с FluentWait в Selenium на языке Java.

Если у вас остались вопросы, задавайте их тут. Я постараюсь дополнить статью опираясь на ваши замечания.