# Code Reference — Playwright

_23 pages_

---
# Playwright (/docs/playwright)



# Playwright [#playwright]

E2E browser testing.

Browse the sidebar for every cheat sheet in this section.

## Sections [#sections]

* [Examples](./examples)


---

# Actions (/docs/playwright/actions)



# Actions [#actions]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Actions (`click`, `fill`, `press`, `check`, …) auto-wait for actionability: attached, visible, stable, enabled, and receiving events. Prefer locator actions over raw mouse coordinates.

## 🔧 Core concepts [#-core-concepts]

| Action                       | Notes                            |
| ---------------------------- | -------------------------------- |
| `click` / `dblclick`         | Scrolls into view, waits         |
| `fill`                       | Clears then types                |
| `type` / `pressSequentially` | Key-by-key (rare)                |
| `press`                      | Shortcuts (`Enter`, `Control+A`) |
| `check` / `uncheck`          | Checkboxes                       |
| `selectOption`               | `<select>`                       |
| `hover` / `focus`            | Pointer / focus                  |
| `dragTo`                     | Drag and drop                    |
| `setInputFiles`              | File uploads                     |

Force: `\{ force: true \}` skips checks — use sparingly.

## 💡 Examples [#-examples]

**Forms:**

```ts
await page.getByLabel('Email').fill('ada@example.com');
await page.getByLabel('Password').fill('secret');
await page.getByRole('checkbox', { name: 'Remember me' }).check();
await page.getByLabel('Country').selectOption('GB');
await page.getByRole('button', { name: 'Sign in' }).click();
```

**Keyboard & mouse:**

```ts
await page.getByRole('textbox').press('Control+A');
await page.getByRole('textbox').press('Backspace');
await page.getByText('Item').hover();
await page.getByTestId('handle').dragTo(page.getByTestId('dropzone'));
```

**Files & dialogs:**

```ts
await page.getByLabel('Avatar').setInputFiles('fixtures/avatar.png');
page.once('dialog', (d) => d.accept());
await page.getByRole('button', { name: 'Delete' }).click();
```

**Position / modifiers:**

```ts
await page.getByRole('button').click({ button: 'right' });
await page.getByRole('button').click({ modifiers: ['Shift'] });
await page.getByTestId('canvas').click({ position: { x: 10, y: 20 } });
```

## ⚠️ Pitfalls [#️-pitfalls]

* `page.click('css')` string API is legacy — use locators.
* `fill` on contenteditable may need `pressSequentially`.
* Clicking covered elements — fix overlay or use proper waits, not only `force`.
* Race with animations — wait for stable state / assert visibility first.
* Uploading absolute paths that don't exist on CI agents.

## 🔗 Related [#-related]

* [Locators](/docs/playwright/locators)
* [Assertions](/docs/playwright/assertions)
* [Navigation](/docs/playwright/navigation)
* [Page object](/docs/playwright/page-object)
* [Network](/docs/playwright/network)
* [Fixtures](/docs/playwright/fixtures)


---

# API Testing (/docs/playwright/api-testing)



# API Testing [#api-testing]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`APIRequestContext` (`request` fixture) exercises HTTP APIs without a browser. Combine with UI tests for setup, teardown, and contract checks.

## 🔧 Core concepts [#-core-concepts]

| Piece                           | Role                       |
| ------------------------------- | -------------------------- |
| `request` fixture               | Per-test API context       |
| `playwright.request.newContext` | Custom baseURL/headers     |
| `get/post/put/patch/delete`     | HTTP verbs                 |
| `storageState`                  | Share cookies with browser |

Assertions use response helpers + plain `expect`.

## 💡 Examples [#-examples]

**CRUD smoke:**

```ts
import { test, expect } from '@playwright/test';

test('create user via API', async ({ request }) => {
  const res = await request.post('/api/users', {
    data: { name: 'Ada', email: 'ada@ex.com' },
  });
  expect(res.ok()).toBeTruthy();
  const body = await res.json();
  expect(body).toMatchObject({ name: 'Ada' });

  const get = await request.get(`/api/users/${body.id}`);
  expect(get.status()).toBe(200);
});
```

**Auth header context:**

```ts
test('with token', async ({ playwright }) => {
  const api = await playwright.request.newContext({
    baseURL: 'https://api.example.com',
    extraHTTPHeaders: { Authorization: `Bearer ${process.env.TOKEN}` },
  });
  const res = await api.get('/v1/me');
  expect(res.status()).toBe(200);
  await api.dispose();
});
```

**Seed UI from API:**

```ts
test('ui shows seeded item', async ({ request, page }) => {
  await request.post('/api/items', { data: { title: 'Seeded' } });
  await page.goto('/items');
  await expect(page.getByText('Seeded')).toBeVisible();
});
```

**Config `use.baseURL` applies to `request` when relative.**

## ⚠️ Pitfalls [#️-pitfalls]

* Forgetting `await api.dispose()` on custom contexts.
* Asserting only status codes — check body shape too.
* Sharing mutable server state across parallel API tests.
* Mixing browser cookies and API tokens inconsistently.
* Hitting prod APIs from local suites without guards.

## 🔗 Related [#-related]

* [Network](/docs/playwright/network)
* [Authentication](/docs/playwright/authentication)
* [Fixtures](/docs/playwright/fixtures)
* [Assertions](/docs/playwright/assertions)
* [CI](/docs/playwright/ci)
* [Config](/docs/playwright/config)


---

# Assertions (/docs/playwright/assertions)



# Assertions [#assertions]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Playwright's `expect` auto-retries until the condition passes or timeout. Prefer web-first assertions (`toBeVisible`, `toHaveURL`) over manual polling.

## 🔧 Core concepts [#-core-concepts]

| Assertion                      | Checks              |
| ------------------------------ | ------------------- |
| `toBeVisible` / `toBeHidden`   | Visibility          |
| `toBeEnabled` / `toBeDisabled` | Interactability     |
| `toHaveText` / `toContainText` | Text content        |
| `toHaveValue`                  | Input value         |
| `toHaveAttribute`              | Attribute           |
| `toHaveCount`                  | Locator match count |
| `toHaveURL` / `toHaveTitle`    | Navigation          |
| `toHaveScreenshot`             | Visual baseline     |

