В очереди CI появляется e2e-тест: initial attempt failed, first retry passed. До релиза час, и самое быстрое предложение — увеличить timeout или retries. Проблема в том, что policy меняется раньше минимальных фактов: selector, ожидаемый UI-result, событие первой попытки и evidence конкретного запуска.
Цена поспешного «исправления flaky» двойная. Если первая ошибка отражает неверный readiness contract, retry превращает её в нерегулярный шум и оставляет продуктовую границу непроверенной. Если причина в изоляции данных или внешней зависимости, общий timeout увеличивает время обратной связи для всех тестов. Полезный разбор ограничивает утверждения: статус flaky — это результат повторного запуска, trace — evidence попытки, а решение должно быть точечным и иметь rollback. До этих шагов слово «стабилизировали» использовать рано.
Карточка разбора: пять фактов до изменения конфигурации
Начинаем не с видео и не с серии повторов. В карточке достаточно имени теста, initial/retry outcome, selector, readiness и ссылки или отсутствия evidence. Если trace сохранён только на retry, так и пишут: «attempt 1». Карточка удерживает разговор на фактах и не даёт перескочить от красного статуса к глобальной настройке.
Для selector важны форма и область: getByRole('button', { name: 'Оплатить' }) внутри нужного диалога или контейнера — другой контракт, чем CSS-цепочка по классам. Для readiness важен терминальный факт: текст confirmed в status, новая строка с итогом или явный route. Для retry важен номер попытки и policy, а не эмоция «на второй раз повезло». Для evidence важно происхождение: trace, screenshot или log могут сузить вопрос, но должны быть привязаны к одному запуску и не обещать root cause.
| Наблюдаемая форма | Что уже известно | Чего ещё нет | Минимальная проверка | Безопасное действие |
|---|---|---|---|---|
| 0 или несколько locator matches | нарушен selector contract | причина изменения DOM и корректный target | сузить scope, role/name или test id | исправить locator; readiness и retry не менять вслепую |
| click не проходит actionability | control не готов для действия | успех операции после click | проверить overlay, disabled, animation или неправильный state | исправить предусловие экрана либо сценарий |
| click проходит, readiness не достигнут | действие допустимо, ожидаемый факт не наблюдается | почему UI не получил нужное состояние | сверить semantic assertion и входные данные | уточнить UI-contract или изоляцию данных |
| failed → passed на retry | две попытки имеют разные outcome | корневую причину и воспроизводимость | сравнить данные, worker boundary и evidence по попыткам | создать triage, не увеличивать timeout автоматически |
| trace есть только на retry | есть контекст повторной попытки | контекст initial failure | подписать attempt и вопрос к артефакту | собрать отдельный evidence path, если он нужен |
Политика retry должна сохранять сигнал
В Playwright v1.37.0 retries конфигурируются отдельно, а failed test запускается заново в новом worker. Поэтому retry — не «ещё немного подождать в той же странице». Он создаёт новую попытку с новой границей исполнения. Это удобно для независимых тестов и одновременно важно для диагностики: порядок, cookies, storage, server-side cleanup и данные могут вести себя иначе. Если тест зависит от предыдущего случая, повторный запуск иногда скрывает связь, потому что даёт более чистый контекст.
Flaky — маршрут в triage, не разрешение на merge. Временное исключение требует owner, срока, evidence policy и критерия снятия. Универсального числа retries нет: оно зависит от цены очереди, релиза и способности разобрать сигнал. Лучше оставить значение неизвестным, чем назвать его «стандартом».
// Иллюстрация для Playwright v1.37.0; этот фрагмент не запускается fixture.
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 1 : 0,
use: { trace: 'on-first-retry' },
});
// retry сохраняет evidence первого повторного запуска.
// Он не заменяет явное условие готовности после click.
Trace помогает ставить вопрос, а не закрывать его
По документации Playwright 1.37.0 Trace Viewer показывает последовательность действий, snapshots, action log, source и network log для записанного trace. Режим on-first-retry связывает запись с первой повторной попыткой. Это полезно, если картинка или log помогают отличить «не тот control» от «после click не появилось нужное состояние». Но такой артефакт не подтверждает фактическое выполнение в другой попытке и не знает, какой бизнес-результат ожидался, пока тест не записал его assertion.
Пока у команды нет файла или ссылки, в карточке стоит evidence: absent. У артефакта сначала сверяют attempt, проект и тест, затем выбирают один вопрос: locator перед click или UI-state перед assertion. Запрос «найди причину по trace» слишком широк и поощряет первую красивую гипотезу.
Учебный fixture проверяет форму решения, а не качество теста
Чтобы не превратить текст в лозунг, sidecar содержит детерминированный fixture. Он создаёт два marked synthetic event/outcome объекта в памяти. У initial attempt selector уникален, но readiness payment-state=confirmed не достигнут; у retry тот же selector и readiness достигнут. Классификатор возвращает только форму retry-masked-missing-readiness-contract-in-synthetic-input. Он прямо маркирует, что не оценивал real flake, duration и browser compatibility.
node web/scripts/upgrade-2023-08.mjs --verify-fixture
# Команда детерминированно создаёт marked synthetic attempts только в памяти.
# Она не открывает браузер, не читает проект, не запускает Playwright,
# не записывает trace/video, не измеряет duration и не проверяет compatibility.
# PASS проверяет разделение selector, readiness, retry и synthetic evidence.
# PASS не означает, что настоящий тест flaky, что причина найдена или что UI готов.
Fixture также моделирует множественный selector, расходящуюся retry-policy и evidence, где actualTrace перестало быть not-produced. Так реальный artefact нельзя подложить в учебный пример, а две попытки с разной policy нельзя выдать за сравнимый сценарий. Plan принимает только readiness change с owner, rollback, next check и condition, совпадающим с evidence; timeout или selector без evidence отклоняются. PASS проверяет процедуру, не стабильность настоящего теста.
Маршрут: симптом → причина → проверка → действие
- Симптом. Зафиксируйте failed/pass как два outcome с номерами попыток. Не склеивайте их в фразу «иногда падает» и не удаляйте исходный error из обсуждения.
- Причина. Сформулируйте конкурирующие классы: selector, actionability, readiness, test-data isolation или внешняя зависимость. Retry сам по себе не выбирает один из них.
- Проверка selector. Проверьте cardinality и user-facing смысл locator. Нулевой или множественный match закрывает этот шаг отдельной правкой; не ждите ready state у неопределённой цели.
- Проверка readiness. Сравните assertion с пользовательским результатом. Spinner, network idle или disabled state замените на semantic result, согласованный с владельцем UI.
- Проверка evidence. Привяжите trace, log или screenshot к attempt. Для
on-first-retryв v1.37.0 evidence относится к retry, поэтому initial failure может требовать отдельного способа сохранения контекста. - Действие. Выберите малый diff: locator, semantic assertion, изоляция данных или policy артефактов. Timeout меняют после названного события и доказанной верхней границы.
- Rollback и следующий шаг. До merge зафиксируйте прежнее условие, owner и критерий возврата. После следующего запуска смотрите не на один зелёный retry, а на то, что новый contract проверяет нужный пользовательский факт.
Маленький diff и явный rollback лучше широкой настройки
Если причина указывает на readiness, хороший diff меняет assertion и ничего больше: он ждёт терминальный UI-state, а retry/timeouts остаются прежними. Если причина указывает на selector, diff ограничивается locator и тестом его scope. Если нарушена изоляция, изменение может быть в setup/cleanup или фикстуре данных. Эти варианты имеют разный owner и разную цену. Общий timeout объединяет их в один параметр и поэтому редко даёт объяснимый rollback.
Rollback должен быть конкретным. Для assertion — вернуть предыдущую строку теста и снова собрать evidence; для selector — восстановить прошлый locator, если новый контракт не подтвердился; для данных — отменить созданную запись через тот же интерфейс подготовки. Для policy retry — вернуть прошлое значение отдельным config diff и указать, какие тесты это затронет. Нельзя считать rollback успешным, пока команда не понимает, какой слой откатывает. Учебная функция rollbackSyntheticReadinessPlan() специально ничего не применяет и возвращает это явным полем.
Ограничения и следующий проверяемый шаг
Материал не содержит реального trace, video, браузерного запуска, CI-лога, оценки flake rate, duration или данных пользователя. Он не утверждает совместимость Playwright v1.37.0 с вашим окружением; version pin не заменяет проверку пакета и project config. Правило переносят только после review данных и рисков контура.
Следующий шаг — взять один flaky из отчёта и за 15 минут заполнить карточку: selector, readiness, initial/retry, evidence, owner/rollback. Если хотя бы одно поле неизвестно, зафиксируйте его как вопрос, а не как предположение. После этого сделайте один малый diff или соберите один недостающий артефакт. Так устойчивость тестов строится не на ожидании удачного повтора, а на проверяемом контракте пользовательского сценария.
Проверяемые источники
- Playwright v1.37.0: официальный GitHub release, 10 августа 2023 — версионный срез, опубликованный до конца августа 2023. Он фиксирует выпуск, но не подтверждает версию, браузеры или настройку конкретного проекта.
- Playwright v1.37.0: Auto-waiting, исходник официальной документации по тегу — для click описаны проверки attached, visible, stable, receives events и enabled. Эти проверки готовят действие с элементом, но не доказывают бизнес-результат после действия.
- Playwright v1.37.0: Retries, исходник официальной документации по тегу — описывает повторный запуск после сбоя, новый worker и статусы passed, flaky, failed. Статус flaky — классификация результата запусков, а не причина сбоя.
- Playwright v1.37.0: Trace Viewer, исходник официальной документации по тегу — описывает действия, snapshots, action log, source и network log; режим on-first-retry записывает trace при первом retry. Такой артефакт — evidence одного запуска, а не доказательство корневой причины.
- Playwright v1.37.0: Best Practices, исходник официальной документации по тегу — рекомендует изоляцию тестов, пользовательские locator и web-first assertions. Рекомендация не заменяет проверку реального контракта конкретного экрана.