DarkRiDDeR12 мин

Проверки пользовательского сценария: ждать состояние, а не секунду

FrontendТестирование

Проблема E2E-проверки, которая после клика ждёт произвольную секунду, в том, что она проверяет не пользовательский сценарий, а удачное совпадение времени. На быстрой машине она может пройти раньше, на занятой — упасть позже, а при реальном дефекте просто задержать сигнал. Цена такого теста не только во флаке: команда перестаёт понимать, что именно должно быть готово после действия.

Нужна короткая замена: тест ждёт не время, а наблюдаемое состояние с понятным владельцем. В этой статье состояние — учебный contract: `editing`, `submitting`, `saved`, `recovery-required`. Fixture не открывает страницу и не запускает Playwright. Она нужна, чтобы отдельно проверить переходы и rollback, прежде чем выбирать locator и писать настоящий browser-тест.

Секунда не является условием готовности

Фраза `wait(1000)` отвечает только на вопрос «прошло ли 1000 миллисекунд». Она не говорит, обработан ли клик, заблокирована ли повторная отправка, принят ли ответ и виден ли пользователю итог. Поэтому после такой паузы assertion часто привязан к косвенному признаку: исчезла кнопка, появился class или закончилась анимация. Это слабая связь с задачей пользователя — отправить изменение и получить объяснимый результат.

Что именно ждёт проверка
ПодходНаблюдениеПроблемаЗамена
Произвольная паузаистёк отрезок временине доказывает переходожидать status с именем состояния
Ожидание selector без смыслаэлемент найденэлемент может быть старым или скрытымпроверить role, имя и test state
Сразу после clickhandler вернулсяподтверждение ещё не принятосначала увидеть `submitting`, затем `saved`
Один happy pathсохранение прошлонет границы на stale reply и double submitпроверить guard, failure и rollback

Сформулируйте контракт страницы

Контракт начинается не с automation API, а с вопроса: что пользователь может увидеть и что означает каждая надпись. После заполнения поля модель возвращает status «Черновик готов к отправке». После submit — «Отправка ожидает подтверждения». Только acknowledgement с ожидаемым request id переводит сценарий в «Изменение сохранено». Если подтверждения модели нет, экран честно показывает alert «Сценарий требует восстановления». У каждого текста есть role и testState; это не CSS-деталь.

Такое имя состояния полезно и без автоматизации. Product reviewer может спросить, допустима ли повторная отправка во время `submitting`; разработчик — где хранится request id; тестировщик — какой факт он ждёт. Когда ответ один, проверка не угадывает внутреннюю задержку. Она ждёт public contract. В реальном интерфейсе можно выбрать другие слова и роли, но менять их следует вместе с тестом и требованиями доступности.

Схема учебного сценария: editing после ввода текста переходит в submitting после submit; acknowledgement с ожидаемым request id ведёт в saved, отсутствие acknowledgement — в recovery-required; повторный submit во время submitting заблокирован.
Рисунок показывает переходы локальной модели, а не браузерный trace или реальный прогон теста.

Проверяемый пример без браузера

В fixture создаются только объекты JavaScript. `submit` намеренно возвращает `transport: not-performed`: так код не делает вид, что отправил HTTP-запрос. Assertion проверяет, что submit назвал наблюдаемое состояние, duplicate submit остановлен, stale acknowledgement не меняет state, а правильное acknowledgement даёт `saved`. Запуск ниже проверяет только эти правила.

import { runUiTestsFixture } from './upgrade-2022-10.mjs';
const report = runUiTestsFixture();
if (!Object.values(report.assertions).every(Boolean)) throw new Error('fixture contract failed');
console.log(report.evidence.planned.observation.testState); // 'submitting'
console.log(report.evidence.accepted.observation.name); // 'Изменение сохранено'

Этот пример не является рецептом locator-ов. Его ценность в более узком свойстве: transition нельзя признать успешным до acknowledgement, а отмена или ошибка не должны незаметно превратиться в `saved`. Когда такое свойство есть у reducer или state machine продукта, рядом с ним можно написать unit test. E2E затем проверит, что реальная страница действительно показывает тот же contract.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. Найдите test с паузой после user action или с retry вокруг всего теста.
  2. Причина. Назовите отсутствующий факт: submit принят, кнопка заблокирована, результат признан или ошибка объяснена.
  3. Проверка contract. Выполните `node web/scripts/upgrade-2022-10.mjs --verify-fixture`. PASS доказывает только локальные assertions.
  4. Действие. Добавьте в страницу один наблюдаемый status/alert с семантическим именем и стабильным смыслом.
  5. Проверка реализации. В отдельном browser-тесте дождитесь этого состояния штатным assertion API; не переносите fixture как «реальный run».
  6. Rollback. Если новый contract мешает сценариям, верните прежний state reducer и тест вместе; не оставляйте временную секунду как запасной оракул.

Граница Playwright и WebDriver

Зафиксированная документация Playwright v1.27.0 описывает assertions, которые повторно проверяют условие до успеха или timeout. Это подтверждает идею ждать condition, но не решает, какое condition правильное для конкретной формы. Датированный Working Draft WebDriver описывает remote-control interface, не бизнес-смысл экрана. Следовательно, ни инструмент, ни протокол не могут назвать за команду «сохранено».

WCAG 2.1 здесь важен другой причиной: видимая зелёная иконка не обязана быть достаточным сообщением состояния. Статья не проводит accessibility-аудит, но требует назвать role и доступное имя до выбора тестового selector. Иначе тест становится единственным потребителем скрытого `data-*` атрибута, а пользователь по-прежнему не получает ясного результата.

Ограничение и следующий шаг

Модель не создаёт DOM, не измеряет timeout, не проверяет aria-дерево, не использует сеть, а её request id выдуман. Она не показывает частоту флаков и не доказывает, что любой `status` корректен для любой операции. Для реальной формы нужны отдельные решения о transport, retry, error copy и доступности. Учебная модель специально не маскирует эти решения.

Следующий шаг — выбрать одну нестабильную проверку и выписать четыре состояния вокруг её клика. Для каждого состояния укажите видимый текст, owner transition и условие выхода. Затем удалите только одну паузу, заменив её assertion на это условие. Сравните изменение на обычном CI, но не объявляйте эффект без сохранённого отчёта и условий прогона.

Историческая граница октября 2022

Использованы immutable commit документации Playwright от 7 октября 2022 года, датированный WebDriver Working Draft от 25 октября 2022 года и WCAG 2.1 Recommendation. Источники задают исторический контекст; всё поведение в примере — локальная модель, не утверждение о конкретной версии браузера или продукта.

Проверяемые источники

  • Playwright: test assertions, commit af0d2936 (7 октября 2022) — неизменяемый исходный документ версии v1.27.0, опубликованной 7 октября 2022 года. Описывает retrying assertion до условия или timeout; статья не запускает Playwright.
  • W3C WebDriver, Working Draft 25 октября 2022 года — датированный Working Draft, доступный в октябре 2022 года. Он задаёт remote-control interface, но не определяет смысл прикладного состояния страницы.
  • W3C WCAG 2.1, Recommendation 5 июня 2018 года — нормативная Recommendation, доступная к октябрю 2022 года. Используется только для границы: видимое состояние нуждается в доступной семантике, а не в одном визуальном эффекте.