Проверки HTTP API
В предыдущем уроке браузер получал заранее заданные ответы. Это позволило исследовать состояния интерфейса, но ничего не сказало о настоящем /api/courses учебного сервера. Если сервер случайно изменит поле courses на другое имя, все проверки с подменой продолжат получать правильный объект и могут не заметить несовместимость.
Теперь обратимся к HTTP API напрямую через фикстуру request. Проверим список курсов и отклонение недопустимого сообщения. Общие исходники Node-сервера остаются теми же. Полный файл lesson-11/expected/lesson.spec.js доступен в архиве; HTTP-запросы при подготовке материала не выполнялись.
Контракт ответа
Контракт — договорённость о том, что можно ожидать от запроса: статус, тип содержимого, структура и смысл данных. Для каталога GET /api/courses возвращает 200 и JSON-объект с массивом courses. Четыре записи имеют постоянные идентификаторы, названия, разделы, число уроков и относительные адреса.
Проверка начинает с статуса и заголовка, а затем читает тело:
const response = await request.get('/api/courses');
expect(response.status()).toBe(200);
expect(response.headers()['content-type']).toContain('application/json');
const body = await response.json();
expect(body).toEqual({ courses });
expect(body.courses.reduce((total, course) => total + course.lessons, 0))
.toBe(50);
Здесь courses импортируется из известного файла public/data.js. Это строгая проверка нашего учебного набора, а не универсальный тест любого каталога. Сравнение всего объекта обнаружит пропущенное поле, лишнюю запись или изменённое число уроков. Дополнительная сумма связывает данные с понятным результатом: четыре курса содержат 50 уроков.
Проверка суммы сама по себе была бы недостаточна. Сервер мог бы вернуть две записи с общим числом 50 и потерять остальные. Сравнение объекта даёт точный состав, а сумма помогает объяснить значение числовых полей. Она не заменяет структурное ожидание и не является измерением реального объёма ProfessorWeb.
Request без браузерной страницы
Фикстура request принадлежит Playwright Test и использует настройки baseURL для относительного пути. В этом сценарии не требуется page.goto: мы исследуем серверный ответ, а не DOM. Общая конфигурация всё равно поднимает учебный webServer, поэтому endpoint доступен в отдельном процессе на 127.0.0.1:4407.
Работа APIRequestContext описана в API testing. Наш пример использует его для чтения и отправки JSON. Подмена page.route сюда не переносится: браузерного маршрута вообще нет. Это позволяет проверить настоящий код сервера, который в других уроках был отделён от клиентской обработки.
Мы не добавляем ручной запуск сервера перед runner. Два процесса попытались бы занять один порт. Если endpoint недоступен, проблема подготовки должна быть видна явно. При самостоятельном запуске файла Playwright выберет только нужные тесты, но конфигурация сервера останется частью общего жизненного цикла.
Обычное expect(response.status()) сравнивает уже полученное значение. Здесь не нужен повторяющийся локаторный assert: ответ уже завершился. Если сервер вернул 500, ожидание 200 не должно бесконечно ждать, что тот же объект ответа превратится в другой. Повтор HTTP-запроса был бы отдельным действием и мог бы скрыть нестабильность или изменить условия.
Недопустимый ввод на сервере
Форма на странице отклоняет неправильную почту до отправки. Но сервер обязан самостоятельно обработать недопустимый запрос. Во второй проверке обойдём форму и передадим известный неправильный адрес:
const response = await request.post('/api/feedback', {
data: { email: 'не-почта', message: 'Опечатка' }
});
expect(response.status()).toBe(400);
expect(await response.json()).toEqual({ error: 'invalid-feedback' });
Объект data сериализуется как JSON для запроса. Учебный сервер проверяет типы полей, небольшой допустимый шаблон почты и непустое сообщение. Для этого примера ответ 400 обозначает недопустимый ввод. В теле возвращается стабильный код ошибки, который удобнее связывать с логикой, чем произвольный текст внутреннего исключения.
Никаких сообщений этот endpoint не сохраняет. Поэтому тесты с допустимым и недопустимым вводом не меняют общий список обращений и не требуют очистки таблицы. Это выбранная граница лаборатории. В настоящем сервисе создание сообщения было бы изменяемым ресурсом: понадобились бы отдельные данные сценария, управление их временем жизни и проверка сохранения.
Два наших сценария могут выполняться независимо. Первый читает неизменяемый каталог, второй получает отказ без побочного эффекта. Мы не проверяем, что первый создал что-нибудь для второго, и не используем порядок запуска как способ подготовки. Такая структура пригодится, когда позже включим несколько workers.
Строгий набор и более свободный контракт
Сравнение { courses } подходит учебной лаборатории, где сервер обязан вернуть именно четыре известные записи. В живом каталоге содержимое меняется редакторами. Там часто проверяют структуру, обязательные поля, уникальность идентификаторов и допустимые значения, а точный набор задают в изолированной тестовой базе. Выбор зависит от того, является ли содержимое частью исходных условий.
Ослаблять проверку до одного response.ok() без причины не следует. Любой успешный статус не обязательно соответствует договорённости: endpoint мог вернуть 204 без тела, хотя клиент ждёт JSON. Нужное число записей тоже не подтверждает корректные URL или разделы. Точные ожидания должны следовать обещаниям конкретного запроса.
Полезно отдельно различать серверную и клиентскую валидацию. Если сервер возвращает правильный объект, но интерфейс показывает пустоту, ищите обработку JSON и создание карточек. Если сервер поменял контракт, а моки сохранили старую форму ответа, исправляйте согласование границы. Сценарии дают разные сигналы, поэтому названия должны отражать слой наблюдения.
Проверяемый смысл данных
Для нашего каталога можно вычислить ещё два числа: frontend содержит 32 урока, publishing — 18. Добавление таких вычислений имеет смысл, если приложение показывает эти суммы или они входят в договорённость API. Пока интерфейс показывает количество курсов, поэтому основной тест не обещает наличие этих чисел на странице.
Не смешивайте идентификатор записи с номером урока в серии. c001 обозначает курс HTML, а 12 — число уроков внутри него. Поле url указывает страницу курса. При изменении данных важно сохранять эту модель в тексте, моках и сервере, иначе тесты могут сравнивать разные сущности и выглядеть правильными только из-за совпадения числа.
На этом шаге появились прямые проверки серверного контракта без открытия браузерной страницы. Они дополняют, а не заменяют сценарии человека: корректный JSON ещё не гарантирует удобный интерфейс. Далее рассмотрим учебные роли и то, как отдельные браузерные контексты получают собственное состояние сессии, сохраняя независимость проверок.
В нашем сервере ошибка JSON и неправильные поля также приводят к 400, но это разные причины. Основной сценарий отправляет корректный JSON с недопустимым значением почты. Поэтому его неудача не должна объясняться повреждённой сериализацией: вход задан на уровне объекта, а runner готовит тело запроса.
Если нужно исследовать неверный синтаксис JSON, передают отдельное сырое тело и явно задают тип содержимого. Такой вариант защищал бы обработку чтения запроса, а не проверку поля. Разделение позволяет дать каждому тесту понятное имя и не приписывать одному ожидаемому статусу все возможные причины отказа.