Состояние авторизации в сценариях
У нашего каталога есть скрытая для обычного посетителя ссылка «Редактор». Важно проверить не только её видимость, но и серверный доступ: отсутствие ссылки не мешает человеку самостоятельно открыть адрес. Для сценариев нужны две независимые роли, чтобы состояние одного пользователя не перешло в проверку другого.
В этом уроке создадим собственный контекст для учебного читателя и редактора. Лаборатория имитирует сессию, но не реализует настоящий безопасный вход: endpoint /api/lab-login принимает название роли без пароля. Такой механизм предназначен исключительно для отдельного локального примера. Полные файлы находятся в lesson-12/expected архива, запусков и сохранённых авторизационных файлов нет.
Сессия принадлежит контексту
После POST к учебному входу сервер создаёт случайный идентификатор и записывает роль в память своего процесса. В ответ добавляется HttpOnly-cookie lab_sid. Имя роли не является значением cookie: браузер хранит ссылку на серверную запись. Разные входы получают разные идентификаторы, поэтому читатель и редактор не делят одну изменяемую сессию.
Cookie имеет Path=/ и SameSite=Lax. В лаборатории используется HTTP на loopback, поэтому пример не выдаётся за производственную настройку HTTPS-cookie. Здесь нет пароля, проверки личности, истечения сессии или полного механизма выхода. Нельзя переносить /api/lab-login в публичное приложение: любой вызывающий мог бы выбрать роль редактора.
Наш учебный результат уже: подготовить и изолировать известное состояние для двух сценариев. Производственная аутентификация требует отдельного проектирования. Эти границы позволяют обсуждать работу контекста и cookie, не приписывая упрощённому серверу свойств, которых у него нет.
Подготовка через API контекста
Для каждого значения reader и editor объявляется отдельный тест. Он создаёт контекст с явным учебным адресом:
const context = await browser.newContext({
baseURL: 'http://127.0.0.1:4407'
});
try {
const response = await context.request.post('/api/lab-login', {
data: { role }
});
expect(response.status()).toBe(200);
const state = await context.storageState();
expect(state.cookies.some(cookie => cookie.name === 'lab_sid')).toBe(true);
} finally {
await context.close();
}
Это фрагмент полного теста: проверки страницы ниже выполняются внутри того же try. context.request связан с cookie-хранилищем контекста. Поэтому cookie из ответа входа доступна странице, которую мы создадим в нём. Отдельная встроенная фикстура request имела бы собственный жизненный цикл; смешивать её с контекстом без явной передачи состояния было бы ошибкой.
Вызов storageState() здесь возвращает объект в память. Мы не указываем путь и не создаём файл с авторизационными данными. Это позволяет показать состав состояния без появления настоящего артефакта на диске. В архиве нет .auth-файлов, cookie или секретов, а .gitignore заранее исключает подобную рабочую папку для будущих упражнений.
Механизм сохранения и повторного применения состояния описан в Playwright Authentication. Для данной лаборатории повторный вход дешевле и понятнее: каждая проверка получает свою сессию. При использовании настоящего state-файла понадобились бы отдельные правила срока действия, доступа к нему и соответствия роли конкретному сценарию.
Интерфейс после определения роли
Страница выполняет /api/me и только после ответа решает, показать ли ссылку редактора. В body появляется data-session-ready="true". Это явный сигнал завершения учебной подготовки, необходимый особенно для проверки отсутствующей ссылки.
const page = await context.newPage();
await page.goto('/');
await expect(page.locator('body'))
.toHaveAttribute('data-session-ready', 'true');
await expect(page.getByRole('link', { name: 'Редактор', exact: true }))
.toBeVisible({ visible: role === 'editor' });
Без ожидания готовности проверка читателя могла бы пройти слишком рано. В начальном HTML ссылка скрыта для всех, в том числе будущего редактора. Отсутствие видимости до ответа не доказывает, что роль правильно обработана. Сигнал session-ready отделяет исходную скрытость от конечного решения приложения.
В этом тесте создаётся контекст вручную, поэтому его параметры не следует автоматически считать копией всех настроек встроенной page. Мы явно задаём нужный baseURL и используем стандартные условия для остальных свойств. Если задача потребует одинаковый viewport или locale, их нужно передать либо оформить собственную фикстуру. Неявная надежда на наследование конфигурации могла бы создать непонятные различия.
Серверная граница доступа
Теперь обратимся к защищённому учебному endpoint в том же контексте:
const access = await context.request.get('/api/editor');
expect(access.status()).toBe(role === 'editor' ? 200 : 403);
Редактор получает 200, читатель — 403. Это проверка настоящего локального сервера, не подмена. Нужная cookie отправляется через API контекста. Она связывает роль с серверной записью, поэтому наличие кнопки в браузере не становится источником разрешения.
У страницы /editor/ сервер также проверяет роль. Текущий основной файл исследует endpoint доступа и видимость ссылки; он не заявляет, что пользовательский путь редактора и все его операции проверены. Для создания настоящих материалов понадобились бы отдельные права на чтение и изменение, данные каждого сценария и защита от нежелательных действий.
Если ссылка скрыта, но endpoint даёт читателю 200, визуальная часть сценария не заметит нарушения. Если endpoint корректно даёт 403, но интерфейс показывает читателю кнопку, человек получит лишнее обещание. Поэтому два наблюдения относятся к разным сторонам одной договорённости и оба полезны.
Независимость и завершение
Каждый тест закрывает свой контекст в finally, даже если утверждение завершится ошибкой. Серверные записи живут только в памяти отдельного процесса; остановка лаборатории удалит их. Мы не запускаем глобальный reset между сценариями и не перезаписываем общую сессию известным идентификатором. Поэтому подготовка редактора не меняет уже созданного читателя.
В настоящем приложении независимые cookie ещё не означают независимые изменяемые данные. Два редактора могли бы менять одну статью и конфликтовать. Наш урок пока только читает каталог и проверяет доступ, поэтому общий неизменяемый массив безопасен для этой задачи. В следующей статье рассмотрим параллельные сценарии и границы общих ресурсов подробнее.
Полученный результат — два определённых состояния роли, каждое в собственном контексте, с проверкой интерфейса и серверного решения. Он показывает способ подготовки сценария, а не готовую систему авторизации для сайта. Команды, API-запросы и cookie здесь описаны как будущая учебная работа; реальные сессии ProfessorWeb не использовались.
Условия повторного применения состояния
Если позже мы захотим создать второй контекст из объекта state, cookie продолжит ссылаться на запись того же серверного процесса. Перезапуск сервера удалит карту сессий, и прежнее состояние перестанет задавать роль. Это ожидаемое ограничение лаборатории, а не ошибка сохранения cookie.
Поэтому подготовка роли связана с текущим запуском webServer. Нельзя сохранить объект сегодня и обещать, что он будет работать после любого следующего запуска. Для учебных проверок проще получить новую сессию, чем вводить устойчивое хранилище пользователей только ради демонстрации state.
В примере нет передачи сессий между workers и нет общего файла, который они одновременно перезаписывают. Каждый тест получает результат собственного входа. Если в другом проекте state генерируется отдельным setup-тестом, его зависимости и путь нужно объявить явно, чтобы потребитель не начал работу раньше завершённой подготовки.