Sharing Playwright storageState for Auth Without Losing Test Isolation
Log in once with a Playwright BrowserContext, save storageState, and load a fresh incognito context per test worker to keep auth fast without sharing live state.
26 May 2026, 00:52 UTC

CI suites that log in on every test pay for slow, flaky auth flows and risk rate limits. The useful takeaway is to log in once with a dedicated Playwright BrowserContext, persist storageState, and then give each test worker a fresh incognito context loaded from that file. You get fast authenticated tests without sharing a live context.
Requirements for shared auth without shared state
Tests must start authenticated but remain isolated. No test should mutate cookies or localStorage that another test sees. The login itself is expensive, often involving redirects, CSRF tokens, or third-party identity providers, so it should not run per test.
Playwright supports this via BrowserContext.storageState, a JSON blob that captures cookies, localStorage and sessionStorage for a context. The file can be written once and reloaded into new contexts. The design constraint is: never reuse the same BrowserContext instance across tests.
Smallest suitable design
Create a one-off setup project that runs before the test suite. It launches a persistent context, performs the login using known credentials, then writes storageState to a workspace file.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /auth.setup\.ts/ },
{ name: 'chromium', use: { storageState: 'auth/state.json' }, dependencies: ['setup'] }
]
});
The setup test runs in a single worker, writes the file, and exits. Each test worker then creates its own BrowserContext with storageState loaded. The context is incognito by default, so file system and in-memory state do not leak between tests.
// auth.setup.ts
import { test as setup } from '@playwright/test';
setup('authenticate', async ({ page, context }) => {
await page.goto('https://app.example.com/login');
await page.getByLabel('Email').fill(process.env.TEST_USER);
await page.getByLabel('Password').fill(process.env.TEST_PASS);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('**/dashboard');
await context.storageState({ path: 'auth/state.json' });
});
Run the generator where the workspace is writable, typically in CI before the main suite. Required permissions are write access to the workspace path and read access to secrets for login. Risk: writing the file to a shared artifact store exposes session tokens.
Trust and data boundaries
storageState is untrusted output from the application under test. Treat it as an opaque blob. Do not log its contents, do not print it to stdout, and do not upload it as a build artifact.
Write the file to a secure workspace path scoped to the job, e.g., ${WORKSPACE}/auth/state.json, and restrict read access to the test process. Never reuse the same state across tenants, users, or environments. A state generated for staging must not be used against production.
storageState format and capabilities are version sensitive across Playwright major releases. Pin the Playwright version used to generate and consume the file, and regenerate on upgrades.
Operational checks
Validate the file before the suite runs. Check existence, non-zero size, and recent modification time. A stale file is a signal that the setup step was skipped.
Confirm authentication by creating a context from state and asserting the initial navigation lands on an authenticated page, not a login redirect. The check is a navigation assertion, not a token inspection.
Monitor flakiness for auth failures mid-suite. Sudden redirects to login indicate session expiry or drift between the state and the server.
Failure modes
Session expiration mid-suite causes auth failures. If the server TTL is shorter than suite duration, tests will start failing with redirects to login. Mitigate by regenerating state more frequently or shortening suite runtime.
Sensitive tokens leaking to logs or artifacts. storageState contains cookies that may include session identifiers. Avoid console logging page.contexts() or storageState content. Redact secrets in CI logs.
Parallel workers corrupting the file during generation. Only the setup project should write the file. Do not run setup in parallel with tests, and do not allow multiple workers to write the same path.
Do not share a single BrowserContext across tests. Sharing a live context loses isolation and causes state bleed, flakiness, and false positives. Persistent contexts are heavier than ephemeral contexts and can retain file system side effects.
When to change the design
Change the design if authentication requires multi-step MFA, device binding, or IP affinity. storageState cannot satisfy those constraints.
Per-test user isolation is required when tests must verify permission boundaries. In that case use a persistent context per user or perform per-test login instead of shared state.
If the application invalidates sessions on user agent change or on concurrent logins, shared storageState will be unreliable. Verify behavior with your Playwright version and the application.
Limitations and verification
storageState does not capture browser-level state such as service workers, IndexedDB, or file system downloads. Tests that depend on those will need additional setup.
Practical verification steps: inspect file existence and timestamp in CI, create a context from state and assert navigation to dashboard not login, and force session expiry by clearing server session and observe tests fail to authenticate, confirming expiry detection works.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.