Перейти к содержанию

Подмена сетевого ответа

Пустой список может означать разные вещи. Сервер вернул ноль курсов, запрос пользователя не нашёл совпадений или данные не удалось получить. Если интерфейс всегда показывает одну и ту же пустоту, посетитель не понимает, нужно ли изменить запрос или повторить действие позже.

В этом уроке зададим три ответа /api/courses: обычные данные, пустой массив и ошибку 503. Для каждого ответа проверим соответствующее состояние страницы. В lesson-10/expected/lesson.spec.js архива находится полный параметризованный сценарий. Ответы подготовлены как исходные условия; фактических запусков и сетевых отчётов пока нет.

Подмена на границе браузера

В предыдущих уроках уже встречался page.route. Теперь применим его для различения вариантов, а не только для подготовки общего каталога. Перехват устанавливается до page.goto, чтобы начальный fetch обязательно получил нужный ответ. Подмена принадлежит конкретной странице и не изменяет общий сервер.

await page.route('**/api/courses', route => route.fulfill({
  status: 200,
  contentType: 'application/json',
  json: { courses: [] }
}));
await page.goto('/');

Объект ответа сохраняет контракт { courses: [...] }. Пустым является массив внутри объекта, а не сам ответ. Если вместо него вернуть [], программа не найдёт data.courses и попадёт в обработчик ошибки формы ответа. Это отдельный сценарий повреждённого контракта, который нельзя смешивать с нормальным отсутствием записей.

Механизм подмены описан в Mock APIs. В нашей лаборатории сервисные работники заблокированы конфигурацией, чтобы дополнительный слой не перехватывал запрос раньше маршрута страницы. Мы не копируем ответы производственного сервиса и не используем реальные сессии. Данные принадлежат известному учебному набору.

Три договорённости интерфейса

Успешный ответ со всеми данными даёт четыре карточки и текст «Найдено курсов: 4». Пустой ответ со статусом 200 даёт ноль карточек и объяснение «По этому запросу ничего не найдено». Ошибка 503 даёт ноль карточек, статус «Каталог недоступен» и alert «Не удалось загрузить курсы».

Ответ Карточки Обычное объяснение пустоты Ошибка загрузки
200, четыре курса 4 Скрыто Скрыта
200, пустой массив 0 Видно Скрыта
503 0 Скрыто Видна

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

В полном файле каждый объект состояния содержит имя, статус, тело, ожидаемый текст и число карточек. Цикл объявляет три отдельные проверки. Это не три последовательных изменения одной страницы: каждый вариант начинается с нового браузерного контекста и собственной подмены.

Наблюдения после ответа

Основные ожидания имеют такой вид:

await expect(page.getByTestId('results-status')).toHaveText(state.text);
await expect(page.getByRole('list', { name: 'Курсы' })
  .getByRole('listitem')).toHaveCount(state.count);
await expect(page.getByRole('alert').filter({
  hasText: 'Не удалось загрузить курсы'
})).toBeVisible({ visible: state.status === 503 });

Фильтр текста у alert отделяет ошибку загрузки от сообщения формы почты, которое тоже использует роль alert. Для скрытого состояния Playwright не должен найти видимое сообщение с этим текстом. В архиве аналогично проверяется обычное объяснение нулевого результата, которое должно быть видно только при успешном пустом ответе.

Утверждение о статусе выполняется раньше проверки числа. Если список пока пуст из-за загрузки, одно сравнение количества могло бы случайно совпасть с вариантом нуля. Ожидание конкретного текста различает окончание загрузки и нужный результат. Мы используем договорённость приложения как сигнал готовности, сохраняя её связь с данными.

Метод route.fulfill создаёт ответ для браузера. Это не означает, что Node endpoint в действительности умеет возвращать 503 при заданных условиях. Текущий тест исследует клиентскую обработку такого ответа. Для проверки поведения сервера потребуется прямой запрос к нему и определённый механизм изменения его состояния. Эти границы стоит сохранять в названиях и выводах.

HTTP-ошибка и сетевой отказ

Статус 503 — полученный HTTP-ответ. Сетевой отказ, например оборванное соединение, не содержит такого статуса и приводит к отклонению fetch. Наш обработчик в обоих случаях показывает одинаковое сообщение о загрузке, но механизм различается. При необходимости можно добавить отдельный вариант route.abort, явно описав отсутствие ответа.

Ещё один вариант — статус 200 с повреждённым JSON. Тогда ошибка возникает при чтении тела. Текущий код также переходит к сообщению недоступности. Если продукт хочет различать временный сетевой сбой и ошибку данных, сначала нужно сформулировать новые пользовательские тексты и поведение. Добавление теста не создаёт такого различия автоматически.

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

Зачем сохранять маленькую таблицу состояний

Три выбранных варианта относятся к одной задаче загрузки каталога. Они используют одинаковую страницу, но отличаются входом и наблюдаемым эффектом. Такая таблица помогает объяснить, почему нулевая карточка ещё не является достаточным результатом. Читатель видит, какое состояние добавлено и какое обещание защищает сценарий.

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

После этого урока клиентское поведение описано для успеха, пустоты и ошибки. Мы не утверждаем, что выполнили тесты или что настоящий сервис выдержал отказ. Полученный результат — воспроизводимые условия и точные ожидания, которые можно выполнить позднее. Далее проверим сам HTTP-контракт нашего сервера и сопоставим его с тем, что ожидает страница.

Тело ошибки { error: 'unavailable' } не выводится на страницу буквально. Интерфейс использует собственный понятный текст. Поэтому изменение внутреннего кода ошибки не обязательно меняет пользовательское сообщение. Если продукт должен различать несколько кодов, это нужно выразить в обработчике и отдельной таблице ожиданий.

В текущем коде новый ответ получается только при первоначальной загрузке. Повторной кнопки загрузки нет. Следовательно, данный тест не доказывает восстановление после 503. Для такого поведения сначала понадобятся действие повторения и договорённость о сохранении либо очистке прежних карточек.