Soft assertions: `expect.soft` continues after failure within the test.

## 💡 Examples [#-examples]

**Element state:**

```ts
import { test, expect } from '@playwright/test';

await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled();
await expect(page.getByText('Saved')).toBeVisible();
await expect(page.getByLabel('Name')).toHaveValue('Ada');
await expect(page.getByRole('listitem')).toHaveCount(3);
```

**Page-level:**

```ts
await expect(page).toHaveURL(/\/dashboard/);
await expect(page).toHaveTitle(/Dashboard/);
await expect(page.getByRole('alert')).toContainText('success');
```

**Custom timeout / soft:**

```ts
await expect(page.getByText('Ready')).toBeVisible({ timeout: 15_000 });
await expect.soft(page.getByTestId('a')).toBeVisible();
await expect.soft(page.getByTestId('b')).toBeVisible();
// test fails at end if any soft failed
```

**API response:**

```ts
const res = await request.get('/api/health');
expect(res.ok()).toBeTruthy();
expect(await res.json()).toMatchObject({ status: 'up' });
```

**Negation:**

```ts
await expect(page.getByText('Error')).not.toBeVisible();
```

## ⚠️ Pitfalls [#️-pitfalls]

* Using Node `assert` or Jest `expect` without auto-wait.
* Asserting immediately after click without waiting for navigation/network.
* Overly strict full-string `toHaveText` when UI adds whitespace/icons.
* Screenshot assertions without stable fonts/animations (see visual comparisons).
* Swallowing failures with try/catch around `expect`.

## 🔗 Related [#-related]

* [Locators](/docs/playwright/locators)
* [Actions](/docs/playwright/actions)
* [Visual comparisons](/docs/playwright/visual-comparisons)
* [API testing](/docs/playwright/api-testing)
* [Network](/docs/playwright/network)
* [Tracing](/docs/playwright/tracing)


---

# Authentication (/docs/playwright/authentication)



# Authentication [#authentication]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Reuse logged-in state via `storageState` so suites skip UI login every test. Combine setup projects, global setup, or API login for speed and stability.

## 🔧 Core concepts [#-core-concepts]

| Approach                          | When                        |
| --------------------------------- | --------------------------- |
| UI login once → `storageState`    | Cookie/session apps         |
| API login → inject cookies/tokens | Faster, less UI flake       |
| Setup project dependency          | Official multi-role pattern |
| `httpCredentials`                 | Basic auth                  |

`storageState` saves cookies + localStorage from a context.

## 💡 Examples [#-examples]

**Save state after login:**

```ts
// auth.setup.ts
import { test as setup } from '@playwright/test';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.USER!);
  await page.getByLabel('Password').fill(process.env.PASS!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.waitForURL('**/home');
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});
```

**Config projects:**

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

**API login:**

```ts
const res = await request.post('/api/login', {
  data: { email, password },
});
const { token } = await res.json();
await context.addCookies([{
  name: 'session',
  value: token,
  domain: 'localhost',
  path: '/',
}]);
```

**Per-role states:** `admin.json`, `user.json` — separate projects or fixtures.

## ⚠️ Pitfalls [#️-pitfalls]

* Committing real credentials or auth files with secrets.
* Expired tokens in cached `storageState` on long CI runs.
* Sharing one storage file across parallel workers that mutate session.
* Testing login UX only in setup — keep a dedicated login test.
* Ignoring CSRF / SameSite cookie constraints in API injection.

## 🔗 Related [#-related]

* [Fixtures](/docs/playwright/fixtures)
* [Config](/docs/playwright/config)
* [API testing](/docs/playwright/api-testing)
* [Network](/docs/playwright/network)
* [Parallel](/docs/playwright/parallel)
* [CI](/docs/playwright/ci)


---

# CI (/docs/playwright/ci)



# CI [#ci]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Run Playwright headlessly in CI with browser deps, artifacts (report, trace, video), sharding, and a started app via `webServer` or job services.

## 🔧 Core concepts [#-core-concepts]

| Concern    | Practice                                     |
| ---------- | -------------------------------------------- |
| Browsers   | `npx playwright install --with-deps`         |
| Retries    | `retries: 2` on CI only                      |
| Workers    | Conservative (1–2) on small runners          |
| Artifacts  | Upload `playwright-report/`, `test-results/` |
| Sharding   | `--shard=n/m` across jobs                    |
| Containers | Official Playwright Docker image             |

## 💡 Examples [#-examples]

**GitHub Actions (sketch):**

```yaml
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
  with: { node-version: 22 }
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
  env:
    CI: true
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/
```

**Config CI guards:**

```ts
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: process.env.CI
  ? [['github'], ['html'], ['list']]
  : [['list'], ['html']],
```

**Docker:**

```shellscript
docker run --rm -v $PWD:/work -w /work mcr.microsoft.com/playwright:v1.49.0-jammy \
  /bin/bash -c "npm ci && npx playwright test"
```

Pin image tag to the same Playwright version as `package.json`.

**Shard matrix:** `strategy.matrix.shard: [1, 2, 3]` + `--shard=$\{\{ matrix.shard \}\}/3`.

## ⚠️ Pitfalls [#️-pitfalls]

* Missing `--with-deps` on Ubuntu → browser launch errors.
* Not uploading artifacts on failure (`if: always()`).
* Snapshot baselines from a different OS than CI.
* Starting the app twice (`webServer` + manual job step).
* Secrets in logs from `DEBUG=pw:api` in CI.

## 🔗 Related [#-related]

* [Install](/docs/playwright/install)
* [Config](/docs/playwright/config)
* [Parallel](/docs/playwright/parallel)
* [Tracing](/docs/playwright/tracing)
* [Visual comparisons](/docs/playwright/visual-comparisons)
* [Authentication](/docs/playwright/authentication)


---

# Codegen (/docs/playwright/codegen)



# Codegen [#codegen]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`playwright codegen` records interactions and emits locator-based scripts. Use it to bootstrap tests, then harden selectors and assertions.

## 🔧 Core concepts [#-core-concepts]

| Flag             | Purpose                 |
| ---------------- | ----------------------- |
| `codegen [url]`  | Open recorder           |
| `-o file`        | Write output file       |
| `--target`       | javascript / python / … |
| `--device`       | Emulate device          |
| `--save-storage` | Capture auth state      |
| `--load-storage` | Start authenticated     |

Inspector also opens via `--debug` or the VS Code extension.

## 💡 Examples [#-examples]

**Record a flow:**

```shellscript
npx playwright codegen https://demo.playwright.dev/todomvc
npx playwright codegen --target=javascript -o tests/todo.spec.ts
npx playwright codegen --device="iPhone 13"
```

**With auth:**

```shellscript
npx playwright codegen --load-storage=playwright/.auth/user.json https://app.local
npx playwright codegen --save-storage=playwright/.auth/user.json
```

**Pick locator in UI mode:**

```shellscript
npx playwright test --ui
```

Use the locator tool to copy `getByRole` suggestions.

**Emitted style (edit after):**

```ts
await page.getByPlaceholder('What needs to be done?').click();
await page.getByPlaceholder('What needs to be done?').fill('Buy milk');
await page.getByPlaceholder('What needs to be done?').press('Enter');
await expect(page.getByText('Buy milk')).toBeVisible();
```

Refactor into page objects and replace fragile generated waits.

## ⚠️ Pitfalls [#️-pitfalls]

* Shipping raw codegen output without assertions or cleanup.
* Generated CSS selectors when roles/labels exist.
* Recording on production with real PII.
* Ignoring hover/timing-sensitive steps that codegen under-specifies.
* Mixing codegen languages with a TS project config.

## 🔗 Related [#-related]

* [Locators](/docs/playwright/locators)
* [Actions](/docs/playwright/actions)
* [Page object](/docs/playwright/page-object)
* [Install](/docs/playwright/install)
* [Authentication](/docs/playwright/authentication)
* [Mobile emulation](/docs/playwright/mobile-emulation)


---

# Config (/docs/playwright/config)



# Config [#config]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`playwright.config.ts` defines projects, timeouts, reporters, base URL, and shared `use` options. One config drives local runs and CI.

## 🔧 Core concepts [#-core-concepts]

| Option           | Purpose                     |
| ---------------- | --------------------------- |
| `testDir`        | Where specs live            |
| `timeout`        | Per-test limit (ms)         |
| `expect.timeout` | Assertion wait              |
| `retries`        | Flake recovery              |
| `workers`        | Parallel processes          |
| `use`            | Shared browser/context opts |
| `projects`       | Browser / device matrix     |
| `reporter`       | list, html, github, etc.    |

## 💡 Examples [#-examples]

**Typical config:**

```ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [['html', { open: 'never' }], ['list']],
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});
```

**Env overrides:**

```shellscript
npx playwright test --config=playwright.config.ts
npx playwright test --project=chromium --workers=4
PWDEBUG=1 npx playwright test
```

**Timeouts:**

```ts
export default defineConfig({
  timeout: 60_000,
  expect: { timeout: 10_000 },
  use: { actionTimeout: 15_000, navigationTimeout: 30_000 },
});
```

## ⚠️ Pitfalls [#️-pitfalls]

* Hardcoding `localhost` without `baseURL` / `webServer` breaks CI.
* Too many workers on shared CI runners causes flakes and OOM.
* `trace: 'on'` for every test balloons artifacts — prefer `on-first-retry`.
* Forgetting `forbidOnly` lets `.only` slip into main.
* Device presets vs custom viewport — pick one strategy per project.

## 🔗 Related [#-related]

* [Install](/docs/playwright/install)
* [Parallel](/docs/playwright/parallel)
* [CI](/docs/playwright/ci)
* [Tracing](/docs/playwright/tracing)
* [Screenshots & video](/docs/playwright/screenshots-video)
* [Mobile emulation](/docs/playwright/mobile-emulation)


---

# Examples (/docs/playwright/examples)



# Examples [#examples]

Playwright notes in **Examples**.


---

# API Mock (/docs/playwright/examples/api-mock)



# API Mock [#api-mock]

*Playwright · Example / how-to*

***

## 📋 Overview [#-overview]

Intercept network calls with `page.route` so UI tests stay fast and deterministic without hitting a real backend.

## 🔧 Core concepts [#-core-concepts]

| Piece           | Role              |
| --------------- | ----------------- |
| `page.route`    | Match URL pattern |
| `route.fulfill` | Return stub JSON  |
| `route.abort`   | Simulate failures |
| Test isolation  | Mock per test     |

## 💡 Examples [#-examples]

**api\_mock.spec.ts:**

```typescript
import { test, expect } from "@playwright/test";

test("renders posts from mocked API", async ({ page }) => {
  await page.route("**/api/posts", async (route) => {
    await route.fulfill({
      status: 200,
      contentType: "application/json",
      body: JSON.stringify([
        { id: 1, title: "Hello" },
        { id: 2, title: "World" },
      ]),
    });
  });

  await page.goto("/posts");
  await expect(page.getByText("Hello")).toBeVisible();
  await expect(page.getByText("World")).toBeVisible();
});

test("shows error when API fails", async ({ page }) => {
  await page.route("**/api/posts", (route) =>
    route.fulfill({
      status: 500,
      contentType: "application/json",
      body: JSON.stringify({ error: "boom" }),
    }),
  );

  await page.goto("/posts");
  await expect(page.getByRole("alert")).toContainText(/failed|error/i);
});
```

## ⚠️ Pitfalls [#️-pitfalls]

* Over-broad globs can mock unrelated requests — tighten the pattern.
* Forgetting `await route.fulfill` leaves the request hanging.
* Keep fixtures small; large snapshots are hard to maintain.

## 🔗 Related [#-related]

* [Login flow](/docs/playwright/examples/login-flow)


---

# Login Flow (/docs/playwright/examples/login-flow)



# Login Flow [#login-flow]

*Playwright · Example / how-to*

***

## 📋 Overview [#-overview]

Automate a login form with Playwright: fill credentials, submit, and assert a post-login URL or UI marker.

## 🔧 Core concepts [#-core-concepts]

