Управление системой как пользователь
Что это такое
Управлять системой как пользователь — значит идти тем же путём, что и человек: браузер, командная строка, публичный 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-классам — рефакторинг стилей ломает тесты, которые проверяют поведение; исправить: выбирайте по роли, подписи или видимому тексту.
- Проверка внутреннего состояния — тест доказывает деталь реализации, а не то, что продукт работает; исправить: проверяйте то, что может наблюдать пользователь.
- Фиксированные паузы вместо ожидания условия — тест падает на медленной машине и проходит на быстрой; исправить: дожидайтесь нужного состояния со щедрым тайм-аутом.
- Сцепливание нескольких путей в одном тесте — первый отказ маскирует все остальные; исправить: один путь на тест с вынесенной общей подготовкой.
Что читать дальше
- Интеграционное тестирование (01-cs-fundamentals/21-integration-testing) — слой, к которому стоит обратиться, когда под вопросом только один стык, поэтому читайте его первым.
- Модульное тестирование (01-cs-fundamentals/10-unit-testing) — где должна жить большая часть покрытия, чтобы этих путей оставалось немного.
- Надёжность сквозных тестов (01-cs-fundamentals/26-end-to-end-testing/02-making-end-to-end-tests-trustworthy) — как уберечь только что написанные пути от нестабильности.