Данные и фикстуры сценария
Открытие страницы и ожидание исходного каталога уже встречаются в нескольких сценариях. Повторение выглядит безобидно, пока данные не меняются. Затем один файл начинает ждать четыре курса, другой — старое число, а третий вообще взаимодействует со страницей до окончания загрузки. Нам нужен один понятный способ подготовить именно ту страницу, на которой начинается действие читателя.
В этом уроке оформим подготовку как фикстуру catalogPage. Она задаёт ответ каталога, открывает страницу и передаёт её сценарию после появления четырёх курсов. Общий сайт остаётся прежним. Полные файлы находятся в архиве, а lesson-05/expected содержит новую проверку; запусков лаборатории пока не было.
Данные как условие задачи
Фикстура — управляемый ресурс сценария с определённой подготовкой и временем жизни. В нашем случае ресурсом является страница с загруженным каталогом. Это не просто функция, сокращающая несколько строк. Она формулирует условие: каждый получатель catalogPage видит одни и те же начальные данные в собственном контексте.
Массив из public/data.js содержит четыре записи. Названия и адреса фиксированы; случайные идентификаторы курсов не генерируются. Если мы проверяем выбор раздела публикации, ожидаем «HTTP и API» и «Статический сайт из Markdown». Случайный набор записей усложнил бы объяснение: отсутствие нужного курса могло бы означать не ошибку фильтра, а другой вход.
Тестовые данные не обязательно должны быть маленькими, но должны соответствовать вопросу. Для проверки двух разделов наш набор достаточен. Он не покрывает сотни страниц, пагинацию и необычные символы заголовков. Эти условия вводят отдельным расширением данных. Увеличение массива без новой задачи делает диагностику сложнее, не добавляя понятного результата.
Собственная страничная фикстура
В common/tests/fixtures.js подготовлен полный модуль:
import { test as base, expect } from '@playwright/test';
import { courses } from '../public/data.js';
export const test = base.extend({
catalogPage: async ({ page }, use) => {
await page.route('**/api/courses', route => route.fulfill({
status: 200, contentType: 'application/json', json: { courses }
}));
await page.goto('/');
await expect(page.getByTestId('results-status'))
.toHaveText('Найдено курсов: 4');
await use(page);
}
});
export { expect };
Встроенная фикстура page остаётся зависимостью нашей фикстуры. Она получает новый контекст для сценария. До вызова use выполняется подготовка: устанавливается маршрут, открывается страница и проверяется исходное состояние. Вызов use(page) передаёт ресурс тесту и ожидает завершения его работы. Здесь нет отдельной очистки, потому что встроенный жизненный цикл страницы и её контекста уже управляется runner.
Маршрут относится к этой странице, а не меняет общий сервер. Другой сценарий получит свой перехват и свои поля формы. Это существенное отличие от записи массива в глобальную базу перед каждой проверкой: два параллельных теста не будут менять данные друг друга. Порядок записей берётся из одного модуля, поэтому список названий можно сравнивать напрямую.
Устройство собственных фикстур подробно описано в Playwright Fixtures. Наш пример использует лишь один небольшой ресурс. Он не требует worker-фикстуры, глобального входа или общего браузерного контекста. Такие механизмы обсуждают, когда появляется отдельная причина разделить область жизни ресурсов.
Сценарий получает готовую страницу
Первый тест импортирует test из нашего модуля и просит catalogPage:
import { test, expect } from './fixtures.js';
test('раздел публикации содержит два курса', async ({ catalogPage }) => {
await catalogPage.getByLabel('Раздел', { exact: true })
.selectOption('publishing');
await catalogPage.getByRole('button', { name: 'Найти', exact: true }).click();
await expect(catalogPage.getByTestId('results-status'))
.toHaveText('Найдено курсов: 2');
await expect(catalogPage.getByRole('list', { name: 'Курсы' })
.getByRole('link')).toHaveText([
'HTTP и API', 'Статический сайт из Markdown'
]);
});
Метод selectOption выбирает значение publishing. Это значение из данных, а не видимая подпись «Публикация». Такая операция подходит для проверки фильтра: мы задаём допустимый выбор стандартного элемента и исследуем результат приложения. В уроке о клавиатуре потребуется другой способ взаимодействия, потому что там результатом станет сам путь управления.
Утверждение с массивом проверяет число найденных ссылок, их текст и порядок. В нашем каталоге исходный порядок сохраняется фильтрацией. Если приложение станет сортировать результаты, изменится договорённость, а вместе с ней ожидаемый массив. Не стоит сортировать фактические значения в тесте только ради того, чтобы скрыть неожиданную перестановку, если порядок важен для читателя.
В полном файле есть также отдельная проверка исходных четырёх названий. Она получает собственную catalogPage, поэтому не наследует выбор раздела от соседнего сценария. Два теста можно описать в одном файле, но их порядок не должен превращаться в обязательную цепочку действий пользователя.
Что именно подменено
Подготовка заменяет лишь /api/courses. Форма сообщения, страницы курсов и запрос учебной роли продолжают работать через локальный сервер. Следовательно, тест фильтра подтверждает обработку известного ответа интерфейсом, а не реализацию endpoint каталога. Для самого endpoint мы позже создадим отдельную HTTP-проверку без подмены.
Такое разделение помогает выбрать причину неудачи. Если интерфейс не показывает четыре подставленные записи, исследуйте отображение или контракт ответа. Если отдельная API-проверка вернула неправильный массив, исследуйте сервер. Одно большое ожидание на всём стеке может обнаружить проблему, но обычно сообщает меньше о границе, на которой она появилась.
Фикстура не должна незаметно выполнять пользовательское действие, которое является предметом самого теста. Если проверка исследует загрузку, нельзя заранее открыть страницу и дождаться готовности: мы потеряем начальный этап. Для такого сценария используется обычная page и собственный маршрут, как в предыдущем уроке. Ресурс выбирают по предпосылкам задачи, а не по стремлению использовать одинаковый шаблон везде.
У этой лаборатории нет общего сброса данных. Каталог неизменяем, сообщения не сохраняются, тема принадлежит браузерному хранилищу, а сессии имеют отдельные идентификаторы. Поэтому подготовка не разрушает чужой работающий сценарий. При настоящей базе потребовался бы отдельный договор об областях данных, который нельзя заменить одним вызовом reset перед каждым тестом.
На выходе получили сценарии, где исходное состояние задано в одном месте, а действия и результат видны в каждом тесте. Следующий шаг использует эту же готовую страницу для перехода к курсу: проверим одновременно назначение ссылки, адрес после нажатия и заголовок конечного документа.
Название ресурса помогает выбрать проверку
Имя catalogPage сообщает больше, чем общий page: каталог уже получил стандартные данные. Сценарий ошибки загрузки должен отказаться от этого ресурса и начать с обычной страницы, иначе фикстура сама подставит успех. Явное название помогает заметить такое противоречие при редакционном чтении.
Внутри теста можно дать ресурсу краткий псевдоним page, как сделано в некоторых следующих уроках. Это меняет только локальное имя переменной; подготовка остаётся той же. Следует объяснять эту замену читателю, чтобы он не решил, что исчезла фикстура или изменилось состояние.
Хорошая подготовка завершается до действия, которое исследует тест. Она не должна выбирать нужный раздел заранее, если результат урока — именно выбор раздела. Иначе код будет проверять последствия скрытого действия, а видимая последовательность потеряет причинную связь с ожидаемыми двумя курсами.