Разделы портала

Онлайн-тренинги

.
Использование ожиданий Selenium WebDriver для повторных попыток
08.10.2021 00:00

Автор: Корина Пип (Corina Pip)
Оригинал статьи
Перевод: Ольга Алифанова

Вопрос падения тестов Selenium из-за неразберихи со временем поднимается достаточно часто. Тесты случайным образом падают, если взаимодействие произошло слишком рано, страница неправильно загрузилась, или веб-элементы не полностью инициализированы. Наибольшие проблемы возникают, если в отрисовке элементов страницы задействован JavaScript.

Хотя в ряде случаев тестировщики пользуются методами ожидания Selenium WebDriver, чтобы условия выполнились до начала взаимодействия, тесты все равно могут продолжать падать. Как же добиться надежных тестов? Читайте дальше, чтобы узнать, как создавать агрегированные методы ожидания, позволяющие заново создавать сложные условия.

Класс и методы WebDriverWait

Прежде, чем начать писать агрегированные методы ожидания, нужно разобраться с классом WebDriverWait. Он позволяет создавать кастомные методы ожидания в тестах, позволяющие ожидать выполнения любых условий – отображения веб-элемента, соответствия текста на веб-элементе определенной строке, и т. д. Создавая методы ожидания, можно использовать существующие в библиотеке Selenium методы ExpectedConditions, или писать кастомный код самостоятельно.

Предположим, что все методы ожидания в вашем проекте можно найти в одном классе. На уровне класса вы задаете константу TIMEOUT – максимальное время в секундах, в течение которого метод ожидания будет ждать выполнения условия.

Теперь допустим, что у вас есть метод ожидания в классе ожидания, ждущий выполнения условия в течение периода TIMEOUT. Веб-элемент, условие для которого проверяется, передается в метод как параметр. При необходимости могут передаваться и другие параметры. К примеру, ожидая текста элемента, вы передаете текст как параметр метода. В этом методе условие оценивается каждые полсекунды. Если оно истинно, метод успешно завершится. Если оно ложно, повторная попытка будет через полсекунды, пока не истечет время TIMEOUT.

Отлично, но где и как применять эти концепции?

Пример – кликнуть по кнопке и ждать отображения другого элемента

public void waitForElementToBeDisplayed(WebElement element) {
WebDriverWait wait = new WebDriverWait(driver, TIMEOUT);
ExpectedCondition<Boolean> elementDisplayed = arg0 -> {
try {
element.isDisplayed();
return true;
} catch (Exception e) {
return false;
}
};
wait.until(elementDisplayed);
}

Теперь посмотрим на метод, в котором мы кликаем по веб-элементу:

public void click(WebElement element) {
WebDriverWait wait = new WebDriverWait(driver, TIMEOUT);
ExpectedCondition<Boolean> elementIsClickable = arg0 -> {
try {
element.click();
return true;
} catch (Exception e) {
return false;
}
};
wait.until(elementIsClickable);
}

В обоих случаях в ходе попытки мы будем проверять, выполнено ли интересующее нас условие.

  • В первом методе мы проверяем, отобразился ли элемент.
  • Во втором методе мы проверяем, работает ли клик по элементу без ошибок. Если система не выдала исключений при попытке клика, метод успешно завершается. Однако при наличии исключений клик будет повторно повторен через полсекунды, если период TIMEOUT еще не истек.

Заметьте, что исключения, возможные в обоих этих методах ожидания, определяются в Selenium и включают, помимо прочего, NoSuchElementException (когда веб-элемент не существует) и StaleElementReferenceException (веб-элемент присутствовал в DOM, но из-за обновления больше не существует).

Простой вариант: нет JavaScript, повторной попытки не требуется

Имея эти методы, можно легко выполнить задачу клика по кнопке и ожидания отображения элемента, вызывая методы один за другим:

waiter.click(theButton);
waiter.waitForElementToBeDisplayed(theElementToBeDisplayed);

в этом случае мы будем ожидать успешного клика, пока не истечет TIMEOUT. Это значит, что клик будет повторяться снова и снова, до момента отсутствия исключений. Это хорошо работает в случаях, когда поведение нужной кнопки не задается через JavaScript. Чистая HTML-кнопка отлично работает с этим методом. Если клик успешен, вызывается метод присутствия нового элемента, и, конечно, он успешно выполнится.

Другой вариант – JavaScript, требуется повторная попытка

В некоторых случаях поведение кнопки определяется средствами JavaScript. При клике кнопка выполнит определенный JavaScript-код, который спровоцирует появление новых элементов. Вышеописанные методы не всегда будут верно работать для такой кнопки: при первой попытке клика может случиться, что исключений не возникнет, и метод ожидания успешно завершится. Однако при взгляде на кнопку вы можете заметить, что она выглядит так, как будто клик произошел, но смотрится замороженной. Она нажата, но не отпущена. Конечно, к этому времени запустится метод waitForElementToBeDisplayed – и упадет, потому что "зависшая" кнопка не повлияла на появление нового элемента.

