P0 · Chapter 06 / 15
Authentication / Isolation
The 60-second cheat sheet#
storageState is a browser-state snapshot, not a permanent server session. Isolated contexts prevent cookie/local-storage collisions; they do not prevent two tests mutating the same account. Choose account scope using mutation and server-session rules, and include pipeline/shard identity in allocation.
Saved state and authentication choices#
Saved state includes cookies and localStorage; IndexedDB capture is opt-in through the supported indexedDB: true option. sessionStorage is not automatically persisted. Verify your installed Playwright version supports the IndexedDB option. Backend TTL, revocation, password changes or single-session rules can invalidate otherwise intact JSON.
| Approach | Use when | Cost / risk |
|---|---|---|
| UI login every test | Login UX is the requirement | Slow; account/session collision if reused |
| Setup project + state | Many read-only compatible tests | Session may expire mid-suite |
| API login | Approved backend supports equivalent session | Must prove actual application authentication works separately |
| Worker account | Worker tests cannot conflict with each other | Restart/parallel jobs need unique allocation |
| Test account | Destructive or concurrent profile/password mutations | More provisioning/cleanup |
| Supported refresh | Application provides refresh protocol | Refresh rotation/revocation must be handled correctly |
Complete authentication setup#
// tests/auth.setup.ts; complete module, illustrative UI contract.
import { test as setup, expect } from '@playwright/test';
import { mkdirSync } from 'node:fs';
setup('authenticate sandbox account', async ({ page }) => {
const email = process.env.QA_EMAIL;
const password = process.env.QA_PASSWORD;
if (!email || !password) throw new Error('QA_EMAIL and QA_PASSWORD required');
await page.goto('/login');
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Sign in', exact: true }).click();
await expect(page).toHaveURL(/\/dashboard(?:\?|$)/);
await expect(page.getByRole('button', { name: 'Sign out' })).toBeVisible();
mkdirSync('playwright/.auth', { recursive: true });
await page.context().storageState({ path: 'playwright/.auth/user.json' });
});
// playwright.config.ts; complete module. Shared state only for compatible tests.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: { baseURL: process.env.BASE_URL ?? 'http://localhost:3000' },
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/ },
{ name: 'chromium', testMatch: /.*\.spec\.ts/,
use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'] },
],
});
// tests/ui/dashboard.spec.ts; complete module using dependent project state.
import { test, expect } from '@playwright/test';
test('authenticated dashboard is visible', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.getByRole('heading', { name: 'Dashboard', exact: true })).toBeVisible();
});
Add playwright/.auth/ to .gitignore. Never publish state as a CI artifact; traces may also contain cookies/payloads. Credentials come from validated environment variables or a secret manager. This setup is scoped to one run/job and assumes its account is compatible with all dependent tests. Four jobs do not share their local files automatically.
Single-active-session and identity allocation#
Failure: four GitLab shards log in with one account; each new login revokes earlier sessions. All four storage JSON files look valid, yet earlier jobs get 401 or redirect. Fix account/session allocation, not file writes or timeouts. Lease/provision a distinct account per conflicting shard/worker and across overlapping pipelines. Prefer a central lease service with expiry over static modulus indexing into a small pool.
| Identifier | Meaning | Global uniqueness? |
|---|---|---|
parallelIndex |
Stable worker slot across replacement workers in a run | No |
workerIndex |
Unique worker process index within run; replacement gets another | No across jobs |
CI_NODE_INDEX / CI_NODE_TOTAL |
GitLab parallel job/shard partition identity | No across pipelines |
CI_PIPELINE_ID + shard + slot + UUID |
Candidate allocation namespace | Still needs backend ownership/lease enforcement |
Four shards × four workers gives up to sixteen worker slots, and another pipeline doubles contention if accounts are reused. Browser contexts are not extra backend identities. Worker restarts require safe reallocation and cleanup of orphan resources.
Five broken scenarios and fixes#
| Broken situation | Root cause | Correct action |
|---|---|---|
| Rewritten state still redirects | Backend token expired | Fresh login or supported refresh; verify authenticated endpoint |
| Only first shard fails after others start | Single-active-session rule | Unique account per concurrent session scope |
| Password-change test breaks later tests | Shared account revoked | Dedicated destructive-test account; recreate/re-auth appropriately |
| Parallel profile tests overwrite names | Shared server data | Per-test account or distinct owned records |
| Setup saves login form state | Missing/invalid credentials or false success check | Validate env, assert dashboard/authenticated UI before save |
Long runs can outlive TTL even after fresh setup. Provision appropriate test sessions or refresh through the actual protocol; don't conceal product expiry bugs. State rollback cannot undo every server mutation, audit effect or password revocation.
Scope decision and interview response#
Test-scoped for destructive operations; worker-scoped for independent/read-only resources; mixed strategies for shared infrastructure plus per-test users. Avoid one global “authenticated user” fixture for every type of scenario.
30 seconds: “I reuse storageState only when server-session policy and test mutations allow it. It saves cookies/localStorage and optionally IndexedDB, not sessionStorage by default. It does not extend backend validity. For conflicting tests I allocate accounts per test or worker with pipeline/shard uniqueness and refresh authentication through the supported flow.”
90-second senior answer: parallel auth diagnosis
I inspect whether the failure is missing browser state or a server rejection of existing credentials. If saved state works locally but fails when shards start, I compare login timestamps, account identity, 401 responses and backend revocation logs. A single-session policy explains why a later shard invalidates an earlier one. More timeouts cannot fix that. I use a lease or provisioning mechanism whose namespace includes concurrent pipeline, shard and worker scope, and dedicate accounts for password/deactivation tests. State files remain local and ignored by Git; reports must not leak them. A fresh setup verifies a real authenticated screen before saving. I also consider suite duration against TTL and distinguish session-policy defects from automation setup errors.
Memory hook: snapshot ≠ session lifetime; context ≠ account.
Quick practice · reveal answers#
What does storageState contain?
- Cookies/localStorage; IndexedDB opt-in where supported.
- sessionStorage is not automatically persisted.
Pitfall: Assuming it captures every browser or server state.
Why can saved state expire?
- Backend TTL, revocation, password change or single-session policy can invalidate it.
- Re-authenticate/refresh through real protocol.
Pitfall: Rewriting identical expired JSON.
Why do four shards with one account fail?
- Later logins may revoke earlier sessions or writes collide.
- Use independent leased/provisioned accounts across jobs and pipelines.
Pitfall: Treating browser-context isolation as account isolation.
When do tests need unique accounts?
- When mutations, password changes or session policy can interfere.
- Worker sharing is acceptable only with compatible state and ownership.
Pitfall: Sharing a destructive-test account with later read tests.
Official references and related topics#
Optional online further reading; all core study material is included above.