# Authentication with storageState — Playwright & Cypress

Source: https://www.skillbyai.com/en/e2e-testing/pw-auth

> Sign in once, reuse everywhere.

## Setup projects and saved state

Logging in through the UI in every test is slow. Playwright can save a browser context's cookies and local storage to a JSON file with `context.storageState({ path })` and load it into new contexts with the `storageState` option. The recommended pattern is a **setup project**: a project that matches `*.setup.ts` files, signs in once and writes the state file; other projects list it in `dependencies` and set `use: { storageState: ... }`. Keep the state file out of version control because it contains session tokens. For several roles, save one file per role and choose per project or per `test.use`.

## Log in once, mock what you must, organise the rest

Real suites reuse authentication, control the network and wrap pages in reusable abstractions.

![Three ideas: storageState and setup projects, page.route mocking, page objects and custom fixtures.](assets/figures/e2e-testing/section-3-map.svg) — Figure 3.1 — Setup project, network routes and fixtures around a test.

## A setup project

Config plus the setup file.

```typescript
// playwright.config.ts (excerpt)
projects: [
  { name: 'setup', testMatch: /.*\.setup\.ts/ },
  {
    name: 'chromium',
    use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' },
    dependencies: ['setup'],
  },
],

// tests/auth.setup.ts
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(process.env.E2E_USER!);
  await page.getByLabel('Password').fill(process.env.E2E_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  await page.context().storageState({ path: authFile });
});
```

## Log in through the API when you can

A setup step can call the login endpoint with the `request` fixture instead of the form; it is faster. Keep one UI login test so the form itself is still covered.

**Quiz:** What does a setup project with dependencies achieve?

- [x] Runs the login once and lets other projects start already authenticated
- [ ] Runs every test twice
- [ ] Disables browser isolation completely
- [ ] Stores passwords in the HTML report

*Answer:* Runs the login once and lets other projects start already authenticated. Saved storageState is loaded into each new context.
