Flaky-тесты способны довести до отчаяния любую команду. Они падают без видимой причины, ломают пайплайны, откладывают релизы и портят настроение всем — и тестировщикам, и разработчикам. В 2ГИС мы жили с этой болью довольно долго, пока не решили подойти к проблеме системно. 

Ниже история, как наша команда UGC перешла от бесконечно красных пайплайнов к контролируемой и измеримой стабильности.

Всё началось с боли

Обычный день тестировщиков…
Обычный день тестировщиков…

Привет! Я Катя, руководитель группы тестирования в команде UGC (User Generated Content). Мы отвечаем за контент, который создают пользователи: фотографии, отзывы и все подобные данные. Для каждого типа контента у нас свой микросервис — всего таких около 35 проектов и порядка 40 000 тестов. Вся автоматизация написана на Python, тесты гоняются через фреймворк Vedro

Со временем мы заметили закономерность: как только количество тестов в проекте переваливает за тысячу, начинает расти частота флаков. 

Флаками мы считаем тесты, которые иногда падают, причём причины могут быть самыми разными: 

  • инфраструктура временно «шалит»; 

  • где-то спрятался неуловимый баг; 

  • поломалась логика теста; 

  • данные оказались некорректными или не обновились; 

  • тесты мешают друг другу; 

  • или просто магия ✨

Они ломали пайплайны и задерживали релизы, поэтому мы решили подойти к этому как к инженерной задаче. Разложили хаос на части и выделили ключевые направления, каждое из которых потребовало отдельного решения. Где‑то в итоге пришлось стабилизировать пайплайны, где‑то — автоматизировать анализ, а где‑то — навести порядок в Jira. 

Дальше по пунктам расскажу, что именно мы сделали.

Проблема 1. Красные пайплайны

Больше всего мы страдали от красных пайплайнов, поэтому первой целью поставили получить зелёные. 

Первая идея могла быть такой — скипать всё, что падало. Логика понятна: нет теста — нет проблемы. Но при нашей доли автоматизации мы не могли себе такое позволить:

скипаем тест теряем покрытие теряем доверие к тестам.

А ручного регресса у нас нет. Поэтому от этого варианта отказались сразу.

Следующая идея — использовать рераны (--reruns): они повторяют упавшие тесты несколько раз. И если тест проходит при повторе в большинстве случаев, то он считается успешным.

Рераны мы начали использовать, но всё же их было недостаточно. Хотелось чего-то стабильнее, желательно без ущерба покрытию. Так мы решили написать плагин — Vedro Flaky Steps (по ссылке можно посмотреть, как его установить и настроить у себя проектах). 

Что он делает: 

  • помечает нестабильные шаги в тестах;

  • сохраняет покрытие, так как тесты в этом плагине не скипаются полностью и запускаются при каждом прогоне как обычно; 

  • обеспечивает зелёный пайплайн, если тест падает по уже известной причине.

Если flaky-тест падает на известном шаге, пайплайн всё равно проходит. В джобе с тестами же появляется предупреждение. То есть мы видим: «Да, тест упал, но мы знаем почему». 

Screenshot from 2025-09-05 16-41-18.png
До внедрения Vedro Flaky Steps упавший тест отмечался как ValidationException, пайплайн соответственно считался упавшим.
Screenshot from 2025-09-05 16-47-55.png
После внедрения — тот же тест и пайплайн отмечаются зелёным, но с врезкой: найдено ожидаемое падение и ссылка на тикет. Пайплайн в этом случае остался зелёный.

Но есть несколько нюансов:

  • Если ошибка не совпадает с регуляркой — тест упадёт как обычно. 

  • Лучше проверить регулярку заранее. Например, на regex101.com, чтобы не запутаться в кавычках или звёздочках. 

  • Важно не указывать имя исключения! Регулярка должна содержать только текст ошибки, без ValidationException

  • Можно навешивать несколько @expected_failure на один шаг, если на нём бывают разные ошибки. 

  • Может создаться ложное ощущение, что с тестами всё хорошо. Важно помнить, что это все же не так.

В первом шаге мы достигли главного — пайплайн зелёный, покрытие не теряется, команда остаётся в контексте.

Проблема 2. Старые тесты падают

Даже если пайплайн зелёный, нам важно видеть реальную картину. Так мы захотели находить уже существующие flaky-тесты до того, как они будут мешать релизу.

Для этого мы:

  1. Настроили регулярные прогоны всех e2e-тестов на master в GitLab CI.

  2. Отключили все рераны, чтобы получать честное состояние тестов. Важно: это мы сделали только в регулярных прогонах по расписанию, в остальных случаях рераны остались. 

  3. Начали собирать статистику падений и анализировать её. 

Для такого анализа сначала использовали Allure TestOps, который показывает список успешных/падающих тестов и выводит самые нестабильные.

Screenshot from 2025-06-18 12-55-24.png

Он удобен для быстрой диагностики и приоритизации, но из-за того, что мы начали использовать плагин Vedro Flaky Steps, Allure уже не отражал реальную ситуацию, т.к. считал что все тесты проходят. 

Поэтому дальше мы начали использовать Vedro Cloud. Этот инструмент: 

  • показывает список всех тестов в разных статусах;

  • учитывает все падения, даже если тест был заплагинен, и даёт более точную картину, чем Allure TestOps;

  • показывает pass rate и длительность тестов;

  • подходит для глубокой аналитики и отслеживания динамики.

Screenshot from 2025-06-18 12-58-47.png
Screenshot from 2025-06-18 12-59-11.png

Vedro Cloud собирает много статистики и складывает в базу данных. Чтобы её визуализировать, мы используем Grafana

Screenshot from 2025-09-05 14-33-22.png

Итого: вторую проблему решили с помощью регулярных прогонов и дашбордов со статистикой.

Бонус — мини-бот падений в ММ

Наглядная визуализация помогла заметить неочевидные проблемы. Мы увидели на графиках, как в некоторые периоды столбики резко падали, затем снова поднимались — причём в эти моменты не создавались тикеты на падающие flaky-тесты и никто не знал о происходящем. Позже оказалось, что это были инфраструктурные сбои, о которых мы раньше просто не догадывались.

Чтобы не пропустить подобные проблемы, мы сделали мини-бота для Маттермоста. Он следит за всеми стадиями и, если падает мастер, моментально шлёт сообщение в командный чат.

Screenshot from 2025-10-13 21-35-33.png

Хотя этот бот и не связан напрямую с flaky-тестами, он помогает нам быстро различать другие проблемы. Например, благодаря этим данным мы увидели, что чаще всего причины падений мастера — именно инфраструктура или стейджинг, а не flaky-тесты. Это подтверждает, что наш подход к контролю flaky работает, позволяя команде сосредоточиться на настоящих ошибках.

Проблема 3. Новые тесты падают

Конечно, хотелось бы сразу писать тесты, которые проходят стабильно и не флакуют, но такое случается редко. Чтобы не допускать появления нестабильных тестов в проекте, мы настроили отдельную джобу check_new_tests_stability, которая многократно прогоняет изменённые e2e‑тесты (до девяти раз подряд). Если хотя бы один прогон падает, джоба считается упавшей. 

.PHONY: e2e-check-flaky
e2e-check-flaky:
    # запуск новых / изменённых тестов
    if [ -z "$$CHANGED_TESTS" ]; then echo "No changed tests to run"; exit 0; fi; \
    has_fails=0; \
    i=0; while [ "$$i" -le 2 ]; do \
        docker-compose exec ${DISABLE_TTY} e2e python3 bootstrap.py -r gitlab \
        --gitlab-collapsable=vars --repeats 3 --order-random $$CHANGED_TESTS ; \
        if [ "$$?" -ne 0 ]; then has_fails=1; fi; \
        i=$$((i + 1)); \
    done; \
    exit $${has_fails};
check_new_tests_stability:
  ...
  variables:
    # ограничение количества тестов
    MAX_CHANGED_TESTS: "15"
  ...
  before_script:
    # получение target ветки
    - 'TARGET_BRANCH_NAME=$(curl -LsS -H "PRIVATE-TOKEN: $PRIVATE_TOKEN" "https://gitlab.2gis.ru/api/v4/projects/$CI_PROJECT_ID/merge_requests?source_branch=$CI_COMMIT_REF_NAME" | jq --raw-output ".[0].target_branch")'
    # загрузка ветки с удаленного репозитория
    - git fetch origin $TARGET_BRANCH_NAME
    # получение списка новых / изменённых тестов
    - CHANGED_TESTS=$(git diff --name-only --diff-filter=ACMT origin/$TARGET_BRANCH_NAME -- tests/e2e | { grep -o 'scenarios.*\.py' || true; } | tr '\n' ' ')
    - CHANGED_TESTS_COUNT=$(wc -w <<< $CHANGED_TESTS)
    # проверка количества новых / изменённых тестов
    - if [ "$CHANGED_allow_failure: trueTESTS_COUNT" == "0" ]; then echo "No changed tests to run"; exit 0; fi
    - if [[ "$CHANGED_TESTS_COUNT" -ge $MAX_CHANGED_TESTS && "$MAX_CHANGED_TESTS" != "-1" ]]; then echo "Too many changed tests, run job manually!"; exit 1; fi
    # подготовка окружения для запуска тестов
    ...
  script:
    # запуск новых / изменённых тестов
    - make e2e-check-flaky CHANGED_TESTS="$CHANGED_TESTS"
  only:
    # правило, чтобы джоба появлялась только при изменениях в тестовых файлах
    changes:
      - tests/e2e/**/*.py

