Playwright tiene una API directa para cookies a través del objeto context, pero localStorage requiere page.evaluate(() => localStorage.getItem('key')) porque no existe un método equivalente al estilo de locators para el almacenamiento. Ambos importan para el testing de autenticación: storageState captura cookies y localStorage juntos en un archivo para que cada test pueda empezar ya con sesión iniciada sin tocar la UI de login. Este artículo cubre lectura, escritura y limpieza de cookies, pre-carga de localStorage, testeo del comportamiento de expiración de cookies, y el patrón de global setup que genera storageState una sola vez y lo reutiliza en toda la suite.

Leer cookies

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

test('el login establece la cookie de sesión', async ({ page, context }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('usuario@ejemplo.com');
  await page.getByLabel('Password').fill('contraseña123');
  await page.getByRole('button', { name: 'Iniciar sesión' }).click();

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

  // Leer todas las cookies de la página actual
  const cookies = await context.cookies();
  const sessionCookie = cookies.find(c => c.name === 'session_token');

  expect(sessionCookie).toBeDefined();
  expect(sessionCookie!.httpOnly).toBe(true); // No puede ser leída por JS: bien
  expect(sessionCookie!.secure).toBe(true);   // Solo se envía por HTTPS
  expect(sessionCookie!.sameSite).toBe('Strict');
});

context.cookies() devuelve todas las cookies de todas las páginas en el contexto actual. Filtra por name, domain o path según necesites.

Establecer cookies

Pre-carga cookies antes de navegar: útil para testear estados autenticados sin pasar por el flujo de login:

test('el usuario autenticado ve el dashboard', async ({ page, context }) => {
  // Establecer la cookie de auth directamente, sin la UI de login
  await context.addCookies([{
    name: 'session_token',
    value: 'valid-test-token-abc123',
    domain: 'localhost',
    path: '/',
    httpOnly: true,
    secure: false, // false para localhost
    sameSite: 'Lax',
  }]);

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

Para un setup de auth de calidad productiva, usa storageState en su lugar: captura cookies Y localStorage en un solo archivo. La inyección manual de cookies es útil cuando necesitas control preciso sobre atributos individuales de una cookie.

Limpiar cookies

// Limpiar todas las cookies del contexto
await context.clearCookies();

// Limpiar una cookie específica (navegando a un endpoint de cierre de sesión
// o usando CDP). La mayoría de las apps tienen un endpoint /logout que limpia
// la cookie de sesión en el servidor.
await page.goto('/logout');

Testear el comportamiento de expiración de cookies

test('la sesión expirada redirige al login', async ({ page, context }) => {
  // Establecer una cookie expirada
  const ayer = new Date(Date.now() - 86400000);
  await context.addCookies([{
    name: 'session_token',
    value: 'token-expirado',
    domain: 'localhost',
    path: '/',
    expires: ayer.getTime() / 1000, // Timestamp Unix en segundos
  }]);

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

Trabajar con localStorage

Playwright no tiene una API directa para localStorage: se accede a través de page.evaluate():

// Leer localStorage
const tema = await page.evaluate(() => localStorage.getItem('theme'));
expect(tema).toBe('dark');

// Establecer localStorage antes de la interacción con la página
await page.evaluate(() => {
  localStorage.setItem('theme', 'dark');
  localStorage.setItem('user_preferences', JSON.stringify({ fontSize: 'large' }));
});

// Limpiar una clave específica
await page.evaluate(() => localStorage.removeItem('theme'));

// Limpiar todo localStorage
await page.evaluate(() => localStorage.clear());

Testear que localStorage persiste preferencias

test('la preferencia de modo oscuro persiste después de recargar', async ({ page }) => {
  await page.goto('/settings');
  await page.getByRole('switch', { name: 'Modo oscuro' }).click();

  // Verificar que se almacenó
  const almacenado = await page.evaluate(() => localStorage.getItem('theme'));
  expect(almacenado).toBe('dark');

  // Recargar la página
  await page.reload();

  // Verificar que la preferencia se restauró desde localStorage
  await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
});

Pre-cargar localStorage

test('el usuario con preferencia de fuente grande ve texto más grande', async ({ page }) => {
  await page.goto('/'); // Navegar primero para establecer el dominio

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

  await page.reload(); // Recargar para que la app lea la preferencia

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

sessionStorage

Misma API que localStorage, diferente alcance: sessionStorage se limpia cuando se cierra la pestaña:

// Leer sessionStorage
const itemsCarrito = await page.evaluate(() => sessionStorage.getItem('cart'));

// Establecer sessionStorage
await page.evaluate(() => {
  sessionStorage.setItem('cart', JSON.stringify([{ id: 1, qty: 2 }]));
});

Guardar y restaurar el estado completo del navegador

Para testing de auth a escala, capturá el estado completo del almacenamiento (cookies + localStorage) en un archivo:

// global-setup.ts — se ejecuta una vez antes de todos los tests
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('testusuario@ejemplo.com');
  await page.getByLabel('Password').fill('contraseña123');
  await page.getByRole('button', { name: 'Iniciar sesión' }).click();
  await page.waitForURL('/dashboard');

  // Guardar cookies + localStorage en archivo
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
  await browser.close();
}

export default globalSetup;

Luego reutilízalo en los tests sin iniciar sesión:

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

Este es el patrón recomendado para suites de tests autenticados: más rápido, más confiable, y no depende de que la UI de login funcione.

→ See also: Manejo de Autenticación en Playwright con storageState (Sin Iniciar Sesión en Cada Test) | Aislamiento de Tests: Por qué Cada Test de Playwright Debe ser sin Estado | Configuración y Limpieza Global en Playwright