Ожидания в UI-автотестах: почему тесты флакают и как ждать правильно
Спроси любого автоматизатора, почему тест «то зелёный, то красный» — и чаще всего причина одна: ожидания. UI живёт асинхронно: запрос ушёл на сервер, спиннер крутится, элемент дорисовывается, анимация доигрывает. А тест выполняется мгновенно и стучится в элемент, которого ещё нет. Разберём, как ждать правильно и перестать ловить флаки.
sleep(5) — главное зло
Самый частый «фикс» флака — воткнуть паузу: time.sleep(5). Это проигрыш сразу по двум причинам:
- Медленно. Если элемент готов за 0.2 сек, ты всё равно ждёшь 5. Умножь на сотни тестов — прогон раздувается на десятки минут.
- Всё равно флакает. Сегодня сервер ответил за 3 сек, завтра под нагрузкой — за 6, и
sleep(5)не хватило. Ты просто угадываешь число: поставишь мало — падает, поставишь много — тормозит. Угадать нельзя.
Хардкод времени лечит симптом на конкретной машине в конкретный день. Правильный ответ — ждать событие/состояние, а не время.
Implicit wait — коварный
Implicit wait — глобальная настройка: «жди появления элемента до N секунд перед каждым поиском». Звучит удобно, но в проде подводит:
- Маскирует баги. Элемента нет из-за реального дефекта, а тест молча ждёт 10 секунд и только потом падает — диагностика замедляется, причина размывается.
- Конфликтует с explicit wait. Смешение implicit и explicit даёт непредсказуемые суммарные таймауты (в Selenium это официально не рекомендуется).
- Только про «присутствие в DOM». Ждёт, что элемент нашёлся, но не что он видим и кликабелен. Нашёлся ≠ готов.
Практика: выставить implicit в 0 и ждать всё явно.
Explicit wait — ждать условие
Explicit wait — «ждать конкретное условие до таймаута». Это правильный дефолт для Selenium-подобных фреймворков:
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "submit"))
)
Ключевое — ждёшь условие, а не время: элемент видим, кликабелен, текст появился, спиннер исчез, число строк в таблице изменилось. Как только условие выполнилось — идём дальше немедленно (быстро), а таймаут страхует от зависаний (стабильно).
Auto-wait — что меняют Playwright и Cypress
Современные фреймворки (Playwright, Cypress) делают ожидания встроенными: перед каждым действием фреймворк сам проверяет actionability — элемент прикреплён к DOM, видим, стабилен (не двигается), включён, не перекрыт. Плюс ассершены с ретраем:
await page.getByRole('button', { name: 'Submit' }).click(); // ждёт готовности сам
await expect(page.getByText('Готово')).toBeVisible(); // ретраит до таймаута
Это снимает 90% ручных ожиданий: ты пишешь «кликни» и «проверь», а ждать — забота движка. Отсюда и меньше флака из коробки.
Ждать СОСТОЯНИЕ, а не таймер
Главный сдвиг мышления — привязывайся к наблюдаемому состоянию:
- Сеть. Дождись ответа API, а не «примерно 2 секунды» (
waitForResponse, перехват запроса). - Спиннер/лоадер. Жди, пока индикатор загрузки исчезнет, а не фиксированное время.
- Навигация. Сменился URL, загрузилась страница, появился заголовок.
- Три разных состояния элемента. «Есть в DOM» ≠ «видим» ≠ «кликабелен». Кликать надо по последнему.
- Данные. Таблица дорисовалась (появились строки), а не «подождём, обычно успевает».
Анти-паттерны
sleep«на всякий случай» после каждого действия.- Ожидание фиксированной длительности анимации (
sleep(0.3)под CSS-transition). - Полл без таймаута — тест может висеть вечно.
- Слишком большие глобальные таймауты — маскируют реальные тормоза приложения.
- Ожидание «присутствия», когда нужно «кликабельности».
- Ретрай всего теста вместо починки конкретного ожидания (маскирует флак).
Как переписать флакающий тест
- Найди
sleepи спроси: чего именно я жду? (элемент, ответ, исчезновение спиннера). - Замени на explicit wait этого условия (или положись на auto-wait, если фреймворк умеет).
- Кликай/читай по «кликабельности/видимости», а не по «наличию в DOM».
- Для данных с сервера — жди ответ сети или конечное состояние UI, а не время.
- Прогони тест 20–50 раз подряд (в т.ч. в CI/параллели) — стабильно зелёный значит починил, а не замаскировал.
Коротко
- №1 причина флака — ожидания;
sleep(N)и медленный, и ненадёжный. - Implicit wait коварен: маскирует баги, конфликтует с explicit — держи его в 0.
- Explicit wait — ждать УСЛОВИЕ до таймаута: быстро, когда готово; стабильно, когда нет.
- Auto-wait (Playwright/Cypress) снимает большинство ручных ожиданий.
- Привязывайся к состоянию: ответ сети, исчез спиннер, элемент кликабелен — не к таймеру.
- Проверяй починку прогоном ×20–50, а не «вроде не падает».