Skip to content

INTEGRATION TESTING · LESSON 11 OF 24

Storage state and authentication patterns

Reuse browser state while understanding its security and isolation limits.

  • storageState
  • newContext
  • authentication
  • localStorage

Your runnable checkpoint

Extract into a new practice folder. Each download contains tests, configuration, the complete local practice app and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.

Install dependencies and browser binaries using the commands below. The test runner starts the practice server on port 4174. Tests target localhost, never the production EduPoly site.

1. Understand the concepts

storageState captures cookies and local storage so another context can start with saved state. It does not automatically copy sessionStorage. Some applications also need IndexedDB state or a different authentication setup.

The lab uses a clearly labeled demo display name in localStorage. It is not authentication or authorization. This runnable example teaches the storage-state mechanism without asking for a real password.

For a real test environment, authenticate a dedicated test account through the actual login flow, wait for evidence that login completed, then save state in a setup project. Dependent projects can load that file. Never commit real authentication state; it may contain credentials that impersonate an account.

Shared authenticated state is appropriate only when tests will not interfere through server-side changes. Use separate accounts or data scopes for tests that mutate shared state. A fresh browser context does not isolate a shared account’s database records.

2. Build and run

These are complete test files and configuration. The download also includes app/ and server.mjs; preserve their paths. Stop any manually started practice server before running the suite.

Terminal
npm install
npx playwright install chromium
npm run check
npm test

tests/lesson.spec.ts

tests/lesson.spec.ts
import { test, expect } from '@playwright/test';
test('reuse a demo session in a new context', async ({ page, browser, baseURL }) => {
  await page.goto('/');
  await page.getByLabel('Demo name').fill('Practice Learner');
  await page.getByRole('button', { name: 'Save demo session' }).click();
  await expect(page.getByTestId('welcome')).toHaveText('Hello, Practice Learner');
  const state = await page.context().storageState();
  const second = await browser.newContext({ storageState: state });
  try {
    const restored = await second.newPage();
    await restored.goto(baseURL!);
    await expect(restored.getByTestId('welcome')).toHaveText('Hello, Practice Learner');
  } finally { await second.close(); }
});

playwright.config.ts

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 1 : 0,
  workers: process.env.CI ? 2 : undefined,
  timeout: 30000,
  expect: { timeout: 5000 },
  reporter: [['list'], ['html', { open: 'never' }]],
  use: { baseURL: 'http://127.0.0.1:4174', trace: 'retain-on-failure', screenshot: 'only-on-failure' },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
  webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});

package.json

package.json
{
  "name": "edupoly-playwright-storage-state-and-authentication",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "test": "playwright test",
    "test:headed": "playwright test --headed",
    "test:ui": "playwright test --ui",
    "check": "tsc --noEmit",
    "report": "playwright show-report",
    "serve": "node server.mjs"
  },
  "devDependencies": {
    "@playwright/test": "1.56.1",
    "typescript": "5.9.3",
    "@types/node": "22.20.5"
  }
}

tsconfig.json

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": [
      "node"
    ],
    "noEmit": true
  },
  "include": [
    "tests/**/*.ts",
    "pages/**/*.ts",
    "playwright.config.ts"
  ]
}

.gitignore

.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env

3. Trace the code and check the result

  1. The first assertion proves the state-changing action completed.
  2. The in-memory state object avoids writing even demo credentials to disk.
  3. The manually created context has explicit cleanup.

Verify your result

  • The second context displays the saved demo name.
  • The extra context is closed even if an assertion fails.

4. Practice independently

Create a third context without storageState and check its greeting.

Show the explanation

A fresh context should display Hello, guest. Close it in finally. This demonstrates isolation rather than proving real authentication.

When something goes wrong

If a browser executable is missing, run npx playwright install chromium in the project folder. If port 4174 is occupied, stop your manual practice server. Read assertion call logs for mismatched locators or expectations. Use npm run report to inspect failures; do not add fixed sleeps or broad resets to hide them.

Read the related official Playwright documentation ↗