Authentication and Storage State Reuse
Playwright's storageState captures every cookie and origin-scoped localStorage entry present in a browser context after a successful login, and saving it to a JSON file lets every subsequent test load that same authenticated state instantly instead of repeating the actual login flow, typing a username, a password, clicking submit, waiting for a redirect, in every single test file.
Cricket analogy: It is like a team keeping a pre-approved squad list from the toss so every subsequent innings just references that same list instantly, instead of re-verifying every player's eligibility from scratch before each over.
Saving Storage State
After performing a login, whether by driving the UI form or by calling the login endpoint directly through the request context, context.storageState({ path: 'auth.json' }) serializes every cookie and origin's localStorage into a JSON file, which can be committed to a fixtures directory for local development or generated fresh at the start of every CI run to avoid depending on stale, possibly-expired session data.
Cricket analogy: It is like a curator taking a detailed soil and moisture reading of the pitch on the morning of the match and writing it into a report, a snapshot that captures the exact state at that moment for reference during the day's play.
// auth.setup.ts — a Playwright setup project that logs in once and saves storage state
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test.user@example.com');
await page.getByLabel('Password').fill('S3curePass!');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByText('Welcome back')).toBeVisible();
await page.context().storageState({ path: authFile });
});
Reusing Storage State with Projects and Fixtures
In modern playwright.config.ts, a dedicated setup project runs the login once and other projects declare dependencies: ['setup'] plus use: { storageState: 'playwright/.auth/user.json' }, so every test in those dependent projects starts each new browser context already authenticated, without any per-test login code at all.
Cricket analogy: It is like a designated opener who bats through the powerplay once to get the team off to a fast start, and every subsequent batter simply walks in already benefiting from that platform, instead of each one needing to rebuild momentum from zero.
Use test.use({ storageState: 'admin.json' }) inside a describe block to run just that subset of tests as a different role, layering role-specific auth on top of a project's default storageState without creating an entirely separate project.
Multiple Roles and Expiring Sessions
Applications with multiple user roles typically save separate storageState files per role, for example admin.json and viewer.json, generated by separate setup tests that log in as each role, and these files need periodic refreshing because session cookies or JWTs can expire, at which point every test depending on that stale file starts failing with the same authentication error, a signature that usually points straight to an outdated storage state rather than a real regression.
Cricket analogy: It is like a squad maintaining separate playing-XI lists for Test matches and T20Is, each needs updating separately as form changes, and if one list goes stale, every selection meeting based on it makes the same outdated call.
storageState files often contain real-looking, sometimes long-lived session tokens. Keep them out of public repositories, add them to .gitignore, and prefer regenerating them fresh on every CI run through a setup project rather than committing long-lived credentials that could be replayed if leaked.
- storageState captures cookies and origin-scoped localStorage after a successful login.
- context.storageState({ path }) serializes that state to a JSON file for reuse.
- A setup project that logs in once, paired with dependencies and use.storageState, avoids per-test logins.
- test.use({ storageState }) inside a describe block layers a different role onto a subset of tests.
- Multiple roles typically need separate storageState files, each refreshed on its own schedule.
- Expired session tokens in a stale storageState file cause many tests to fail with the same auth error.
- Never commit long-lived storageState credentials to a public repo; regenerate them per CI run instead.
Practice what you learned
1. What does Playwright's storageState capture?
2. In a modern playwright.config.ts, how do dependent projects reuse authentication captured by a setup project?
3. Why might many previously-passing tests suddenly all fail with the same authentication error?
4. What is a recommended precaution when handling storageState files in a repository?
Was this page helpful?
You May Also Like
API Testing with the Request Context
Use Playwright's APIRequestContext to make real HTTP calls, assert on responses, and seed data for faster, more focused tests.
Network Interception with page.route()
Learn how Playwright's page.route() and context.route() intercept, inspect, modify, fulfill, and abort network requests before they reach the real server.
Waiting for Network Events
Master page.waitForResponse(), waitForRequest(), networkidle, and traffic listeners to synchronize tests with real network activity.