Skip to main content

Shared Auth State Pollutes E2E Tests

Short answer

One storageState.json logged in as admin makes every spec inherit admin powers—or stale cookies from Test A break Test B's viewer flow. Fix with per-spec auth seeds, role fixtures, and runId-scoped users—never a global login in globalSetup without namespacing.

Part of Common E2E testing gotchas.

Symptom

  • Viewer spec passes alone, fails in suite: "Expected button Export to be hidden"
  • Parallel CI: random 401s, wrong tenant, or session overwritten mid-run
  • Logout test leaves next spec unauthenticated
  • Admin actions appear in specs that should run as free tier

Root cause

Auth leaks across specs through shared mutable session state:

  • Single storageState file reused for every project
  • globalSetup logs in once as admin@staging.com
  • test.use({ storageState: 'auth.json' }) at config root
  • Cookie/localStorage not reset between workers

This is a special case of world-state mutation—sessions are world-state too.

Fix: role-scoped auth per spec

1. Seed user + token in Arrange (preferred)

const runId = `auth-${test.info().workerIndex}-${Date.now()}`;

test('viewer cannot export', async ({ page, request }) => {
const { cookieHeader } = await request.post('/api/test/seed-user', {
data: { runId, role: 'viewer' },
}).then(r => r.json());

await page.context().setExtraHTTPHeaders({ Cookie: cookieHeader });
await page.goto('/dashboard');

await expect(page.getByRole('button', { name: 'Export' })).toHaveCount(0);
});

Seed routes create the user and return session material keyed to runId—see seed routes and probe Assert.

2. Playwright projects per role (when UI login is required)

// playwright.config.ts — separate storageState per role, generated in setup
projects: [
{ name: 'viewer', use: { storageState: '.auth/viewer.json' } },
{ name: 'admin', use: { storageState: '.auth/admin.json' } },
],

Generate .auth/*.json in setup projects with unique emails per worker—not one shared admin file. See Playwright authentication.

3. Never mutate shared storageState in tests

// Bad — writes to file other workers read
await page.context().storageState({ path: 'auth.json' });

If you must persist state, use worker-scoped paths: .auth/worker-${workerIndex}-viewer.json.

Auth + parallel CI

Parallel workers amplify session races. Before raising timeouts:

  1. Reproduce with npx playwright test --workers=4
  2. Audit storageState, globalSetup, and shared test users
  3. Add runId to every auth seed route

Full CI patterns: GitHub Actions parallel. RBAC depth: testing RBAC permissions.

Anti-patterns

Anti-patternWhy it failsBetter approach
One admin storageState for suiteViewer tests inherit adminRole fixture per spec/project
Login in beforeAll without isolationOrder-dependent; parallel racesSeed route returns session per runId
Logout in spec without ArrangeNext spec inherits logged-out stateFresh context per test (default)
Reuse Firebase/Auth0 prod credentialsQuota, lockout, cross-tenantEmulator or seed route per worker
Assert UI role badge onlyBadge wrong; API still openProbe protected endpoint with seeded token

TestChimp workflow

/testchimp init scaffolds auth seed routes (POST /api/test/seed-user with role and runId) alongside cart and order fixtures. /testchimp test on PRs repairs specs that assumed a global admin session—agents read markdown scenarios for required roles.

Frequently asked questions

Is storageState bad?

No—it is bad when one file is shared across roles or workers. Generate per-role, per-worker state in setup projects, or prefer seed routes that return session headers per runId.

Should every test log in through the UI?

Only when the login flow itself is under test. For most specs, seed a session via API or set cookies from a test route—faster and more stable than typing credentials every time.

We use Auth0/Okta—how do we avoid shared sessions?

Use test tenants, programmatic user creation, or headed capture for smoke storageState with documented expiry. Never point all workers at one staging user—see Auth0/Okta guides in the auth section.

Logout test breaks the next spec—why?

Shared storageState or a mutated context. Each spec should Arrange its own session; do not rely on a previous spec leaving the browser in a known state.

Can parallel CI share one login if tests are read-only?

Read-only still races on session refresh, MFA, and rate limits. Unique users per worker cost little with seed routes.

How do probes help auth tests?

UI may show the wrong nav for a role while APIs still return 200. Probe protected endpoints with the seeded token—authoritative for RBAC.

Does /testchimp init add auth seed routes?

Yes—it scaffolds seed-user patterns with role and runId alongside other test-only routes so SmartTests stop depending on a single admin storageState file.

globalSetup vs per-test seed—which wins?

Per-test or per-worker seed wins for parallel CI. globalSetup is fine for one-time asset prep—not for mutable session state every spec consumes.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo