Playwright работает с куками напрямую через объект context, но localStorage требует page.evaluate(() => localStorage.getItem('key')): прямого аналога локаторов для хранилища нет. Оба инструмента нужны для тестирования аутентификации. storageState захватывает куки и localStorage в один файл, чтобы каждый тест стартовал уже залогиненным без обращения к форме входа. Эта статья разбирает чтение, запись и очистку куков, предустановку данных в localStorage, проверку истёкших куков и паттерн глобальной подготовки для переиспользования auth-состояния по всему сьюту.

Чтение куков

import { test, expect } from '@playwright/test';

test('login sets session cookie', async ({ page, context }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Log in' }).click();

  await expect(page).toHaveURL('/dashboard');

  // читаем все куки текущей страницы
  const cookies = await context.cookies();
  const sessionCookie = cookies.find(c => c.name === 'session_token');

  expect(sessionCookie).toBeDefined();
  expect(sessionCookie!.httpOnly).toBe(true); // JS не может прочитать — хорошо
  expect(sessionCookie!.secure).toBe(true);   // отправляется только по HTTPS
  expect(sessionCookie!.sameSite).toBe('Strict');
});

context.cookies() возвращает все куки для всех страниц в текущем контексте. Фильтруй по name, domain или path по необходимости.

Установка куков

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

test('authenticated user sees dashboard', async ({ page, context }) => {
  // ставим auth-куку напрямую, минуем форму входа
  await context.addCookies([{
    name: 'session_token',
    value: 'valid-test-token-abc123',
    domain: 'localhost',
    path: '/',
    httpOnly: true,
    secure: false, // false для localhost
    sameSite: 'Lax',
  }]);

  await page.goto('/dashboard');
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

Для продакшн-качества auth-настройки используй storageState: он захватывает куки И localStorage в одном файле. Ручная установка куков нужна когда требуется точный контроль над атрибутами конкретной куки.

Очистка куков

// очищаем все куки контекста
await context.clearCookies();

// очистка конкретной куки — через /logout эндпоинт который чистит куку на сервере
await page.goto('/logout');

Тестирование истёкших куков

test('expired session redirects to login', async ({ page, context }) => {
  // ставим истёкшую куку
  const yesterday = new Date(Date.now() - 86400000);
  await context.addCookies([{
    name: 'session_token',
    value: 'expired-token',
    domain: 'localhost',
    path: '/',
    expires: yesterday.getTime() / 1000, // Unix timestamp в секундах
  }]);

  await page.goto('/dashboard');
  await expect(page).toHaveURL('/login');
});

Работа с localStorage

У Playwright нет прямого API для localStorage: доступ через page.evaluate():

// читаем localStorage
const theme = await page.evaluate(() => localStorage.getItem('theme'));
expect(theme).toBe('dark');

// устанавливаем до взаимодействия с страницей
await page.evaluate(() => {
  localStorage.setItem('theme', 'dark');
  localStorage.setItem('user_preferences', JSON.stringify({ fontSize: 'large' }));
});

// удаляем конкретный ключ
await page.evaluate(() => localStorage.removeItem('theme'));

// полная очистка
await page.evaluate(() => localStorage.clear());

Проверка что localStorage сохраняет настройки

test('dark mode preference persists after refresh', async ({ page }) => {
  await page.goto('/settings');
  await page.getByRole('switch', { name: 'Dark mode' }).click();

  // проверяем что настройка сохранена
  const stored = await page.evaluate(() => localStorage.getItem('theme'));
  expect(stored).toBe('dark');

  // перезагружаем страницу
  await page.reload();

  // проверяем что настройка восстановлена из localStorage
  await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
});

Предустановка данных в localStorage

test('user with large font preference sees larger text', async ({ page }) => {
  await page.goto('/'); // сначала переходим на страницу чтобы установить домен

  await page.evaluate(() => {
    localStorage.setItem('fontSize', 'large');
  });

  await page.reload(); // перезагружаем чтобы приложение прочитало настройку

  const body = await page.locator('body');
  await expect(body).toHaveCSS('font-size', '18px');
});

sessionStorage

То же API что у localStorage, другая область видимости: sessionStorage очищается при закрытии вкладки.

// читаем sessionStorage
const cartItems = await page.evaluate(() => sessionStorage.getItem('cart'));

// устанавливаем sessionStorage
await page.evaluate(() => {
  sessionStorage.setItem('cart', JSON.stringify([{ id: 1, qty: 2 }]));
});

Сохранение и восстановление состояния браузера

Для auth-тестирования в масштабе захватывай всё состояние хранилища (куки + localStorage) в файл:

// global-setup.ts — выполняется один раз перед всеми тестами
import { chromium } from '@playwright/test';

async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  await page.goto('http://localhost:3000/login');
  await page.getByLabel('Email').fill('testuser@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Log in' }).click();
  await page.waitForURL('/dashboard');

  // сохраняем куки + localStorage в файл
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
  await browser.close();
}

export default globalSetup;

Переиспользуй в тестах без повторного входа:

// playwright.config.ts
projects: [{
  name: 'authenticated',
  use: { storageState: 'playwright/.auth/user.json' },
}],

Рекомендуемый паттерн для авторизованных тест-сьютов: быстрее, надёжнее, не зависит от работоспособности формы входа.

→ See also: Авторизация в Playwright через storageState (без логина в каждом тесте) | Изоляция тестов: почему каждый тест Playwright должен быть stateless | Глобальная настройка и очистка в Playwright