check_new_tests_stability:manual:
  extends: check_new_tests_stability
  when: manual
  variables:
    MAX_CHANGED_TESTS: "-1"
  only:
    - branches

Что делает check_new_tests_stability:

  • определяет target-ветку;

  • находит изменённые тесты;

  • прогоняет их заданное количество раз;

  • если хоть один упал — джоба падает;

  • запускается только в ветках.

Screenshot from 2025-07-23 14-51-12.png
Screenshot from 2025-09-05 13-33-36.png

В этом решении есть нюансы, которые пока не победили. Во-первых, хелперы не считаются изменением теста: если изменился только хелпер, а сами тесты нет — джоба не запустится. Во-вторых, есть сложности с параметризованными тестами: если он изменился — запустится в джобе по всем параметрам и время выполнение джобы может быть долгим.

Когда это починим, отдельно напишем и расскажем. Но даже с этими ограничениями джоба даёт огромный профит. 

Проблема 4. Тикеты дублируются

Когда флаков много, быстро превращаются в лавину тикетов. Кто-то оформляет подробно, кто-то — в одну строчку, кто-то дублирует существующие запросы. Мы решили автоматизировать и этот процесс.

Создали Flakyzavr — плагин, который:

  • сам создаёт тикеты на flaky-тесты с ночных прогонов;

  • добавляет комментарий, если тикет уже есть и он открыт;

  • создаёт новый, если тикет был закрыт;

  • проставляет все нужные метки на тикет;

  • подтягивает стектрейс, ссылку на джобу и описание падения.

Например, вот как выглядит тикет на новый падающий тест:

Screenshot from 2025-09-05 18-36-37.png
Screenshot from 2025-09-05 18-34-12.png
Screenshot from 2025-09-05 18-35-30.png

Или тест упал не первый раз, тикет был:

Мы также оптимизировали плагин, чтобы он корректно обрабатывал параметризацию — теперь тикет создаётся по пути теста, а не по его имени. 

Планы на будущее — научить Flakyzavr сам писать регулярки для Vedro Flaky Steps и автоматически добавлять их в тесты. А ещё вскоре прикрутим AI.

Проблема 5. Некогда фиксить

Чтобы тикеты не копились мёртвым грузом, мы ввели дежурство по флакам. Раз в неделю один из тестировщиков берёт смену. Он работает только с тикетами Flakyzavr и определяет — чинить сразу, плагинить или отложить в бэклог.

В каждом новом тикете на flaky-тест — подробные шаги, что делать. 

Screenshot from 2025-09-05 19-13-03.png

Также в помощь дежурным создали Deflakyzavr, который автоматически создаёт тикеты на каждое дежурство. 

Также он анализирует, какие из тикетов давно не обновлялись, связывает с текущими и закрывает устаревшие. Если же тест упадёт снова — создастся новый тикет. 

Screenshot from 2025-10-15 22-03-26.png
Связь flaky-тикетов, которые давно не обновлялись

Результаты

За июль в двух проектах джоба check_new_tests_stability не пропустила в мастер 45 флаков. Это значит, что мы предотвратили появление 45 новых тикетов и столько же потенциальных «пожаров». 

Динамика по проекту «Фото» показала, что тестов стало больше, а падений — меньше. Средняя длительность пайплайна тоже снизилась: меньше реранов, больше уверенности.

Лонг стори шорт: памятка для тех, кто пролистал статью

❌ Пайплайны всегда красные?

✔️ Используем рераны + помечаем нестабильные шаги плагином Vedro Flaky Steps

❌ Старые тесты падают?

✔️ Проверяем стабильность на мастере: делаем прогоны по расписанию и используем дашборды

❌ Новые тесты падают?

✔️ Проверяем стабильность до мастера: используем джобу check_new_tests_stability

❌ Тикеты дублируются?

✔️Автоматизируем работу с тикетами с плагином Flakyzavr

❌ Фиксить некогда?

✔️ Используем регулярный мониторинг и дежурства: Flakyzavr + Deflakyzavr + task weight

❌ А становится ли лучше?

✔️ Контролируем через цифры: Jira + Vedro Cloud + alerting в mm

И пусть мы не победили flaky-тесты полностью, но постарались немного их приручить! ?

Комментарии (6)


  1. EgasVegas
    26.08.2026 18:38

    спасибо за статью, я очень люблю флаки тесты за их изюминку появляться не в нужный момент и за то, что они бросают мне вызов в их излечении. флаки тест - это гораздо интереснее, чем обычное падение.

    но есть и капля дёгтя - это флаки тесты, которые появляются из-за других тестов (о которых ты можешь и не знать, и тут ещё есть квест их найти), например когда разные сценарии запускаются на общих данных параллельно, особенно когда эти данные нельзя сделать изолированными.

    хотел бы узнать часто ли бывают у вас такие ситуации и как вы боритесь с этим?