Все разборы

Автотесты

Как ответить на вопрос «Что такое flaky-тесты и как с ними бороться» на собеседовании QA

Что сказать на собеседовании про нестабильные автотесты: откуда они берутся, как разобрать причину, чем плох sleep и как отличить баг продукта от бага теста.

Зачем это спрашивают

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

Ещё этот вопрос проверяет, понимаете ли вы, что нестабильность бывает не только в тесте. Иногда плавает сам продукт, и тест честно это показывает.

Как построить сильный ответ

Определение

Flaky-тест — тест, который на одном и том же коде и окружении то проходит, то падает. Ключевое — «без изменений»: если упал после коммита, это обычное падение, а не нестабильность.

Основные причины

  1. Общие данные и порядок. Тесты используют один и тот же объект, и результат зависит от того, кто пришёл первым. Особенно заметно при параллельном запуске.
  2. Время и ожидания. Жёсткие паузы, слишком короткие таймауты, проверка элемента до того, как он отрисовался.
  3. Асинхронность. Результат появляется не сразу: фоновые задачи, очереди, обновление интерфейса после ответа сервера.
  4. Окружение. Нестабильный стенд, медленная сеть, внешние сервисы, которые иногда отвечают ошибкой.
  5. Дата и случайность. Тест падает в полночь, в конце месяца или на определённом случайном значении.
  6. Настоящая гонка в продукте. Например, двойной клик создаёт два заказа, но не всегда.

Как бороться

  • Разобрать конкретное падение. Сообщение assert, трейсбек, скриншот, видео, трейс. В Playwright для этого есть trace viewer; в pytest-playwright трейс можно сохранять только для упавших тестов флагом --tracing retain-on-failure.
  • Изолировать данные. Каждый тест создаёт свои данные и убирает за собой — через фикстуры с yield, подробнее в статье про фикстуры pytest. В браузерных тестах — новый контекст на каждый тест, чтобы не тащить куки и localStorage.
  • Ждать состояние, а не время. Вместо time.sleep(3) или page.wait_for_timeout(3000) — проверки, которые сами повторяются до таймаута:
from playwright.sync_api import Page, expect


def test_login_shows_username(page: Page):
    page.goto("/login")
    page.get_by_label("Имя пользователя").fill("qa_user")
    page.get_by_label("Пароль").fill("qa_password")
    page.get_by_role("button", name="Войти").click()
    expect(page.get_by_test_id("username")).to_have_text("qa_user")

expect ждёт, пока текст станет нужным, и падает только по таймауту. Локаторы Playwright сами ждут, пока элемент станет видимым и доступным для клика.

  • Стабилизировать внешнее. Подменять нестабильные внешние сервисы, фиксировать время, задавать seed для случайных данных.
  • Воспроизвести локально. Запустить тест много раз подряд или в другом порядке, чтобы поймать частоту падения.
  • Карантин с задачей, если быстро починить нельзя. Не удалять и не отключать молча.

Пример ответа

Flaky-тест — это тест, который на том же коде то проходит, то падает. Чаще всего причина в общих данных между тестами, в ожиданиях по времени, в асинхронности или в нестабильном стенде. Иногда это настоящая гонка в продукте, поэтому я не списываю падение на тест сразу. Сначала смотрю сообщение, трейсбек и трейс, повторяю шаг руками. Дальше лечу причину: каждому тесту — свои данные с уборкой через фикстуры, свой контекст браузера; вместо sleep — ожидание нужного состояния через expect; внешние сервисы — подменяю. Если быстро не починить, переношу тест в карантин с задачей и сроком. Перезапуск без разбора и отключение теста решением не считаю: так команда перестаёт доверять прогонам.

Частые ошибки

  • «Добавлю sleep». Пауза либо слишком короткая и тест всё равно падает, либо слишком длинная и прогон замедляется. Причина остаётся.
  • «Перезапущу, и всё пройдёт». Повтор прячет проблему, а иногда и настоящий баг продукта.
  • Молча отключить тест. Проверка исчезает, а риск остаётся.
  • Путать нестабильный тест с упавшим после изменений. Если тест стабильно падает после коммита, это сигнал о регрессии, а не flaky.
  • Не различать баг теста и баг продукта. Без ручного воспроизведения это гадание. Похожий разбор нужен, когда разработчик не может воспроизвести баг.

Пример на MarketCove

Представьте набор UI-тестов на учебный магазин https://shop.tomiddle.com, запущенный параллельно. Где можно ожидать нестабильности и как её предупредить:

  • Корзина хранится в localStorage. Если тесты делят один контекст браузера, товар из одного теста окажется в корзине другого. Решение — новый контекст на каждый тест.
  • Заказы и аккаунты живут в памяти сервера. Тест «история заказов в профиле» не должен рассчитывать на точное число заказов, если параллельно другие тесты оформляют заказы под тем же пользователем. Решение — отдельный пользователь на тест или проверка конкретного созданного заказа.
  • Вход. Тест нажимает «Войти» и сразу открывает профиль. Токен попадает в localStorage только после ответа сервера, поэтому переход раньше времени даёт то профиль, то форму входа. Нужно дождаться признака входа через expect, а не полагаться на скорость стенда.
  • Перезапуск сервера. Данные в памяти пропадают, поэтому тест не должен опираться на заранее созданные заказы.

Задание для практики

Напишите один UI-тест на вход в аккаунт и сделайте его специально нестабильным: прочитайте имя пользователя на странице через inner_text() сразу после клика «Войти», без ожидания. Запустите тест 20 раз подряд и посчитайте, сколько раз он упал. Затем замените чтение на expect(...).to_have_text(...), повторите запуск и сравните. Запишите короткий разбор: причина, как нашли, как починили. Проверьте себя по вопросам в разделе про Playwright и автотесты.

Что спросят следом

  • Можно ли просто включить автоматический перезапуск упавших тестов?
    Как временная мера на время разбора — да, если каждый перезапуск фиксируется и по нестабильному тесту заведена задача. Как постоянное решение — нет: перезапуск прячет и проблемы теста, и настоящие плавающие баги продукта, например гонки при параллельных запросах. Команда перестаёт доверять зелёному прогону.
  • Автотест упал. Как понять, баг это продукта или теста?
    Сначала прочитать сообщение assert и трейсбек, затем повторить тот же шаг руками в браузере, Postman или curl. Если вручную поведение неверное и расходится с требованиями — это баг продукта, его оформляют баг-репортом с запросом и ответом. Если вручную всё работает, смотрят на тест и окружение: устаревший локатор, протухший токен, недоступный стенд, отсутствующие данные.
  • Что такое карантин для тестов?
    Это отдельная пометка или группа для тестов, которые признаны нестабильными: они продолжают запускаться, но их падение не блокирует сборку. Карантин работает, только если у каждого теста в нём есть задача, ответственный и срок. Иначе он превращается в кладбище отключённых проверок.
  1. Q-01
    Что такое фикстуры в pytest?
  2. Q-02
    Разработчик говорит, что баг не воспроизводится. Ваши действия?
  3. Q-03
    Что такое пирамида тестирования?

Отрепетировать ответ с куратором

В сопровождении — весь банк вопросов, мок-собеседование и разбор каждого настоящего собеседования, пока не придёт оффер. Гарантий трудоустройства не даём — готовим к тому, что спросят.

Весь банк вопросов

Здесь — разбор одного вопроса. Банк целиком, с разбором и повторением, открыт в сопровождении; три вопроса — в витрине тренажёра.