automationflakywaitsseleniumplaywrightqa

Ожидания в 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).
  • Полл без таймаута — тест может висеть вечно.
  • Слишком большие глобальные таймауты — маскируют реальные тормоза приложения.
  • Ожидание «присутствия», когда нужно «кликабельности».
  • Ретрай всего теста вместо починки конкретного ожидания (маскирует флак).

Как переписать флакающий тест

  1. Найди sleep и спроси: чего именно я жду? (элемент, ответ, исчезновение спиннера).
  2. Замени на explicit wait этого условия (или положись на auto-wait, если фреймворк умеет).
  3. Кликай/читай по «кликабельности/видимости», а не по «наличию в DOM».
  4. Для данных с сервера — жди ответ сети или конечное состояние UI, а не время.
  5. Прогони тест 20–50 раз подряд (в т.ч. в CI/параллели) — стабильно зелёный значит починил, а не замаскировал.

Коротко

  • №1 причина флака — ожидания; sleep(N) и медленный, и ненадёжный.
  • Implicit wait коварен: маскирует баги, конфликтует с explicit — держи его в 0.
  • Explicit wait — ждать УСЛОВИЕ до таймаута: быстро, когда готово; стабильно, когда нет.
  • Auto-wait (Playwright/Cypress) снимает большинство ручных ожиданий.
  • Привязывайся к состоянию: ответ сети, исчез спиннер, элемент кликабелен — не к таймеру.
  • Проверяй починку прогоном ×20–50, а не «вроде не падает».