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

Привет! Я Катя, руководитель группы тестирования в команде UGC (User Generated Content). Мы отвечаем за контент, который создают пользователи: фотографии, отзывы и все подобные данные. Для каждого типа контента у нас свой микросервис — всего таких около 35 проектов и порядка 40 000 тестов. Вся автоматизация написана на Python, тесты гоняются через фреймворк Vedro.
Со временем мы заметили закономерность: как только количество тестов в проекте переваливает за тысячу, начинает расти частота флаков.
Флаками мы считаем тесты, которые иногда падают, причём причины могут быть самыми разными:
инфраструктура временно «шалит»;
где-то спрятался неуловимый баг;
поломалась логика теста;
данные оказались некорректными или не обновились;
тесты мешают друг другу;
или просто магия ✨
Они ломали пайплайны и задерживали релизы, поэтому мы решили подойти к этому как к инженерной задаче. Разложили хаос на части и выделили ключевые направления, каждое из которых потребовало отдельного решения. Где‑то в итоге пришлось стабилизировать пайплайны, где‑то — автоматизировать анализ, а где‑то — навести порядок в Jira.
Дальше по пунктам расскажу, что именно мы сделали.
Проблема 1. Красные пайплайны
Больше всего мы страдали от красных пайплайнов, поэтому первой целью поставили получить зелёные.
Первая идея могла быть такой — скипать всё, что падало. Логика понятна: нет теста — нет проблемы. Но при нашей доли автоматизации мы не могли себе такое позволить:
скипаем тест → теряем покрытие → теряем доверие к тестам.
А ручного регресса у нас нет. Поэтому от этого варианта отказались сразу.
Следующая идея — использовать рераны (--reruns): они повторяют упавшие тесты несколько раз. И если тест проходит при повторе в большинстве случаев, то он считается успешным.
Рераны мы начали использовать, но всё же их было недостаточно. Хотелось чего-то стабильнее, желательно без ущерба покрытию. Так мы решили написать плагин — Vedro Flaky Steps (по ссылке можно посмотреть, как его установить и настроить у себя проектах).
Что он делает:
помечает нестабильные шаги в тестах;
сохраняет покрытие, так как тесты в этом плагине не скипаются полностью и запускаются при каждом прогоне как обычно;
обеспечивает зелёный пайплайн, если тест падает по уже известной причине.
Если flaky-тест падает на известном шаге, пайплайн всё равно проходит. В джобе с тестами же появляется предупреждение. То есть мы видим: «Да, тест упал, но мы знаем почему».

ValidationException, пайплайн соответственно считался упавшим.
Но есть несколько нюансов:
Если ошибка не совпадает с регуляркой — тест упадёт как обычно.
Лучше проверить регулярку заранее. Например, на regex101.com, чтобы не запутаться в кавычках или звёздочках.
Важно не указывать имя исключения! Регулярка должна содержать только текст ошибки, без
ValidationException.Можно навешивать несколько
@expected_failureна один шаг, если на нём бывают разные ошибки.Может создаться ложное ощущение, что с тестами всё хорошо. Важно помнить, что это все же не так.
В первом шаге мы достигли главного — пайплайн зелёный, покрытие не теряется, команда остаётся в контексте.
Проблема 2. Старые тесты падают
Даже если пайплайн зелёный, нам важно видеть реальную картину. Так мы захотели находить уже существующие flaky-тесты до того, как они будут мешать релизу.
Для этого мы:
Настроили регулярные прогоны всех e2e-тестов на master в GitLab CI.
Отключили все рераны, чтобы получать честное состояние тестов. Важно: это мы сделали только в регулярных прогонах по расписанию, в остальных случаях рераны остались.
Начали собирать статистику падений и анализировать её.
Для такого анализа сначала использовали Allure TestOps, который показывает список успешных/падающих тестов и выводит самые нестабильные.

Он удобен для быстрой диагностики и приоритизации, но из-за того, что мы начали использовать плагин Vedro Flaky Steps, Allure уже не отражал реальную ситуацию, т.к. считал что все тесты проходят.
Поэтому дальше мы начали использовать Vedro Cloud. Этот инструмент:
показывает список всех тестов в разных статусах;
учитывает все падения, даже если тест был заплагинен, и даёт более точную картину, чем Allure TestOps;
показывает pass rate и длительность тестов;
подходит для глубокой аналитики и отслеживания динамики.


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

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

Хотя этот бот и не связан напрямую с 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-ветку;
находит изменённые тесты;
прогоняет их заданное количество раз;
если хоть один упал — джоба падает;
запускается только в ветках.


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



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


Мы также оптимизировали плагин, чтобы он корректно обрабатывал параметризацию — теперь тикет создаётся по пути теста, а не по его имени.
Планы на будущее — научить Flakyzavr сам писать регулярки для Vedro Flaky Steps и автоматически добавлять их в тесты. А ещё вскоре прикрутим AI.
Проблема 5. Некогда фиксить
Чтобы тикеты не копились мёртвым грузом, мы ввели дежурство по флакам. Раз в неделю один из тестировщиков берёт смену. Он работает только с тикетами Flakyzavr и определяет — чинить сразу, плагинить или отложить в бэклог.
В каждом новом тикете на flaky-тест — подробные шаги, что делать.

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

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

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

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

Лонг стори шорт: памятка для тех, кто пролистал статью
❌ Пайплайны всегда красные? |
✔️ Используем рераны + помечаем нестабильные шаги плагином Vedro Flaky Steps |
❌ Старые тесты падают? |
✔️ Проверяем стабильность на мастере: делаем прогоны по расписанию и используем дашборды |
❌ Новые тесты падают? |
✔️ Проверяем стабильность до мастера: используем джобу check_new_tests_stability |
❌ Тикеты дублируются? |
✔️Автоматизируем работу с тикетами с плагином Flakyzavr |
❌ Фиксить некогда? |
✔️ Используем регулярный мониторинг и дежурства: Flakyzavr + Deflakyzavr + task weight |
❌ А становится ли лучше? |
✔️ Контролируем через цифры: Jira + Vedro Cloud + alerting в mm |
И пусть мы не победили flaky-тесты полностью, но постарались немного их приручить! ?
EgasVegas
спасибо за статью, я очень люблю флаки тесты за их изюминку появляться не в нужный момент и за то, что они бросают мне вызов в их излечении. флаки тест - это гораздо интереснее, чем обычное падение.
но есть и капля дёгтя - это флаки тесты, которые появляются из-за других тестов (о которых ты можешь и не знать, и тут ещё есть квест их найти), например когда разные сценарии запускаются на общих данных параллельно, особенно когда эти данные нельзя сделать изолированными.
хотел бы узнать часто ли бывают у вас такие ситуации и как вы боритесь с этим?