Флаки-тесты и таймауты

Нестабильные тесты — главная боль E2E. Разбираем причины и проверенные способы лечения.

Флаки-тест (flaky test) — тест, который иногда проходит, а иногда падает без изменений в коде. Это самая частая и самая дорогая проблема E2E.

Почему флаки так вредны

Один тест, падающий раз в десять прогонов, кажется мелочью. Но когда в наборе сотня тестов и каждый изредка «моргает», красный CI становится нормой. Команда перестаёт доверять тестам и начинает перезапускать их «вслепую» — а это уже путь к тому, что реальные баги проскальзывают незамеченными. Поэтому борьба с флаки — не косметика, а вопрос ценности всего набора тестов.

Главные причины флаки

ПричинаЛечение
Ручные паузы вместо ожиданийубрать waitForTimeout, довериться авто-ожиданиям
Мгновенное чтение состоянияиспользовать web-first assertions
Хрупкие локаторы (CSS/XPath по позиции)перейти на getByRole/getByText
Зависимость тестов друг от другаизоляция: чистый контекст на каждый тест
Общие данные между тестамиуникальные данные на каждый прогон
Гонки с анимациейдождаться стабильности (Playwright делает сам)

Антипаттерн и его исправление

// ФЛАКИ: пауза наугад + чтение «сейчас»
await page.click('#load');
await page.waitForTimeout(1000);
const items = await page.locator('.item').count();
expect(items).toBe(5);

// СТАБИЛЬНО: семантический локатор + web-first assertion
await page.getByRole('button', { name: 'Загрузить' }).click();
await expect(page.getByRole('listitem')).toHaveCount(5);

Второй вариант сам дождётся, пока появятся ровно пять элементов — без угадывания времени. Большинство флаки лечатся именно такой заменой: «угадал тайминг» → «дождался состояния».

Изоляция тестов

Тесты не должны зависеть от порядка запуска. Если тест Б ожидает, что тест А что-то создал, то при параллельном или выборочном запуске всё ломается. Playwright по умолчанию даёт каждому тесту чистый контекст браузера — пользуйтесь этим и не оставляйте «хвостов» в общем состоянии.

Ретраи: лечить, а не прятать

Playwright умеет перезапускать упавший тест автоматически. Это настраивается в конфиге.

// playwright.config.ts
export default defineConfig({
  retries: 2,  // упал — перезапустить до 2 раз
});

Ретраи полезны в CI, чтобы случайный сбой инфраструктуры не валил весь прогон. Но это обезболивающее, а не лекарство: если тест проходит только со второй попытки, в нём есть реальная нестабильность, которую надо найти и устранить. Playwright помечает такие тесты как «flaky» в отчёте — это сигнал к разбору, а не повод расслабиться.

Виды таймаутов

В Playwright несколько уровней таймаутов — важно не путать.

ТаймаутЧто ограничиваетПо умолчанию
Test timeoutвесь тест целиком30 с
Expect timeoutодну web-first проверку5 с
Action timeoutодно действие (клик и т.п.)не ограничен
// playwright.config.ts
export default defineConfig({
  timeout: 30_000,                  // на весь тест
  expect: { timeout: 5_000 },       // на каждую проверку
});

Увеличивать таймауты стоит осознанно. Если тест стабильно не укладывается в 30 секунд — обычно проблема не в таймауте, а в том, что тест делает слишком много или ждёт чего-то ненадёжного. Раздувание таймаутов — это попытка заглушить симптом.

Итог

  • Флаки-тест падает случайно и подрывает доверие к тестам — это главная боль E2E.
  • Частые причины: ручные паузы, мгновенные проверки, хрупкие локаторы, зависимость тестов.
  • Лечение: авто-ожидания, web-first assertions, семантические локаторы, изоляция.
  • Ретраи — обезболивающее: «flaky» в отчёте означает, что причину надо найти.
  • Таймауты бывают на тест, на проверку и на действие — увеличивайте их осознанно.
Проверьте себя
1. Что такое флаки-тест?
AТест, который всегда падает
BТест, который то проходит, то падает без изменений в коде
CТест, написанный на устаревшем синтаксисе
DТест, который проверяет слишком много за раз
2. Как правильно относиться к ретраям (retries) в Playwright?
AЭто полное решение проблемы флаки
BЭто обезболивающее: пометка flaky сигналит, что причину надо найти
CРетраи замедляют тесты и их надо отключать
DРетраи делают тесты случайными
3. Какой таймаут ограничивает выполнение всего теста целиком?
AExpect timeout (по умолчанию 5 с)
BAction timeout
CTest timeout (по умолчанию 30 с)
DNavigation timeout
Поддержать проект