Q2 of 42 · Playwright

How does Playwright's storageState work and when would you use it?

PlaywrightSeniorplaywrightauthenticationstorage-statefixtures

Short answer

Short answer: storageState is a JSON snapshot of cookies and origin storage that Playwright can save after a one-time login and inject into every test's browser context. It's the canonical way to skip UI login in large suites.

Detail

storageState solves the same problem as Cypress's cy.session: log in once, reuse the authenticated state across every test, and skip the UI login. The mechanics are different — Playwright serializes cookies and origins[].localStorage to a JSON file, and any subsequent browser.newContext({ storageState: 'auth.json' }) starts already authenticated.

The standard pattern uses the project setup dependency. You define a setup project that runs first, performs a real login (UI or API), and writes auth.json. Other projects depend on setup and read that file. Playwright won't re-run setup if the file is still valid, so login happens once per CI run, not per test.

For multiple roles, write multiple files — admin.json, viewer.json — and have each test or project pick the one it needs. For more isolation, you can scope storageState to a specific test using test.use({ storageState: '...' }).

Two pitfalls worth raising in an interview: tokens that expire mid-run (regenerate auth.json more often than the token TTL, or refresh them in globalSetup), and SameSite/secure cookie issues when running against a different origin in CI than you originally captured. Keep auth.json out of source control — it's a credential.

// EXAMPLE

playwright.config.ts

import { defineConfig } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'setup',
      testMatch: /global\.setup\.ts/,
    },
    {
      name: 'chromium',
      use: { storageState: 'playwright/.auth/admin.json' },
      dependencies: ['setup'],
    },
  ],
});

// global.setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ request }) => {
  const res = await request.post('/api/login', {
    data: { email: process.env.E2E_EMAIL, password: process.env.E2E_PASSWORD },
  });
  const { token } = await res.json();
  await request.storageState({
    path: 'playwright/.auth/admin.json',
  });
});

// WHAT INTERVIEWERS LOOK FOR

Knowing the setup-project + dependency pattern, awareness that storageState is a credential file, and understanding token expiry as a real failure mode.

// COMMON PITFALL

Logging in once in a globalSetup hook and forgetting that it runs only at the start of the run — so a 4-hour suite with a 1-hour token TTL silently breaks halfway through.