| Piece            | Role                       |
| ---------------- | -------------------------- |
| `page.goto`      | Open login page            |
| Locators         | Role/label-based selectors |
| `fill` / `click` | Interact                   |
| Assertions       | `expect` URL or text       |

## 💡 Examples [#-examples]

**login\_flow\.spec.ts:**

```typescript
import { test, expect } from "@playwright/test";

test("user can sign in", async ({ page }) => {
  await page.goto("/login");

  await page.getByLabel("Email").fill("ada@example.com");
  await page.getByLabel("Password").fill("correct-horse-battery");
  await page.getByRole("button", { name: "Sign in" }).click();

  await expect(page).toHaveURL(/\/dashboard$/);
  await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible();
});

test("shows error on bad password", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel("Email").fill("ada@example.com");
  await page.getByLabel("Password").fill("wrong");
  await page.getByRole("button", { name: "Sign in" }).click();

  await expect(page.getByRole("alert")).toContainText(/invalid/i);
  await expect(page).toHaveURL(/\/login/);
});
```

**Reuse auth via storage state (sketch):**

```typescript
await page.context().storageState({ path: "playwright/.auth/user.json" });
```

## ⚠️ Pitfalls [#️-pitfalls]

* Prefer `getByRole` / `getByLabel` over brittle CSS selectors.
* Hard waits (`waitForTimeout`) flake — assert on conditions instead.
* Never commit real passwords; use env vars or test-only accounts.

## 🔗 Related [#-related]

* [API mock](/docs/playwright/examples/api-mock)


---

# Fixtures (/docs/playwright/fixtures)



# Fixtures [#fixtures]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Fixtures inject `page`, `context`, `browser`, `request`, and custom setup into tests. Extend `test` to share auth, DB seeds, or page objects without globals.

## 🔧 Core concepts [#-core-concepts]

| Built-in  | Provides                 |
| --------- | ------------------------ |
| `page`    | Fresh page per test      |
| `context` | Isolated browser context |
| `browser` | Shared browser instance  |
| `request` | APIRequestContext        |
| `baseURL` | From config `use`        |

Custom fixtures use `test.extend` with setup/teardown and optional scope (`test` | `worker`).

## 💡 Examples [#-examples]

**Built-in usage:**

```ts
import { test, expect } from '@playwright/test';

test('uses page fixture', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('heading')).toBeVisible();
});
```

**Custom fixture:**

```ts
import { test as base, expect } from '@playwright/test';

type Fixtures = {
  todoPage: { add: (text: string) => Promise<void> };
};

export const test = base.extend<Fixtures>({
  todoPage: async ({ page }, use) => {
    await page.goto('/todos');
    const todoPage = {
      add: async (text: string) => {
        await page.getByPlaceholder('What needs doing?').fill(text);
        await page.keyboard.press('Enter');
      },
    };
    await use(todoPage);
  },
});

test('add todo', async ({ todoPage, page }) => {
  await todoPage.add('Buy milk');
  await expect(page.getByText('Buy milk')).toBeVisible();
});
```

**Worker-scoped (expensive setup once per worker):**

```ts
export const test = base.extend<{}, { token: string }>({
  token: [async ({}, use) => {
    const token = await fetchToken();
    await use(token);
  }, { scope: 'worker' }],
});
```

## ⚠️ Pitfalls [#️-pitfalls]

* Mutating shared worker state across tests → order-dependent flakes.
* Creating pages outside fixtures without closing them.
* Overusing worker scope for data that must be isolated per test.
* Forgetting to `await use(...)` — teardown never runs.
* Importing bare `@playwright/test` after extending — use your extended `test`.

## 🔗 Related [#-related]

* [Page object](/docs/playwright/page-object)
* [Authentication](/docs/playwright/authentication)
* [Config](/docs/playwright/config)
* [API testing](/docs/playwright/api-testing)
* [Parallel](/docs/playwright/parallel)
* [Assertions](/docs/playwright/assertions)


---

# Glossary (/docs/playwright/glossary)



# Glossary [#glossary]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Alphabetical glossary of core Playwright terms for browser automation, locators, fixtures, and end-to-end testing.

## 🔧 Core concepts [#-core-concepts]

| Term                 | Definition                                                                 |
| -------------------- | -------------------------------------------------------------------------- |
| Action               | An interaction such as click, fill, or press performed on a locator.       |
| Assertion            | A check that verifies page state, often auto-retrying with `expect`.       |
| Auto-waiting         | Playwright’s habit of waiting for elements to be actionable before acting. |
| Browser              | A launched browser type instance such as Chromium, Firefox, or WebKit.     |
| BrowserContext       | An isolated browser session with its own cookies, storage, and cache.      |
| Codegen              | The recorder that generates Playwright scripts from manual interactions.   |
| Expect               | Playwright’s assertion API that retries until timeout.                     |
| Fixture              | Reusable test setup/teardown provided by the test runner (e.g. `page`).    |
| Frame                | A nested browsing context inside a page, such as an iframe.                |
| Headless             | Running a browser without a visible UI window.                             |
| Locator              | A lazy, strict query describing how to find element(s) on a page.          |
| Network interception | Mocking or observing HTTP requests via route handlers.                     |
| Page                 | A single tab/document you navigate and automate.                           |
| Page Object          | A class that encapsulates selectors and actions for a UI area.             |
| Parallelism          | Running tests concurrently across workers for speed.                       |
| Playwright Test      | The built-in test runner (`@playwright/test`) with projects and reporters. |
| Project              | A named configuration slice (browser, device, or dependency set).          |
| Reporter             | A plugin that formats and outputs test results.                            |
| Screenshot           | A captured image of a page or element for debugging or visual checks.      |
| Selector engine      | The matching strategy behind locators (role, text, CSS, XPath, etc.).      |
| Storage state        | Saved cookies/localStorage used to reuse authenticated sessions.           |
| Timeout              | The maximum wait before an action or assertion fails.                      |
| Trace                | A detailed recording of actions, DOM snapshots, and network for debugging. |
| Video                | An optional recording of the test browser session.                         |
| Worker               | A parallel process that executes a subset of tests.                        |

## 💡 Examples [#-examples]

**Locator and assertion:**

```ts
import { test, expect } from "@playwright/test";

test("login", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel("Email").fill("a@b.com");
  await page.getByRole("button", { name: "Sign in" }).click();
  await expect(page.getByText("Dashboard")).toBeVisible();
});
```

**Network mock:**

```ts
await page.route("**/api/user", async (route) => {
  await route.fulfill({
    status: 200,
    body: JSON.stringify({ name: "Ada" }),
  });
});
```

**Page object sketch:**

```ts
class LoginPage {
  constructor(private page: Page) {}
  async login(email: string, password: string) {
    await this.page.getByLabel("Email").fill(email);
    await this.page.getByLabel("Password").fill(password);
    await this.page.getByRole("button", { name: "Sign in" }).click();
  }
}
```

## ⚠️ Pitfalls [#️-pitfalls]

* Confusing **locator** (lazy, re-querying) with a one-shot **ElementHandle**.
* Mixing **page** and **context** — auth/storage lives on the context.
* Treating **CSS selectors** as first choice when **role/text** locators are more stable.
* Equating a **trace** with a **screenshot** — traces capture the full timeline.
* Assuming **auto-waiting** removes the need for good assertions and timeouts.

## 🔗 Related [#-related]

* [locators](/docs/playwright/locators)
* [assertions](/docs/playwright/assertions)
* [fixtures](/docs/playwright/fixtures)
* [page\_object](/docs/playwright/page-object)
* [network](/docs/playwright/network)
* [tracing](/docs/playwright/tracing)


---

# Install (/docs/playwright/install)



# Install [#install]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Playwright is a cross-browser E2E library for Chromium, Firefox, and WebKit. Install the Node package, then download browser binaries with `npx playwright install`.

## 🔧 Core concepts [#-core-concepts]

| Piece              | Role                        |
| ------------------ | --------------------------- |
| `@playwright/test` | Test runner + assertions    |
| `playwright`       | Library-only (no runner)    |
| Browser binaries   | Chromium / Firefox / WebKit |
| System deps        | OS libs for headed/headless |

**Install (npm):**

```shellscript
npm init playwright@latest
# or into existing project:
npm i -D @playwright/test
npx playwright install
npx playwright install --with-deps   # CI / Linux
```

**Python / Java / .NET** also exist; this folder focuses on the JS/TS runner.

## 💡 Examples [#-examples]

**Minimal package scripts:**

```json
{
  "scripts": {
    "test": "playwright test",
    "test:ui": "playwright test --ui",
    "test:headed": "playwright test --headed",
    "report": "playwright show-report"
  }
}
```

**Verify browsers:**

```shellscript
npx playwright --version
npx playwright install chromium
npx playwright install firefox webkit
```

**First test (`tests/example.spec.ts`):**

```ts
import { test, expect } from '@playwright/test';

test('homepage has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});
```

**Run:**

```shellscript
npx playwright test
npx playwright test --project=chromium
npx playwright test tests/example.spec.ts
```

## ⚠️ Pitfalls [#️-pitfalls]

* Installing the npm package without `playwright install` → missing browser binaries.
* Mixing global and local CLI — prefer `npx playwright` from the project.
* Skipping `--with-deps` on Linux CI causes cryptic launch failures.
* Pinning `@playwright/test` without reinstalling browsers after upgrades.
* Using `playwright` alone when you need `@playwright/test` fixtures.

## 🔗 Related [#-related]

* [Config](/docs/playwright/config)
* [Codegen](/docs/playwright/codegen)
* [CI](/docs/playwright/ci)
* [Fixtures](/docs/playwright/fixtures)
* [Parallel](/docs/playwright/parallel)
* [Tracing](/docs/playwright/tracing)


---

# Locators (/docs/playwright/locators)



# Locators [#locators]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Locators find elements lazily and auto-wait until actionable. Prefer user-facing selectors: role, label, text, placeholder — avoid brittle CSS/XPath when possible.

## 🔧 Core concepts [#-core-concepts]

| Locator                       | Use when                          |
| ----------------------------- | --------------------------------- |
| `getByRole`                   | Buttons, links, headings, dialogs |
| `getByLabel`                  | Form controls with labels         |
| `getByPlaceholder`            | Inputs by placeholder             |
| `getByText`                   | Visible text                      |
| `getByTestId`                 | Stable `data-testid`              |
| `getByAltText` / `getByTitle` | Images / titled nodes             |
| `locator('css')`              | Escape hatch                      |

Chain with `filter`, `nth`, `and`, `or`. Locators re-query on each action.

## 💡 Examples [#-examples]

**Preferred patterns:**

```ts
page.getByRole('button', { name: 'Submit' });
page.getByRole('heading', { name: 'Settings', level: 1 });
page.getByLabel('Email');
page.getByPlaceholder('Search…');
page.getByText('Welcome back');
page.getByTestId('user-menu');
```

**Filtering & chaining:**

```ts
const row = page.getByRole('row').filter({ hasText: 'Ada' });
await row.getByRole('button', { name: 'Edit' }).click();

page.locator('ul').filter({ has: page.getByText('Active') });
page.getByRole('listitem').nth(0);
```

**Strict mode:** actions fail if multiple matches. Narrow with filter or `exact: true`:

```ts
page.getByText('Save', { exact: true });
page.getByRole('link', { name: 'Docs', exact: true });
```

**Frame / shadow:**

```ts
page.frameLocator('iframe#editor').getByRole('textbox');
page.locator('my-widget').getByRole('button'); // pierce open shadow
```

## ⚠️ Pitfalls [#️-pitfalls]

* CSS like `.btn-primary:nth-child(3)` breaks on layout changes.
* `getByText` substring matches — use `exact` or role when ambiguous.
* Clicking before visibility — locators auto-wait; don't add blind `waitForTimeout`.
* Ignoring accessible names — fix a11y labels instead of forcing testids everywhere.
* Assuming order of `all()` is stable without sorting assertions.

## 🔗 Related [#-related]

* [Actions](/docs/playwright/actions)
* [Assertions](/docs/playwright/assertions)
* [Page object](/docs/playwright/page-object)
* [Codegen](/docs/playwright/codegen)
* [Fixtures](/docs/playwright/fixtures)
* [Mobile emulation](/docs/playwright/mobile-emulation)


---

# Mobile Emulation (/docs/playwright/mobile-emulation)



# Mobile Emulation [#mobile-emulation]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Emulate mobile viewports, touch, user agents, and device scale via `devices` presets or custom `use` options. Real device clouds are separate — this is browser emulation.

## 🔧 Core concepts [#-core-concepts]

| Option                        | Controls                   |
| ----------------------------- | -------------------------- |
| `devices['iPhone 13']`        | Viewport, UA, touch, scale |
| `viewport`                    | Width / height             |
| `isMobile` / `hasTouch`       | Mobile heuristics / touch  |
| `deviceScaleFactor`           | DPR                        |
| `geolocation` / `permissions` | Location APIs              |
| `colorScheme`                 | light / dark               |

WebKit project ≈ Safari-like; Chromium ≈ Chrome Android-ish with device UA.

## 💡 Examples [#-examples]

**Project matrix:**

```ts
import { defineConfig, devices } from '@playwright/test';

projects: [
  { name: 'Pixel 7', use: { ...devices['Pixel 7'] } },
  { name: 'iPhone 13', use: { ...devices['iPhone 13'] } },
  {
    name: 'iPhone landscape',
    use: {
      ...devices['iPhone 13 landscape'],
    },
  },
],
```

**Custom phone:**

```ts
use: {
  viewport: { width: 390, height: 844 },
  userAgent: 'Mozilla/5.0 ...',
  isMobile: true,
  hasTouch: true,
  deviceScaleFactor: 3,
},
```

**Geolocation:**

```ts
await context.grantPermissions(['geolocation']);
await context.setGeolocation({ latitude: 51.5, longitude: -0.12 });
```

**Codegen on device:**

```shellscript
npx playwright codegen --device="iPhone 13" https://example.com
```

## ⚠️ Pitfalls [#️-pitfalls]

* Emulation ≠ real iOS/Android WebView quirks.
* Touch gestures differ — use `tap` where needed; hover is desktop-only.
* Fixed desktop locators failing on collapsed mobile nav.
* Ignoring safe-area / soft keyboard overlap in screenshots.
* Running all devices on every PR without sharding — slow CI.

## 🔗 Related [#-related]

* [Config](/docs/playwright/config)
* [Locators](/docs/playwright/locators)
* [Screenshots & video](/docs/playwright/screenshots-video)
* [Visual comparisons](/docs/playwright/visual-comparisons)
* [Codegen](/docs/playwright/codegen)
* [Parallel](/docs/playwright/parallel)


---

# Navigation (/docs/playwright/navigation)



# Navigation [#navigation]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`page.goto`, history APIs, and wait helpers synchronize tests with loads, SPA route changes, and downloads. Pair navigation with URL assertions.

## 🔧 Core concepts [#-core-concepts]

| API                         | Role                                        |
| --------------------------- | ------------------------------------------- |
| `page.goto(url)`            | Full navigation                             |
| `page.reload()`             | Refresh                                     |
| `page.goBack` / `goForward` | History                                     |
| `waitForURL`                | SPA / redirect                              |
| `waitForLoadState`          | `load` / `domcontentloaded` / `networkidle` |
| `baseURL`                   | Relative paths in config                    |

`goto` waits for `load` by default (`waitUntil` option). Prefer `domcontentloaded` for SPAs that keep connections open.

## 💡 Examples [#-examples]

**Basic:**

```ts
await page.goto('/');
await page.goto('/settings', { waitUntil: 'domcontentloaded' });
await expect(page).toHaveURL(/\/settings/);
```

**Click that navigates:**

```ts
await Promise.all([
  page.waitForURL('**/dashboard'),
  page.getByRole('link', { name: 'Dashboard' }).click(),
]);
// or simply:
await page.getByRole('link', { name: 'Dashboard' }).click();
await expect(page).toHaveURL(/dashboard/);
```

**Load states:**

```ts
await page.goto('/heavy');
await page.waitForLoadState('networkidle'); // avoid if websockets/polling
```

**Popups / new tabs:**

```ts
const [popup] = await Promise.all([
  page.waitForEvent('popup'),
  page.getByRole('link', { name: 'Open report' }).click(),
]);
await popup.waitForLoadState();
await expect(popup).toHaveTitle(/Report/);
```

**Downloads:**

```ts
const [download] = await Promise.all([
  page.waitForEvent('download'),
  page.getByRole('button', { name: 'Export' }).click(),
]);
await download.saveAs('out/report.csv');
```

## ⚠️ Pitfalls [#️-pitfalls]

* `networkidle` flakes on apps with long-polling or analytics.
* Missing `await` on `goto` — race with next assertion.
* Absolute URLs ignoring `baseURL` in multi-env setups.
* Not handling client-side redirects with `waitForURL` / `toHaveURL`.
* Assuming same-tab navigation when target opens a popup.

## 🔗 Related [#-related]

* [Actions](/docs/playwright/actions)
* [Assertions](/docs/playwright/assertions)
* [Network](/docs/playwright/network)
* [Config](/docs/playwright/config)
* [Authentication](/docs/playwright/authentication)
* [Tracing](/docs/playwright/tracing)


---

# Network (/docs/playwright/network)



# Network [#network]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Intercept, mock, abort, and assert HTTP traffic with `page.route`, `page.waitForResponse`, and HAR. Useful for isolating UI from flaky backends.

## 🔧 Core concepts [#-core-concepts]

| API                                  | Purpose                     |
| ------------------------------------ | --------------------------- |
| `page.route`                         | Intercept matching requests |
| `route.fulfill`                      | Mock response               |
| `route.abort`                        | Block (ads, analytics)      |
| `route.continue`                     | Pass through / modify       |
| `waitForRequest` / `waitForResponse` | Sync on traffic             |
| `page.request`                       | Out-of-band API calls       |

Glob patterns: `**/api/users*`. RegExp also supported.

## 💡 Examples [#-examples]

**Mock JSON:**

```ts
await page.route('**/api/users', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify([{ id: 1, name: 'Ada' }]),
  });
});
await page.goto('/users');
await expect(page.getByText('Ada')).toBeVisible();
```

