Флаки (flaky test) это тест, который на одном и том же коде то проходит, то падает. Один флаки в наборе из 500 тестов, и через месяц команда перестаёт смотреть на красный билд. Поэтому вопросы про ожидания и нестабильность есть почти на каждом собесе автоматизатора.
Почему тест не может просто кликнуть
Браузер асинхронный: страница догружает данные, фреймворк перерисовывает список, анимация двигает кнопку. Тест работает быстрее человека и приходит к элементу раньше, чем тот готов. Значит, тест должен ждать состояния, а не времени.
sleep: почему нельзя
time.sleep(5)
driver.find_element(By.ID, "pay").click()
Если страница грузится 2 секунды, тест 3 секунды простаивает. Если 6 (нагруженный CI), тест падает. Сотня таких пауз, и регресс идёт лишние полчаса, а флаки всё равно остаются.
Ожидания в Selenium
- Неявное (
driver.implicitly_wait(10)): глобальная настройка. Каждыйfind_elementдо 10 секунд опрашивает DOM, пока элемент не появится. Ждёт только наличия, а не видимости или кликабельности. - Явное (
WebDriverWait(driver, 10).until(EC.element_to_be_clickable(...))): ждёт конкретного условия, опрашивая его. В Python интервал опроса по умолчанию 0.5 секунды. - Fluent wait (в Java
FluentWait): явное ожидание с настройкой интервала и списка игнорируемых исключений.
Документация Selenium прямо предупреждает: не смешивайте неявные и явные ожидания, время ожидания становится непредсказуемым.
Ожидания в Playwright
Playwright ждёт сам. Перед кликом он проверяет, что элемент прикреплён к DOM, видим, стабилен (не анимируется), получает события и включён. Проверки await expect(locator).toHaveText(...) повторяются, пока не станут истинными или не выйдет таймаут (по умолчанию 5 секунд). Поэтому в Playwright ручные ожидания нужны редко, а page.waitForTimeout() в тестах считается запахом.
Поллинг своими руками
Иногда ждать надо не элемент, а внешний мир: письмо в почтовом ящике, статус заказа в API, запись в базе.
def wait_until(check, timeout=10, interval=0.5):
deadline = time.monotonic() + timeout
while True:
if check():
return
if time.monotonic() >= deadline:
raise TimeoutError("условие не выполнилось")
time.sleep(interval)
Здесь sleep уместен: это интервал опроса, а не надежда «вдруг успеет». time.monotonic() не прыгает при переводе системных часов.
Ретраи и экспоненциальная задержка
Для сетевых вызовов в тестах (подготовка данных через API) ретрай оправдан на временные ошибки: 429, 502, 503, 504, обрыв соединения. На 400 или 404 повтор ничего не даст. Задержка растёт: 0.5, 1, 2, 4 секунды, с потолком.
Откуда берутся флаки
- Гонки: тест не дождался нужного состояния.
- Зависимость от порядка и общих данных: тесты делят пользователя или корзину.
- Время: часовые пояса, «сегодня» около полуночи,
sleep. - Внешние сервисы и нестабильный стенд.
- Случайные данные без фиксированного seed.
- Утечки состояния: кука, localStorage, незакрытая транзакция.
Как с ними жить
- Ретраи теста (
retriesв Playwright,pytest-rerunfailures) снимают симптом. Пусть флаки помечаются (в Playwright такой тест получает статус flaky), но обязательно разбираются. - Карантин: известные флаки уходят в отдельный набор, не блокируют мерж, но имеют владельца и срок.
- Метрика: доля флаки по истории прогонов. Тест, который на одном коммите и падал, и проходил, флаки по определению.
- Воспроизведение: прогнать один тест 50 раз подряд (
--repeat-eachв Playwright,pytest-repeat), посмотреть трейс и видео.
Что спросят на собесе
- Чем неявное ожидание отличается от явного и почему их не смешивают?
- Почему
sleepплох и когда он допустим? - Как Playwright понимает, что можно кликать?
- Назовите причины флаки и как вы их ищете.
- Нормально ли лечить флаки ретраями?