Привет, дорогие коллеги! В прошлой статье мы разобрали с вами один из наиболее удобных и гибких, на мой взгляд, способов построить фреймворк для проекта по автоматизации тестирования на Python. В качестве примера я приводил автотесты для API. Сегодня же я хочу поделиться с вами небольшим гайдом (или шпаркалкой, кому как удобнее) по одному из аспектов UI - тестирования. Если быть конкретным, речь идет о грамотных ожиданиях в тестах.
Наверное, каждый, кто уже более или менее погрузился в автоматизацию, уже не раз слышал либо понял сам, что использовать в тестах функцию sleep из стандартного модуля Python - это плохая практика, да и вообще моветон. Так вот. Для того, чтобы избежать косых вглядов коллег и просто не напороться в один момент на вилы в собственных тестах, предлагаю вам разобраться, как и когда использовать специальный модуль Selenium-a - Expected Conditions, который управляет теми самыми ожиданиями. Начнем.
БАЗА. Что, как и зачем?
В Selenium есть два основных типа ожидания: неявное (implicity_wait) и явное (WebDriverWait).
Неявное ожидание. Когда мы говорим Selenium: "Если ты при открытии страницы браузера сразу не можешь найти искомый элемент, не кричи сразу "тревога, мы все уронили", а сначала подожди n секунд, и уже потом, если элемент так и не появился, выдавай ошибку". Конструкция выглядит так: browser. implicity_wait(n). Вместо n, как вы уже догадались, нужное нам количество секунд. Рекомендую это также выносить в фикстуру вместе с инициализацией browser (о фикстурах и файле conftest.py я рассказывал в прошлой статье). Например:

Явное ожидание. Мы говорим Selenium: "Жди n секунд, пока не выполнится конкретное условие/событие (expected_conditions), и потом проверяй это условие". Такое подход наиболее гибкий и является стандартом. Выглядит это примерно следующим образом:
#Сначала импорт нужных модулей
from selenium.webdriver.support.ui import WebDriverWait
# as EC принято называть для сокращения длинного "expected_conditions"
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# Инициализируем ожидатор:) и говорим ему жди 10 секунд
wait = WebDriverWait(browser, 10)
element = wait.until(EC.условие(By.локатор, "значение_локатора"))
Это примерный базовый синтаксис для понимания конструкции в целом. Далее я как раз расскажу о том, какие же условия бывают.
Примечание: приведенная ниже классификация является исключительно авторской и представлена для удобства восприятия.
Группа условий № 1. Проверка присутствия видимости элемента
Как правильно наиболее часто используемая группа условий (на моей практике).
presence_of_element_located((locator))
- Что делает: Ждет, пока элемент появился в DOM-дереве страницы.
- Когда использовать:
-- Когда нужно убедиться, что элемент загрузился в HTML - коде
-- Когда работаем с "невидимыми" элементами
- Доп. заметки:
-- Элемент может быть в DOM - дереве, но невидим на странице
-- Самое "слабое" ожидание, но самое быстрое, если видимость не имеет значенияvisability_of_element_located((locator))
- Что делает: Ждет, пока элемент не только появится в DOM - дереве, но и станет видимым на странице
- Когда использовать:
-- Практически в 90% случаев. Чтобы кликнуть, ввести текст, как-то повзаимодействовать с элементом, нужно, чтобы он появился.element_to_be_clickable((locator))
- Что делает: Ждет, пока элемент станет видимым И активным (enabled).
- Когда использовать:
-- Всегда перед кликом на кнопку, ссылку или чекбокс. Фактически, решает проблему, когда кнопка уже видима, но еще неактивна (задизейблена).
- Доп. заметки:
-- На мой взгляд, самое сильное и надежное ожидание перед кликом.
Группа № 2. Проверка состояния элемента
Используются для проверки атрибутов, текста и т.д.
text_to_be_present_in_element((locator), ''ожидаемый текст")
- Что делает: Ждет, пока внутри указанного элемента появится ожидаемый текст
- Когда использовать:
-- Когда ждем, пока на странице обновится какой-то счетчик, статус или сообщение после выполнения определенного действия. Например: после добавления товара в корзину, мы ждем, пока в элементе с id "cart_counter" появится текст "1".invisibility_of_element_located((locator))
- Что делает: Ждет, пока элемент станет невидимым или полностью исчезнет из DOM.
- Когда использовать:
-- Когда ждем, пока исчезнет какой-нибудь спиннер загрузки (лоадер)
-- Когда проверяем, что после удаления элемент действительно пропал со страницы.
Группа № 3. Работа с alert -ами
Это группа специализированных ожиданий для работы со всплывающими окнами.
alert_is_present()
- Что делает: Ждет, пока на странице не появится стандартное окно alert, confirm или prompt.
- Когда использовать:
-- При тестировании функционала, вызывающего данные окна.
- Доп. заметки:
-- Этот тип ожидания (метод ожидания) не принимает локатор. Он возвращает объект alert, с которым мы будем работать (alert.accept(), alert.dismiss())
С классификацией, кажется, все. Как все мы знаем, прочитать информацию это одно, совсем другое - увидеть на примере. Поэтому набросал для вас небольшой пример использования:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# Представим, что тестируем форму логина
LOGIN_BUTTON = (By.CSS_SELECTOR, '#login-button')
USERNAME_INPUT = (By.ID, 'username')
ERROR_MESSAGE = (By.CLASS_NAME, 'error-message')
LOAD_SPINNER = (By.CSS_SELECTOR, '.loader')
# Инициализируем ждуна, с неявным типом ожиданием
wait = WebDriverWait(browser, 15)
# Вводим имя пользователя (применяем ожидание появления элемента на странице)
username = wait.until(EC.visability_of_element_located(USERNAME_INPUT))
username.send_keys('testuser')
# Кликаем на кнопку (после того, как дождались, пока она стала кликабельной)
login_button = wait.until(EC.element_to_be_clickable(LOGIN_BUTTON))
login_button.click()
# Теперь ждем, пока исчезнет спиннер загрузки, который появился после клика
load_spinner = wait.until(EC.invisibility_of_element_located(LOAD_SPINNER))
# К примеру мы не ввели пароль и ожидаем появления сообщения об ошибке после спиннера
wait.untill(EC.text_to_be_present_in_element(ERROR_MASSAGE, 'Пароль не может быть пустым, дружочек'))
Этот пример максимально простой и показывает, как можно комбинировать различные типы ожидания, чтобы наши автотесты не "моргали". На этом сегодня, пожалуй, у меня все. Конструктивная критика и замечания приветствуются, плюсы в карму ценятся, ибо надеюсь кому-то изложенный мной материал будет полезен. Всем добра!