**Abort & modify:**

```ts
await page.route('**/*.{png,jpg}', (route) => route.abort());
await page.route('**/api/**', async (route) => {
  const headers = { ...route.request().headers(), 'X-Test': '1' };
  await route.continue({ headers });
});
```

**Wait for response:**

```ts
const [response] = await Promise.all([
  page.waitForResponse((r) =>
    r.url().includes('/api/save') && r.status() === 200
  ),
  page.getByRole('button', { name: 'Save' }).click(),
]);
expect(await response.json()).toMatchObject({ ok: true });
```

**HAR replay:**

```ts
await context.routeFromHAR('hars/checkout.har', {
  url: '**/api/**',
  update: false,
});
```

## ⚠️ Pitfalls [#️-pitfalls]

* Registering routes after navigation — set up before `goto`.
* Over-mocking hides contract bugs — balance with real API tests.
* Glob too broad (`**/*`) intercepts assets and slows tests.
* Forgetting `await route.fulfill()` hangs the request.
* CORS / service workers can bypass simple route handlers.

## 🔗 Related [#-related]

* [API testing](/docs/playwright/api-testing)
* [Authentication](/docs/playwright/authentication)
* [Navigation](/docs/playwright/navigation)
* [Assertions](/docs/playwright/assertions)
* [Tracing](/docs/playwright/tracing)
* [CI](/docs/playwright/ci)


---

# Page Object (/docs/playwright/page-object)



# Page Object [#page-object]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Page Object Model (POM) wraps locators and flows in classes/modules so specs stay readable. Prefer composition and fixtures over deep inheritance.

## 🔧 Core concepts [#-core-concepts]

| Piece      | Responsibility                    |
| ---------- | --------------------------------- |
| Page class | Locators + actions for one screen |
| Component  | Reusable widget (nav, dialog)     |
| Fixture    | Construct POM with `page`         |
| Spec       | Arrange / act / assert only       |

Keep assertions in tests (or thin expect helpers), not buried only inside pages.

## 💡 Examples [#-examples]

**Page class:**

```ts
import { type Page, type Locator } from '@playwright/test';

export class LoginPage {
  readonly email: Locator;
  readonly password: Locator;
  readonly submit: Locator;

  constructor(private readonly page: Page) {
    this.email = page.getByLabel('Email');
    this.password = page.getByLabel('Password');
    this.submit = page.getByRole('button', { name: 'Sign in' });
  }

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.email.fill(email);
    await this.password.fill(password);
    await this.submit.click();
  }
}
```

**Fixture wiring:**

```ts
export const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
});

test('signs in', async ({ loginPage, page }) => {
  await loginPage.goto();
  await loginPage.login('ada@ex.com', 'secret');
  await expect(page).toHaveURL(/home/);
});
```

**Component example:**

```ts
export class Nav {
  constructor(private page: Page) {}
  link(name: string) {
    return this.page.getByRole('navigation').getByRole('link', { name });
  }
}
```

## ⚠️ Pitfalls [#️-pitfalls]

* God-objects with every selector in the app.
* Returning raw CSS strings instead of Locators.
* Duplicating waits inside every method — trust locator auto-wait.
* Assertions-only pages that hide failures from the report title.
* Over-abstracting one-off screens used by a single test.

## 🔗 Related [#-related]

* [Fixtures](/docs/playwright/fixtures)
* [Locators](/docs/playwright/locators)
* [Actions](/docs/playwright/actions)
* [Authentication](/docs/playwright/authentication)
* [Assertions](/docs/playwright/assertions)
* [Codegen](/docs/playwright/codegen)


---

# Parallel (/docs/playwright/parallel)



# Parallel [#parallel]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Playwright runs tests in parallel across workers and shards. Isolate state, avoid shared mutable resources, and tune workers for CI hardware.

## 🔧 Core concepts [#-core-concepts]

| Control                               | Effect                          |
| ------------------------------------- | ------------------------------- |
| `fullyParallel`                       | Parallelize tests inside a file |
| `workers`                             | Process count                   |
| `shard`                               | Split suite across machines     |
| `test.describe.configure(\{ mode \})` | `parallel` / `serial`           |
| Project dependencies                  | Setup before dependents         |

Each worker gets its own browser; contexts isolate cookies/storage.

## 💡 Examples [#-examples]

**Config:**

```ts
export default defineConfig({
  fullyParallel: true,
  workers: process.env.CI ? 2 : undefined,
});
```

**Serial describe (order matters):**

```ts
test.describe.configure({ mode: 'serial' });
test.describe('checkout flow', () => {
  test('add to cart', async ({ page }) => { /* ... */ });
  test('pay', async ({ page }) => { /* ... */ });
});
```

**Sharding in CI:**

```shellscript
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
```

**Limit parallelism for a file:**

```ts
test.describe.configure({ mode: 'default' }); // file-level sequential workers
```

**Worker index for unique data:**

```ts
test('unique user', async ({}, testInfo) => {
  const email = `user-${testInfo.parallelIndex}@ex.com`;
});
```

## ⚠️ Pitfalls [#️-pitfalls]

* Shared DB rows / accounts without unique keys → cross-test collisions.
* `workers: 1` everywhere hides local parallelism bugs until CI.
* Serial suites that are unnecessarily long — prefer independent tests.
* Uneven shards when heavy tests cluster in one file.
* Relying on test order outside `serial` mode.

## 🔗 Related [#-related]

* [Config](/docs/playwright/config)
* [CI](/docs/playwright/ci)
* [Fixtures](/docs/playwright/fixtures)
* [Authentication](/docs/playwright/authentication)
* [Tracing](/docs/playwright/tracing)
* [Page object](/docs/playwright/page-object)


---

# Screenshots & Video (/docs/playwright/screenshots-video)



# Screenshots & Video [#screenshots--video]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Capture screenshots and video for debugging and visual checks. Configure globally in `use`, or call APIs ad hoc in tests.

## 🔧 Core concepts [#-core-concepts]

| Option            | Values                                                   |
| ----------------- | -------------------------------------------------------- |
| `screenshot`      | `off` \| `on` \| `only-on-failure`                       |
| `video`           | `off` \| `on` \| `retain-on-failure` \| `on-first-retry` |
| `page.screenshot` | Full page / element / clip                               |
| Trace             | Separate — includes snapshots                            |

