Управление системой как пользователь

Что это такое

Управлять системой как пользователь — значит идти тем же путём, что и человек: браузер, командная строка, публичный API. Тест кликает, вводит текст, загружает файлы и отправляет формы, а потом проверяет только то, что пользователь способен наблюдать: подтверждение, новую строку в списке, сообщение. Во внутреннее состояние он не заглядывает и функции напрямую не вызывает, ведь испытываемый объект — сам интерфейс.

Почему это важно

Ценность сквозного теста как раз в том, что о внутреннем устройстве системы он ничего не предполагает, зато ловит отказы сборки, которых не видит больше никто: пропущенную переменную окружения, маршрут, зарегистрированный по неверному пути, cookie, не выставленный для нужного домена, миграцию, не применённую в том порядке, в каком она попадает в развёртывание. Каждый тест стоит секунд, каждый путь — поддержки, поэтому мастерство здесь в том, чтобы выбрать те несколько путей, которые этих затрат стоят, и написать их так, чтобы они пережили изменения интерфейса.

Ключевые идеи

Покрывайте пути, а не экраны

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

путь регистрации:
  открыть лендинг -> создать аккаунт -> подтвердить почту -> попасть в панель

Взаимодействуйте так, как это делает человек

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

await page.getByRole("button", { name: "Place order" }).click();
await expect(page.getByText("Order confirmed")).toBeVisible();

Проверяйте видимый пользователю результат

Проверяйте сообщение о подтверждении, новую строку, URL после перенаправления — а не запрос к базе данных и не внутренний флаг: тест утверждает, что продукт работает, и только видимый пользователю результат подтверждает это утверждение. Когда единственный способ проверки — заглянуть в базу данных, тест незаметно превратился в интеграционный, поэтому и жить он должен на том уровне.

Держите тест коротким и сфокусированным

Один путь на тест, вместе с явно названной подготовкой, которая ему нужна. Длинный тест, который выполняет пять путей подряд, падает по пяти возможным причинам, и первый отказ скрывает остальные. Там, где пути действительно имеют общее начало, вынесите подготовку, а не сцепляйте проверки.

Учитывайте время и порядок

Сквозные тесты выполняются против развёрнутой системы, поэтому встречают настоящую задержку, настоящие часы и настоящие очереди. Всё, что приходит асинхронно, требует от теста ожидания условия, а не фиксированного промежутка. Каждую паузу считайте ошибкой, которая рано или поздно проявится на медленной машине.

await expect(page.getByText("Processing complete")).toBeVisible({ timeout: 15_000 });

Практика

Напишите один сквозной путь

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

Готово, когда: тест проходит на заново запущенной системе и падает, если вы намеренно сломаете функциональность.

Сократите набор путей

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

Готово, когда: каждый тест в файле можно описать пользовательской целью, а те, которые так описать нельзя, удалены.

Частые ошибки

  • Один тест на каждый экран — набор становится медленным и дорогим, покрытие дублируется, но большего риска он так и не закрывает; исправить: тестируйте пути, несущие пользователю ценность.
  • Выбор элементов по внутренним id или CSS-классам — рефакторинг стилей ломает тесты, которые проверяют поведение; исправить: выбирайте по роли, подписи или видимому тексту.
  • Проверка внутреннего состояния — тест доказывает деталь реализации, а не то, что продукт работает; исправить: проверяйте то, что может наблюдать пользователь.
  • Фиксированные паузы вместо ожидания условия — тест падает на медленной машине и проходит на быстрой; исправить: дожидайтесь нужного состояния со щедрым тайм-аутом.
  • Сцепливание нескольких путей в одном тесте — первый отказ маскирует все остальные; исправить: один путь на тест с вынесенной общей подготовкой.

Что читать дальше