Надёжность E2E решается не в браузере, а в данных. Самый частый источник флака - не капризный локатор, а общий пользователь, заказ или запись, которые несколько тестов читают и меняют одновременно. Пока данные общие и изменяемые, никакая аккуратность в сценарии не спасёт: тесты будут влиять друг на друга. Поэтому профессиональный подход начинается с вопроса, откуда берутся данные и куда деваются после теста, а уже потом - с кликов.
Способ подготовки данных выбирают по стоимости и надёжности. Создать пользователя или заказ через внутренний тестовый API почти всегда быстрее и стабильнее, чем прокликивать их создание в UI. Прямая запись в базу допустима для своей, контролируемой базы - но только если сохраняет инварианты и ограничения схемы, иначе тест работает с невозможным состоянием. Создание через UI оправдано лишь тогда, когда само создание - проверяемый пользовательский путь.
Ключ к безопасной параллельности - уникальность данных. Если тест создаёт пользователя с фиксированным адресом, два параллельных прогона столкнутся на нём. Уникальность обеспечивают, вплетая в данные идентификатор worker'а и случайный суффикс: email вида e2e-<workerIndex>-<uuid>@example.test гарантированно не пересечётся ни с соседним worker'ом, ни с прошлым прогоном. Каждый тест работает со своим собственным пользователем, а не с общим.
Полезно один раз увидеть, как создаётся такой уникальный набор данных под конкретный тест. Ниже - сценарий, который через доступный в тесте testInfo берёт индекс worker'а, добавляет случайный uuid и создаёт собственного пользователя через API. К этому приёму возвращаются всякий раз, когда тесту нужны свежие данные, которые никто другой не тронет.
Отдельная дисциплина - уборка. Данные, созданные тестом, надо уметь убирать, иначе тестовая база распухает и начинает влиять на прогоны. Работают три механизма: namespace по worker или test id, чтобы данные не путались между собой; TTL, автоматически подчищающий старое; и явное удаление после теста. Вместе они делают параллельный прогон безопасным - каждый убирает за собой, не задевая соседей.
Fixture-подход соединяет создание и уборку в один жизненный цикл. Данные создают перед телом теста и удаляют после него в одном месте, так что тест получает готовое и не заботится об очистке вручную. Это надёжнее, чем разбросанные по сценариям create и delete: если тест упал в середине, уборка всё равно выполнится, и следующий прогон стартует с чистого состояния, а не с обломков предыдущего.
Важный принцип - минимальность и воспроизводимость seed. Factory должна создавать ровно те данные, которые нужны сценарию, а не полный граф связанных сущностей на всякий случай: лишнее замедляет и делает тест непрозрачным. А seed - предсказуемым: один и тот же сценарий на одинаковом входе должен давать одинаковый результат, иначе воспроизвести падение и починить его будет невозможно.
Типичные провалы данных - главная причина флака при параллельности. Общий admin, user или order, который несколько тестов меняют одновременно, - зелёный по одному и красный вместе. Фиксированные данные без namespace, сталкивающиеся между worker'ами. И отсутствие уборки, из-за которого база копит мусор и меняет поведение прогонов. Давайте каждому тесту уникальные минимальные данные и убирайте их - и параллельность станет безопасной.
test('редактирует профиль', async ({ page }, testInfo) => {
// Уникальность: индекс worker'а + случайный uuid - не столкнётся ни с кем
const email = `e2e-${testInfo.workerIndex}-${crypto.randomUUID()}@example.test`
const user = await usersApi.create({ email })
// тест работает со своим собственным пользователем
})