Videos are WebM; screenshots PNG by default.

## 💡 Examples [#-examples]

**Config:**

```ts
use: {
  screenshot: 'only-on-failure',
  video: 'retain-on-failure',
},
```

**Manual screenshots:**

```ts
await page.screenshot({ path: 'artifacts/home.png', fullPage: true });
await page.getByTestId('chart').screenshot({ path: 'artifacts/chart.png' });
await page.screenshot({
  path: 'clip.png',
  clip: { x: 0, y: 0, width: 800, height: 600 },
});
```

**Mask sensitive UI:**

```ts
await page.screenshot({
  path: 'masked.png',
  mask: [page.getByTestId('ssn'), page.getByLabel('Card number')],
});
```

**Element + animations:**

```ts
await page.screenshot({
  path: 'stable.png',
  animations: 'disabled',
  caret: 'hide',
});
```

Artifacts land under `test-results/` with HTML report links when using the html reporter.

## ⚠️ Pitfalls [#️-pitfalls]

* `video: 'on'` for all tests fills disks quickly.
* Full-page screenshots of infinite scroll pages are huge/slow.
* Flaky pixels from fonts, DPI, and animations — prefer visual comparison APIs.
* Paths outside `test-results` may not attach to the HTML report.
* Recording video in headed debug sessions without cleanup.

## 🔗 Related [#-related]

* [Visual comparisons](/docs/playwright/visual-comparisons)
* [Tracing](/docs/playwright/tracing)
* [Config](/docs/playwright/config)
* [CI](/docs/playwright/ci)
* [Assertions](/docs/playwright/assertions)
* [Parallel](/docs/playwright/parallel)


---

# Tracing (/docs/playwright/tracing)



# Tracing [#tracing]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

Traces capture DOM snapshots, network, console, and actions for post-mortem debugging. Open with `npx playwright show-trace` or the HTML report.

## 🔧 Core concepts [#-core-concepts]

| Mode                | Behavior            |
| ------------------- | ------------------- |
| `off`               | No traces           |
| `on`                | Every test          |
| `retain-on-failure` | Keep if failed      |
| `on-first-retry`    | Recommended default |

Trace viewer shows timeline, before/after snapshots, source, and network.

## 💡 Examples [#-examples]

**Config (recommended):**

```ts
use: {
  trace: 'on-first-retry',
},
retries: process.env.CI ? 2 : 0,
```

**CLI:**

```shellscript
npx playwright test --trace on
npx playwright show-trace test-results/.../trace.zip
npx playwright show-report
```

**Programmatic:**

```ts
await browser.startTracing(page, { path: 'trace.json', screenshots: true });
// ... actions
await browser.stopTracing();
```

Prefer context tracing via config over low-level Chrome tracing.

**Debug locally:**

```shellscript
PWDEBUG=1 npx playwright test
npx playwright test --debug
npx playwright test --ui
```

UI mode includes time-travel and locator picker; traces complement CI failures.

## ⚠️ Pitfalls [#️-pitfalls]

* `trace: 'on'` in large suites → huge artifacts and slow uploads.
* Not downloading CI artifacts — traces stay on the runner.
* Comparing traces across different browsers without noting engine differences.
* Expecting traces without retries when using `on-first-retry`.
* Zipping/custom paths that the HTML reporter cannot find.

## 🔗 Related [#-related]

* [Config](/docs/playwright/config)
* [Screenshots & video](/docs/playwright/screenshots-video)
* [CI](/docs/playwright/ci)
* [Parallel](/docs/playwright/parallel)
* [Assertions](/docs/playwright/assertions)
* [Network](/docs/playwright/network)


---

# Visual Comparisons (/docs/playwright/visual-comparisons)



# Visual Comparisons [#visual-comparisons]

*Playwright · Reference cheat sheet*

***

## 📋 Overview [#-overview]

`toHaveScreenshot` and `toMatchSnapshot` compare pixels/text against committed baselines. Stabilize fonts, animations, and dynamic data before trusting diffs.

## 🔧 Core concepts [#-core-concepts]

| API                                   | Use                      |
| ------------------------------------- | ------------------------ |
| `expect(page).toHaveScreenshot()`     | Full page / viewport     |
| `expect(locator).toHaveScreenshot()`  | Element                  |
| `maxDiffPixels` / `maxDiffPixelRatio` | Tolerance                |
| `stylePath`                           | Inject CSS for stability |
| Update flag                           | `--update-snapshots`     |

Baselines are platform-specific (OS + browser). Generate on the same CI image you run.

## 💡 Examples [#-examples]

**Page screenshot assert:**

```ts
await expect(page).toHaveScreenshot('home.png', {
  animations: 'disabled',
  caret: 'hide',
  maxDiffPixelRatio: 0.01,
});
```

**Element + mask:**

```ts
await expect(page.getByTestId('hero')).toHaveScreenshot({
  mask: [page.getByTestId('clock'), page.getByTestId('avatar')],
});
```

**Config defaults:**

```ts
expect: {
  toHaveScreenshot: {
    maxDiffPixels: 50,
    animations: 'disabled',
  },
},
```

**Update baselines:**

```shellscript
npx playwright test --update-snapshots
npx playwright test --update-snapshots --project=chromium
```

**Text snapshot:**

```ts
expect(await page.locator('pre').textContent()).toMatchSnapshot('dump.txt');
```

## ⚠️ Pitfalls [#️-pitfalls]

* Generating snapshots on Windows and comparing on Linux CI.
* Unmasked timestamps, ads, or lazy-loaded images.
* Fonts not installed in CI → different glyph metrics.
* Animations/caret blinking causing 1-pixel flakes.
* Committing huge full-site screenshots without masking.

## 🔗 Related [#-related]

* [Screenshots & video](/docs/playwright/screenshots-video)
* [Assertions](/docs/playwright/assertions)
* [CI](/docs/playwright/ci)
* [Config](/docs/playwright/config)
* [Mobile emulation](/docs/playwright/mobile-emulation)
* [Tracing](/docs/playwright/tracing)