В этом случае требуется другой подход – мы создадим агрегированный метод ожидания, внутри которого будем снова и снова пробовать кликать по кнопке, а затем ожидать появления нового элемента.

Чего мы хотим от этого метода: до завершения TIMEOUT-периода попробовать кликнуть, затем ждать элемента. Если любое из этих действий падает, пробуем снова, начиная с клика вне зависимости от того, какое из действий было неудачным. Для обоих действий используются методы ожидания.

Посмотрим на два имеющихся метода ожидания – оба они ждут в течение периода TIMEOUT. Допустим, что по какой-то причине клик не сработает до момента TIMEOUT-1, в секундах. Это означает, что у метода "ожидание отображение элемента" будет всего одна секунда для запуска до того, как истечет TIMEOUT (так как наш агрегированный метод будет ожидать в течение TIMEOUT-периода и не более). Это неэффективно.

Следовательно, вот что нам нужно: каждый из внутренних методов ожидания в агрегированном методе должен ждать, допустим, 5 секунд. Конечно, это время можно менять в зависимости от ваших тест-окружений – мы создадим новые методы ожидания для клика и для ожидания отображения элемента, принимающие параметр таймаут-периода, который нас интересует. В этом случае мы можем вызывать эти методы с любым количеством необходимых нам секунд.

Посмотрим, как будут выглядеть эти методы. Первый метод, клик:

public void waitForElementToBeDisplayed(WebElement element, int timeoutPeriod) {
WebDriverWait wait = new WebDriverWait(driver, timeoutPeriod);
ExpectedCondition<Boolean> elementDisplayed = arg0 -> {
try {
element.isDisplayed();
return true;
} catch (Exception e) {
return false;
}
};
wait.until(elementDisplayed);
}

Агрегированный метод будет выглядеть так:

public void clickAndWaitForElementDisplayed(WebElement elementToClick, WebElement elementToWaitFor) {
WebDriverWait wait = new WebDriverWait(driver, TIMEOUT);
ExpectedCondition<Boolean> condition = arg0 -> {
try {
click(elementToClick, 5);
waitForElementToBeDisplayed(elementToWaitFor, 5);
return true;
} catch (Exception e) {
return false;
}
};
wait.until(condition);
}

Посмотрим, как это работает для обычной HTML-кнопки. Допустим, метод клика не удался (период таймаута, 5 секунд, истек, но метод каждый раз выдавал исключение). В этом случае метод запустится снова через пять секунд. Это произойдет из-за ветки catch, где мы ловим исключения. Если метод клика падает, он выдает TimeoutException – специализированный тип исключения. Допустим, что спустя еще 3 секунды методу удается кликнуть по кнопке успешно. Затем выполняется метод ожидания элемента, в течение еще пяти секунд. Если он успешен, то агрегированный метод ожидания успешно завершается.

Как это работает с JavaScript-кнопкой: вначале идет попытка клика. Допустим, что исключений не вылетело, но кнопка после клика "заморозилась" – не сработал показ нового элемента. В этом случае после первых пяти секунд клик будет повторен, и в случае успеха выполнится метод ожидания нового элемента.

Заключение

Сравнивая два подхода (агрегированный метод и простой вызов методов ожидания одного за другим) для кнопки JavaScript, легко увидеть, что смысл имеет агрегированный подход. Учитывая, что при залипании кнопки не появляется никаких распознаваемых Selenium исключений, метод клика считает, что клик состоялся. Однако во многих случаях это не так.

Этот подход к повторным попыткам можно использовать во множестве других тест-сценариев. Например, это клик по выпадающему меню и выбор значения из него. Это выпадающее меню отличается от обычного HTML-меню: оно требует клика для показа опций. Другой пример – очистка текстового поля и последующий ввод. В этом случае очистка часто неверно срабатывает с первого раза, или не успевает очистить все содержимое поля до того, как вводится новый текст.

Еще один симпатичный подход – это подход TestProject с механизмом адаптивного ожидания, который убеждается, что все условия окружения достаточны для того, чтобы автотест выполнился. TestProject адаптируется к реальной скорости загрузки приложения и выполнит следующее действие, только если выполнились нужные условия. В качестве дополнительной предосторожности вы можете установить максимальное время ожидания до падения шага (по умолчанию это 15 секунд) в секции шагов – ваш тест перейдет к следующему шагу, если условия так и не выполнятся.

Обсудить в форуме