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

Изоляция сценариев

Два сценария могут не конфликтовать в базе, но всё равно зависеть друг от друга через cookie, localStorage или оставленную страницу. В нашей лаборатории кнопка «Тёмная тема» сохраняет выбор в localStorage. После перезагрузки того же контекста тема должна восстановиться, а новый самостоятельный сценарий должен начать со светлого состояния.

В этом уроке оформим оба ожидания и разберём отличие перезагрузки от нового контекста. Полный файл находится в lesson-14/expected/lesson.spec.js архива. Как и остальные материалы партии, он пока не выполнялся; успешные результаты не заявляются.

Хранилище имеет область жизни

LocalStorage принадлежит origin внутри браузерного контекста. Страница лаборатории читает ключ theme при загрузке и ставит класс dark, если значение равно dark. Нажатие кнопки меняет это значение, класс тела и атрибут aria-pressed. Перезагрузка страницы сохраняет хранилище текущего контекста, поэтому выбор должен восстановиться.

Новая встроенная page для другого теста создаётся в собственном контексте. Она не получает localStorage первого сценария, если мы явно не передаём сохранённое состояние. Следовательно, начальная тема второго теста — светлая. Это не конфликт между сохранением настройки и изоляцией: первое обещание действует внутри контекста, второе — между независимыми контекстами.

Модель независимых браузерных окружений описана в Playwright Isolation. Для нашего примера важна граница, а не конкретное расположение файлов браузера. Сценарий не должен знать, как runner технически хранит профиль, чтобы проверить тему внутри своего ресурса.

Сохранение внутри одного сценария

Первый тест получает исходный каталог, проверяет светлую тему, нажимает кнопку и перезагружает страницу:

test('тема сохраняется внутри своего контекста', async ({ catalogPage: page }) => {
  const theme = page.getByRole('button', { name: 'Тёмная тема', exact: true });
  await expect(theme).toHaveAttribute('aria-pressed', 'false');
  await theme.click();
  await page.reload();
  await expect(theme).toHaveAttribute('aria-pressed', 'true');
});

Локатор theme после перезагрузки снова описывает элемент нового документа. Он не хранит прежний DOM-узел. Это позволяет использовать то же смысловое правило поиска. Утверждение ждёт нужного атрибута после загрузки модуля, который прочитал localStorage и применил состояние.

Мы проверяем атрибут управления темой. Он связан с состоянием кнопки в приложении, но сам по себе не измеряет цвета всех элементов. В исходнике класс тела меняется тем же обработчиком, а визуальный результат требует отдельного наблюдения. При необходимости можно дополнить тест проверкой класса, сохраняя ясное различие между состоянием и изображением.

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

Новый тест не наследует выбор

Вторая проверка начинается независимо:

test('другой сценарий начинает со светлой темы', async ({ catalogPage: page }) => {
  await expect(page.getByRole('button', {
    name: 'Тёмная тема', exact: true
  })).toHaveAttribute('aria-pressed', 'false');
});

У неё нет ссылки на ресурс первого теста и нет условия «выполнить после него». Она проверяет собственное начальное состояние. При параллельном запуске оба сценария могут находиться на странице одновременно, но их хранилища различны. При выборочном запуске только второго теста результат должен оставаться таким же.

Почему не очистить localStorage вручную в начале второго теста? В текущей модели это скрыло бы неправильное разделение контекстов. Мы хотим подтвердить, что ресурс новый, а не исправить последствия случайного совместного использования. Для сценария, который намеренно использует старое состояние, очистка могла бы быть частью подготовки, но тогда это нужно объявить явно.

Передача storageState меняет исходные условия. Если state содержит localStorage с тёмной темой, новый контекст ожидаемо начнёт с неё. Такое поведение не является нарушением изоляции: мы сами скопировали состояние. Важно не использовать один файл роли или темы для всех сценариев без понимания, какие данные в нём находятся.

Общая страница как скрытая зависимость

Представим, что страница создаётся один раз в beforeAll, а все тесты продолжают пользоваться ею. Первый меняет тему, второй ожидает светлую, третий открывает курс вместо каталога. Порядок превращается в часть поведения. Отдельный запуск второго теста и запуск всего файла могут дать разные результаты, хотя сами строки проверки не изменились.

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

Изоляция браузера не очищает внешние ресурсы. Общая база, файл экспорта или учётная запись могут оставаться изменёнными независимо от нового контекста. Предыдущий урок показал эту границу. Здесь мы исследуем localStorage и DOM, потому что именно они участвуют в выбранном примере. Вывод не распространяется автоматически на серверные записи.

Порядок и воспроизводимость

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

Тема служит наглядным примером, но тот же принцип относится к незакрытому диалогу, выбранному фильтру, cookie сессии и сохранённому черновику. Начальные условия следует назвать прямо. Фраза «открыть каталог» слишком неопределённа, если каталог автоматически восстанавливает десять прошлых настроек. Фикстура должна либо создать чистое состояние, либо явно передать известное сохранённое.

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

Теперь разделены два результата: сохранение темы внутри одного пути и независимое начало другого. Следующий урок оставит данные и изоляцию неизменными, но добавит средство разбора неудачи. Trace поможет увидеть последовательность будущего выполнения, когда обычное сообщение об ошибке окажется слишком кратким.

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

В нашей последовательности известно начальное aria-pressed=false, затем происходит одно действие и перезагрузка. Такой путь содержит причинную связь. При необходимости подготовить тёмную тему заранее следует задать определённый ключ в новом контексте или выполнить явный шаг, описав его как предпосылку другого сценария. Ожидание результата остаётся связано с этой подготовкой.