# Playwright — User Guide

Playwright is Microsoft's open-source Node.js library for reliable, cross-browser end-to-end testing and browser automation. A single API drives Chromium, Firefox, and WebKit — the engines behind Chrome, Edge, Firefox, and Safari — so tests written once run everywhere. Auto-wait, network interception, mobile emulation, tracing, and code generation come built in.

This guide covers everything you need to install, configure, write tests, automate browsers, and troubleshoot Playwright **v1.60.0-next** on Node.js 18+. For architecture internals, security hardening, deployment patterns, and full configuration reference, see the [Admin Guide](/microsoft/playwright/admin-guide/).

---

## 1. Quick Start

Get a full Playwright project running in under two minutes.

### 1.1 Create a New Project

```bash
npm init playwright@latest
```

The initializer prompts for TypeScript or JavaScript, a tests folder name, GitHub Actions setup, and whether to install browsers. Accept the defaults for the fastest path.

Expected output:

```text
Initializing NPM project (npm init -y)…
Installing Playwright Test (npm install -D @playwright/test)…
Writing playwright.config.ts
Creating tests/example.spec.ts
Downloading browsers…
✔ Success! Created a Playwright Test project
```

### 1.2 Run the Example Test

```bash
npx playwright test
```

Expected output:

```text
Running 3 tests using 2 workers

  ✓ [chromium] › tests/example.spec.ts:3:1 › has title (2.1s)
  ✓ [firefox] › tests/example.spec.ts:3:1 › has title (3.4s)
  ✓ [webkit] › tests/example.spec.ts:3:1 › has title (2.8s)

  3 passed (8.2s)
```

### 1.3 View the HTML Report

```bash
npx playwright show-report
```

A browser tab opens with an interactive report. Drill into any test for traces, screenshots, and step timings.

> [!TIP]
> Run `npx playwright test --ui` to launch **UI Mode** — a visual test runner with live-reload, time-travel debugging, and a watch mode that re-runs tests on file change. It is the best way to author and debug tests.

### 1.4 Generate Tests with Codegen

```bash
npx playwright codegen https://example.com
```

Two windows open: a browser for you to interact with, and an inspector that records every action as test code. Copy the generated code into your test file when done.

### 1.5 Minimal Test File

```typescript
// tests/homepage.spec.ts
import { test, expect } from '@playwright/test';

test('homepage loads correctly', async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveTitle(/Example/);
  await expect(page.locator('h1')).toBeVisible();
});
```

Save this as `tests/homepage.spec.ts` and run `npx playwright test tests/homepage.spec.ts`.

---

## 2. Installation

### 2.1 Requirements

| Requirement | Minimum |
|---|---|
| Node.js | 18 or higher |
| OS | Windows 10+, macOS 12+, Ubuntu 20.04+ (Debian 11+, other distros vary) |
| Disk | ~500 MB per browser |

### 2.2 Install via npm

```bash
npm init -D @playwright/test
```

This installs the `@playwright/test` package. Then install browsers:

```bash
npx playwright install
```

Expected output:

```text
Downloading Chromium 131.0.6778.33 (playwright build v1154)
Downloading Firefox 133.0 (playwright build v1466)
Downloading WebKit 18.2 (playwright build v2104)
```

### 2.3 Install Specific Browsers

```bash
npx playwright install chromium
npx playwright install firefox
npx playwright install webkit
```

### 2.4 Install System Dependencies

On Linux, browsers need system libraries. Install them automatically:

```bash
npx playwright install-deps
```

Or for a specific browser:

```bash
npx playwright install-deps chromium
```

> [!NOTE]
> On CI systems (Ubuntu), always run `npx playwright install --with-deps` to install both browsers and OS-level libraries in one step. This is the recommended CI installation command.

### 2.5 Install Options Reference

| Option | Description |
|---|---|
| `--with-deps` | Install OS dependencies alongside browsers |
| `--dry-run` | Show what would be installed without doing it |
| `--list` | List installed and available browsers |
| `--force` | Force re-download even if already installed |
| `--only-shell` | Install only the shell/launcher, not the full browser |
| `--no-shell` | Skip shell/launcher installation |

### 2.6 Uninstall Browsers

```bash
npx playwright uninstall          # remove browsers for current version
npx playwright uninstall --all    # remove ALL Playwright browsers
```

### 2.7 Docker Installation

For CI environments, use the official Playwright Docker image:

```dockerfile
FROM mcr.microsoft.com/playwright:v1.60.0-noble
WORKDIR /app
COPY . .
RUN npm ci
CMD ["npx", "playwright", "test"]
```

### 2.8 Using Playwright Library (Without Test Runner)

If you only need browser automation without the test framework:

```bash
npm install playwright
```

Or for the minimal core without bundled browsers:

```bash
npm install playwright-core
```

```typescript
import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
```

---

## 3. Configuration

Playwright uses a configuration file — `playwright.config.ts` (or `.js`) — at the project root. Every setting has a sensible default; you only override what you need.

### 3.1 Minimal Configuration

```typescript
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
});
```

### 3.2 Full Working Configuration

```typescript
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : '50%',
  reporter: process.env.CI ? 'dot' : 'list',
  fullyParallel: true,

  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    locale: 'en-US',
    colorScheme: 'light',
    viewport: { width: 1280, height: 720 },
    testIdAttribute: 'data-testid',
    actionTimeout: 10_000,
    navigationTimeout: 30_000,
  },

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
  ],

  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});
```

### 3.3 Configuration Defaults

| Setting | Default |
|---|---|
| `timeout` | 30000 ms |
| `workers` | 50% of CPU cores |
| `testMatch` | `**/*.@(spec\|test).?(c\|m)[jt]s?(x)` |
| `viewport` | 1280 × 720 |
| `locale` | en-US |
| `colorScheme` | light |
| `screenshot` | off |
| `video` | off |
| `trace` | off |
| `testIdAttribute` | data-testid |
| `reporter` | list (dot on CI) |

### 3.4 Projects — Multi-Browser and Multi-Config

Projects let you run the same tests under different configurations:

```typescript
projects: [
  {
    name: 'setup',
    testMatch: /global\.setup\.ts/,
  },
  {
    name: 'chromium',
    use: { ...devices['Desktop Chrome'] },
    dependencies: ['setup'],
  },
  {
    name: 'mobile',
    use: { ...devices['Pixel 5'] },
    dependencies: ['setup'],
  },
],
```

Use `dependencies` to run setup projects (like authentication) before test projects.

### 3.5 Web Server Configuration

Playwright can start your app before tests and shut it down after:

```typescript
webServer: {
  command: 'npm run start',
  url: 'http://localhost:3000',
  reuseExistingServer: !process.env.CI,
  timeout: 120_000,
},
```

Multiple servers:

```typescript
webServer: [
  { command: 'npm run start:api', port: 4000 },
  { command: 'npm run start:web', port: 3000 },
],
```

### 3.6 Environment Variables

| Variable | Purpose |
|---|---|
| `PLAYWRIGHT_TEST_BASE_URL` | Override `baseURL` without changing config |
| `PWDEBUG` | Set to `1` to launch Playwright Inspector for debugging |
| `PWPAUSE` | Pause at end of test execution |
| `CI` | Auto-detected; changes default reporter to `dot` |
| `PLAYWRIGHT_HTML_TITLE` | Custom title for the HTML report |
| `PLAYWRIGHT_DASHBOARD` | Enable dashboard features |

> [!IMPORTANT]
> When `PWDEBUG=1` is set, Playwright launches in headed mode with the Inspector attached, timeouts are disabled, and the test pauses at each `await` for manual stepping. This is the primary interactive debugging workflow.

### 3.7 Reporters

Configure one or more reporters to control test output:

```typescript
reporter: [
  ['list'],
  ['html', { open: 'never' }],
  ['junit', { outputFile: 'results.xml' }],
],
```

| Reporter | Output | Best For |
|---|---|---|
| `list` | Step-by-step in terminal | Local development |
| `line` | Single updating line | Compact terminal output |
| `dot` | Dots for pass/fail | CI (default when `CI` is set) |
| `html` | Interactive HTML report | Post-run analysis |
| `json` | JSON to stdout or file | Programmatic processing |
| `junit` | JUnit XML | CI systems (Jenkins, GitLab) |
| `blob` | Binary blob | Sharding, cross-machine merge |
| `github` | GitHub Actions annotations | GitHub CI |
| `null` | No output | Suppressing output |

### 3.8 Global Setup and Teardown

```typescript
// playwright.config.ts
export default defineConfig({
  globalSetup: require.resolve('./global-setup'),
  globalTeardown: require.resolve('./global-teardown'),
});
```

```typescript
// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';

async function globalSetup(config: FullConfig) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/login');
  await page.fill('#username', 'admin');
  await page.fill('#password', 'password');
  await page.click('button[type="submit"]');
  await page.context().storageState({ path: './auth.json' });
  await browser.close();
}
export default globalSetup;
```

---

## 4. Core Usage

### 4.1 Running Tests

#### Run All Tests

```bash
npx playwright test
```

#### Run a Specific File

```bash
npx playwright test tests/login.spec.ts
```

#### Run Tests Matching a Name

```bash
npx playwright test -g "login"
```

#### Run on a Specific Browser

```bash
npx playwright test --project=chromium
npx playwright test --project=firefox
npx playwright test --project=webkit
```

#### Run in Headed Mode (See the Browser)

```bash
npx playwright test --headed
```

#### Run a Single Test in Debug Mode

```bash
npx playwright test --debug tests/login.spec.ts
```

#### Run with Specific Workers

```bash
npx playwright test --workers=4
npx playwright test --workers=1    # serial execution
```

#### Run Tests with Tags

```bash
npx playwright test --grep @smoke
npx playwright test --grep-invert @slow
```

### 4.2 Test Runner CLI Reference

| Flag | Description |
|---|---|
| `--headed` | Run in headed (visible) mode |
| `--debug` | Launch Inspector, pause on first action |
| `--ui` | Open UI Mode for interactive development |
| `-g, --grep <regex>` | Filter tests by title |
| `--grep-invert <regex>` | Exclude tests by title |
| `--project <name>` | Run only the named project(s) |
| `--workers <count>` | Number of parallel workers |
| `--retries <count>` | Retry failed tests N times |
| `--reporter <name>` | Override reporter |
| `--output <dir>` | Test artifacts directory |
| `--timeout <ms>` | Override test timeout |
| `--max-failures <N>` | Stop after N failures |
| `--shard <current/total>` | Shard tests (e.g., `1/3`) |
| `--repeat-each <N>` | Repeat each test N times |
| `--forbid-only` | Fail if `test.only` exists (CI guard) |
| `--update-snapshots` | Update visual comparison baselines |
| `--pass-with-no-tests` | Don't fail on empty test suite |
| `--last-failed` | Re-run only previously failed tests |
| `-c, --config <file>` | Path to config file |

### 4.3 Writing Tests

#### Basic Test Structure

```typescript
import { test, expect } from '@playwright/test';

test.describe('Feature: Shopping Cart', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/shop');
  });

  test('adds item to cart', async ({ page }) => {
    await page.click('[data-testid="add-to-cart"]');
    await expect(page.locator('.cart-count')).toHaveText('1');
  });

  test('removes item from cart', async ({ page }) => {
    await page.click('[data-testid="add-to-cart"]');
    await page.click('[data-testid="remove-from-cart"]');
    await expect(page.locator('.cart-count')).toHaveText('0');
  });
});
```

#### Test Annotations

```typescript
test('feature behind flag', async ({ page }) => {
  test.skip(process.env.FEATURE_FLAG !== 'true', 'Feature not enabled');
  // ...
});

test('known flaky on webkit', async ({ page, browserName }) => {
  test.fixme(browserName === 'webkit', 'WebKit rendering issue');
});

test.slow('large data import', async ({ page }) => {
  // timeout tripled automatically
});

test.only('debug this test', async ({ page }) => {
  // only this test runs (remove before commit)
});
```

#### Tagging Tests

```typescript
test('login @smoke @auth', async ({ page }) => {
  // ...
});

test('complex report @slow', async ({ page }) => {
  // ...
});
```

Run tagged tests: `npx playwright test --grep @smoke`

### 4.4 Locators

Locators are the preferred way to find elements. They auto-wait and auto-retry.

#### Recommended Locators (Resilient to DOM Changes)

```typescript
// By role (best for accessibility)
page.getByRole('button', { name: 'Submit' });
page.getByRole('heading', { name: 'Welcome' });
page.getByRole('link', { name: 'Sign Up' });
page.getByRole('textbox', { name: 'Email' });
page.getByRole('checkbox', { name: 'Remember me' });

// By label text
page.getByLabel('Username');
page.getByLabel('Password');

// By placeholder
page.getByPlaceholder('Search...');

// By visible text
page.getByText('Welcome back');
page.getByText(/welcome/i);  // regex

// By alt text (images)
page.getByAltText('Company Logo');

// By title attribute
page.getByTitle('Close dialog');

// By test ID (configurable via testIdAttribute)
page.getByTestId('submit-button');
```

#### CSS and XPath (When Needed)

```typescript
page.locator('css=button.primary');
page.locator('#login-form input[type="email"]');
page.locator('xpath=//button[@aria-label="Close"]');
```

#### Locator Filtering and Chaining

```typescript
// Filter by text
page.getByRole('listitem').filter({ hasText: 'Product' });

// Filter by child locator
page.getByRole('listitem').filter({
  has: page.getByRole('button', { name: 'Buy' }),
});

// Nth item
page.getByRole('listitem').nth(2);
page.getByRole('listitem').first();
page.getByRole('listitem').last();
```

### 4.5 Actions

All actions auto-wait for the element to be actionable (visible, enabled, stable).

```typescript
// Click
await page.click('button#submit');
await page.dblclick('.card');
await page.click('button', { button: 'right' });

// Fill form fields (clears first)
await page.fill('#email', '[email removed]');
await page.fill('#password', 's3cure!');

// Type character by character (for autocomplete testing)
await page.type('#search', 'playwright', { delay: 100 });

// Press keyboard keys
await page.press('#search', 'Enter');
await page.keyboard.press('Control+A');

// Select dropdown
await page.selectOption('select#country', 'US');
await page.selectOption('select#colors', ['red', 'blue']);

// Checkbox and radio
await page.check('#agree-terms');
await page.uncheck('#newsletter');

// File upload
await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
await page.setInputFiles('input[type="file"]', ['file1.jpg', 'file2.jpg']);

// Hover
await page.hover('.tooltip-trigger');

// Drag and drop
await page.dragAndDrop('#source', '#target');

// Focus
await page.focus('#input-field');
```

### 4.6 Assertions

Playwright assertions auto-retry until the condition is met or the timeout expires.

```typescript
import { expect } from '@playwright/test';

// Page assertions
await expect(page).toHaveTitle(/Dashboard/);
await expect(page).toHaveURL(/\/dashboard/);

// Visibility
await expect(page.locator('.alert')).toBeVisible();
await expect(page.locator('.spinner')).toBeHidden();
await expect(page.locator('.modal')).not.toBeVisible();

// Text content
await expect(page.locator('h1')).toHaveText('Welcome');
await expect(page.locator('h1')).toContainText('Welc');
await expect(page.locator('.items')).toHaveCount(5);

// Input values
await expect(page.locator('#email')).toHaveValue('[email removed]');
await expect(page.locator('#name')).toBeEmpty();

// Attributes and CSS
await expect(page.locator('button')).toHaveAttribute('disabled', '');
await expect(page.locator('button')).toBeEnabled();
await expect(page.locator('button')).toBeDisabled();
await expect(page.locator('.item')).toHaveClass(/selected/);
await expect(page.locator('.box')).toHaveCSS('color', 'rgb(255, 0, 0)');

// Checked state
await expect(page.locator('#checkbox')).toBeChecked();
await expect(page.locator('#checkbox')).not.toBeChecked();

// Attached to DOM
await expect(page.locator('.dynamic')).toBeAttached();

// Screenshot comparison
await expect(page).toHaveScreenshot('homepage.png');
await expect(page.locator('.chart')).toHaveScreenshot('chart.png', {
  maxDiffPixels: 100,
});
```

> [!TIP]
> Always prefer Playwright's auto-retrying `expect` assertions over manual `if` checks or raw `textContent()` calls. Auto-retrying assertions handle timing naturally and eliminate flakiness from elements that haven't rendered yet.

### 4.7 Navigation

```typescript
// Navigate
await page.goto('https://example.com');
await page.goto('/dashboard');  // uses baseURL from config

// Wait for navigation
await page.waitForURL('**/dashboard');
await page.waitForLoadState('networkidle');

// Back and forward
await page.goBack();
await page.goForward();

// Reload
await page.reload();
```

### 4.8 Fixtures

Fixtures provide isolated resources for each test. Playwright's built-in fixtures give you a fresh `page` and `context` per test.

#### Built-in Test Fixtures

| Fixture | Scope | Description |
|---|---|---|
| `page` | Test | Isolated page instance |
| `context` | Test | Browser context (cookies, storage) |
| `browser` | Worker | Shared browser instance |
| `browserName` | Worker | `chromium`, `firefox`, or `webkit` |
| `playwright` | Worker | Playwright instance |
| `request` | Test | API request context |

#### Custom Fixtures

```typescript
import { test as base, expect } from '@playwright/test';

type MyFixtures = {
  adminPage: Page;
  userPage: Page;
};

const test = base.extend<MyFixtures>({
  adminPage: async ({ browser }, use) => {
    const context = await browser.newContext({
      storageState: './admin-auth.json',
    });
    const page = await context.newPage();
    await use(page);
    await context.close();
  },

  userPage: async ({ browser }, use) => {
    const context = await browser.newContext({
      storageState: './user-auth.json',
    });
    const page = await context.newPage();
    await use(page);
    await context.close();
  },
});

test('admin sees dashboard', async ({ adminPage }) => {
  await adminPage.goto('/admin');
  await expect(adminPage.locator('h1')).toHaveText('Admin Dashboard');
});
```

#### Fixture Configuration via Config

Many fixtures are configurable directly in `playwright.config.ts`:

```typescript
use: {
  acceptDownloads: true,
  bypassCSP: false,
  colorScheme: 'dark',
  geolocation: { latitude: 40.7128, longitude: -74.0060 },
  hasTouch: false,
  httpCredentials: { username: 'admin', password: 'pass' },
  ignoreHTTPSErrors: true,
  isMobile: false,
  javaScriptEnabled: true,
  locale: 'en-US',
  offline: false,
  permissions: ['geolocation'],
  timezoneId: 'America/New_York',
  userAgent: 'Custom UA String',
  viewport: { width: 1920, height: 1080 },
  serviceWorkers: 'block',
  baseURL: 'http://localhost:3000',
  actionTimeout: 15_000,
  navigationTimeout: 30_000,
  testIdAttribute: 'data-testid',
},
```

### 4.9 Page Object Model

Organize tests with page objects for maintainability:

```typescript
// pages/login-page.ts
import { Page, Locator, expect } from '@playwright/test';

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign In' });
    this.errorMessage = page.locator('.error-message');
  }

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async expectError(message: string) {
    await expect(this.errorMessage).toHaveText(message);
  }
}
```

```typescript
// tests/login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from '../pages/login-page';

test('shows error for bad credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('[email removed]', 'wrong');
  await loginPage.expectError('Invalid credentials');
});
```

---

## 5. Common Tasks

### 5.1 Authentication and Storage State

Reuse authentication across tests by saving and loading browser storage state.

#### Save Authentication State (Setup Project)

```typescript
// 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('[email removed]');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Sign In' }).click();

  await page.waitForURL('/dashboard');
  await page.context().storageState({ path: authFile });
});
```

#### Use Saved State in Config

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

> [!NOTE]
> Add `playwright/.auth/` to your `.gitignore` to avoid committing session tokens.

### 5.2 API Testing

Playwright includes a built-in API testing client — no additional packages needed.

```typescript
import { test, expect } from '@playwright/test';

test('API: create and fetch user', async ({ request }) => {
  // POST
  const createResponse = await request.post('/api/users', {
    data: {
      name: 'Jane Doe',
      email: '[email removed]',
    },
  });
  expect(createResponse.ok()).toBeTruthy();
  const user = await createResponse.json();
  expect(user.name).toBe('Jane Doe');

  // GET
  const getResponse = await request.get(`/api/users/${user.id}`);
  expect(getResponse.ok()).toBeTruthy();
  const fetched = await getResponse.json();
  expect(fetched.email).toBe('[email removed]');

  // DELETE
  const deleteResponse = await request.delete(`/api/users/${user.id}`);
  expect(deleteResponse.status()).toBe(204);
});
```

### 5.3 Network Interception and Mocking

#### Mock API Responses

```typescript
test('shows mocked data', async ({ page }) => {
  await page.route('**/api/products', async (route) => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: 1, name: 'Mock Product', price: 9.99 },
      ]),
    });
  });

  await page.goto('/products');
  await expect(page.locator('.product-name')).toHaveText('Mock Product');
});
```

#### Modify Responses

```typescript
test('modify API response', async ({ page }) => {
  await page.route('**/api/user/profile', async (route) => {
    const response = await route.fetch();
    const json = await response.json();
    json.name = 'Modified Name';
    await route.fulfill({ response, body: JSON.stringify(json) });
  });

  await page.goto('/profile');
});
```

#### Block Resources

```typescript
test('block images for speed', async ({ page }) => {
  await page.route('**/*.{png,jpg,jpeg,gif,svg}', (route) => route.abort());
  await page.goto('/gallery');
});
```

#### HAR Recording and Replay

```bash
# Record network traffic
npx playwright open --save-har=traffic.har https://example.com
```

```typescript
test('replay from HAR', async ({ page }) => {
  await page.routeFromHAR('traffic.har', { update: false });
  await page.goto('https://example.com');
});
```

### 5.4 Screenshots and Visual Comparison

#### Capture Screenshots

```typescript
// Full page
await page.screenshot({ path: 'screenshots/full.png', fullPage: true });

// Specific element
await page.locator('.chart').screenshot({ path: 'screenshots/chart.png' });

// Clip a region
await page.screenshot({
  path: 'screenshots/header.png',
  clip: { x: 0, y: 0, width: 1280, height: 200 },
});
```

#### Visual Regression Testing

```typescript
test('homepage visual', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png');
});

test('component visual', async ({ page }) => {
  await page.goto('/components');
  await expect(page.locator('.card')).toHaveScreenshot('card.png', {
    maxDiffPixelRatio: 0.01,
  });
});
```

Update baselines after intentional changes:

```bash
npx playwright test --update-snapshots
```

#### CLI Screenshot

```bash
npx playwright screenshot https://example.com screenshot.png --full-page
npx playwright screenshot https://example.com hero.png --wait-for-selector="h1"
```

### 5.5 Video Recording

Enable video per project in config:

```typescript
use: {
  video: 'retain-on-failure',   // or 'on', 'off', 'on-first-retry'
},
```

Videos are saved to `test-results/` and embedded in the HTML report.

### 5.6 Tracing

Traces capture a complete timeline of actions, DOM snapshots, network requests, and console logs.

#### Enable in Config

```typescript
use: {
  trace: 'on-first-retry',  // or 'on', 'off', 'retain-on-failure'
},
```

#### Capture Manually in a Test

```typescript
test('traced workflow', async ({ page, context }) => {
  await context.tracing.start({ screenshots: true, snapshots: true });

  await page.goto('/checkout');
  await page.fill('#card', '4242424242424242');
  await page.click('#pay');

  await context.tracing.stop({ path: 'trace.zip' });
});
```

#### View a Trace

```bash
npx playwright show-trace trace.zip
```

The Trace Viewer opens in a browser with a timeline, DOM snapshots at each step, network log, and console output. You can step through actions and inspect the page state at any point.

### 5.7 PDF Generation (Chromium Only)

```typescript
const page = await browser.newPage();
await page.goto('https://example.com/report');
await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  printBackground: true,
});
```

CLI shortcut:

```bash
npx playwright pdf https://example.com/report report.pdf --paper-format=A4
```

### 5.8 File Downloads

```typescript
test('download report', async ({ page }) => {
  const downloadPromise = page.waitForEvent('download');
  await page.click('a#download-report');
  const download = await downloadPromise;

  expect(download.suggestedFilename()).toBe('report.csv');
  await download.saveAs('./downloads/report.csv');
});
```

> [!TIP]
> Set `acceptDownloads: true` in config or fixture options to allow downloads. It is `true` by default with `@playwright/test` fixtures.

### 5.9 File Uploads

```typescript
test('upload avatar', async ({ page }) => {
  await page.goto('/settings');

  // Single file
  await page.setInputFiles('input[type="file"]', 'fixtures/avatar.png');

  // Multiple files
  await page.setInputFiles('input[type="file"]', [
    'fixtures/doc1.pdf',
    'fixtures/doc2.pdf',
  ]);

  // Clear selection
  await page.setInputFiles('input[type="file"]', []);
});
```

### 5.10 Handling Dialogs (alert, confirm, prompt)

```typescript
test('handles confirm dialog', async ({ page }) => {
  page.on('dialog', async (dialog) => {
    expect(dialog.type()).toBe('confirm');
    expect(dialog.message()).toBe('Delete this item?');
    await dialog.accept();
  });

  await page.click('#delete-button');
});

test('dismisses prompt', async ({ page }) => {
  page.on('dialog', (dialog) => dialog.dismiss());
  await page.click('#rename-button');
});
```

### 5.11 iFrames

```typescript
test('interact with iframe', async ({ page }) => {
  const frame = page.frameLocator('#payment-iframe');
  await frame.locator('#card-number').fill('4242424242424242');
  await frame.locator('#submit').click();
});
```

### 5.12 Multiple Pages and Tabs

```typescript
test('handle popup', async ({ page }) => {
  const popupPromise = page.waitForEvent('popup');
  await page.click('a[target="_blank"]');
  const popup = await popupPromise;

  await popup.waitForLoadState();
  await expect(popup).toHaveTitle(/New Tab/);
});
```

### 5.13 Mobile Emulation

```typescript
import { devices } from '@playwright/test';

// In config
projects: [
  {
    name: 'mobile-safari',
    use: {
      ...devices['iPhone 13'],
    },
  },
  {
    name: 'tablet',
    use: {
      ...devices['iPad Pro 11'],
    },
  },
],
```

Or per test:

```typescript
test('mobile layout', async ({ browser }) => {
  const context = await browser.newContext({
    ...devices['Pixel 5'],
  });
  const page = await context.newPage();
  await page.goto('/');
  // test mobile-specific behavior
  await context.close();
});
```

### 5.14 Geolocation, Timezone, and Locale

```typescript
// Config-level
use: {
  geolocation: { latitude: 48.8566, longitude: 2.3522 },
  permissions: ['geolocation'],
  timezoneId: 'Europe/Paris',
  locale: 'fr-FR',
},
```

```typescript
// Per test
test('paris location', async ({ browser }) => {
  const context = await browser.newContext({
    geolocation: { latitude: 48.8566, longitude: 2.3522 },
    permissions: ['geolocation'],
    locale: 'fr-FR',
    timezoneId: 'Europe/Paris',
  });
  const page = await context.newPage();
  await page.goto('/store-locator');
  await context.close();
});
```

### 5.15 Dark Mode and Color Scheme

```typescript
use: {
  colorScheme: 'dark',
},
```

Or toggle mid-test:

```typescript
test('toggle dark mode', async ({ page }) => {
  await page.emulateMedia({ colorScheme: 'dark' });
  await expect(page.locator('body')).toHaveCSS(
    'background-color', 'rgb(0, 0, 0)'
  );

  await page.emulateMedia({ colorScheme: 'light' });
  await expect(page.locator('body')).toHaveCSS(
    'background-color', 'rgb(255, 255, 255)'
  );
});
```

### 5.16 Parallel Execution and Sharding

By default, Playwright runs test files in parallel with 50% of CPU cores as workers.

```typescript
// Make all tests in a file run in parallel (not just across files)
test.describe.configure({ mode: 'parallel' });
```

For CI sharding across machines:

```bash
# Machine 1
npx playwright test --shard=1/3

# Machine 2
npx playwright test --shard=2/3

# Machine 3
npx playwright test --shard=3/3
```

#### Merge Sharded Reports

```bash
npx playwright merge-reports ./blob-reports --reporter=html
```

### 5.17 Test Retries

```typescript
// playwright.config.ts
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
});
```

In tests, detect retry:

```typescript
test('may need retry', async ({ page }, testInfo) => {
  if (testInfo.retry) {
    await page.goto('/clear-cache');
  }
  // ...
});
```

### 5.18 Parameterized Tests

```typescript
const users = [
  { role: 'admin', expectDashboard: true },
  { role: 'viewer', expectDashboard: false },
  { role: 'editor', expectDashboard: true },
];

for (const { role, expectDashboard } of users) {
  test(`${role} sees correct dashboard`, async ({ page }) => {
    await page.goto(`/login?role=${role}`);
    if (expectDashboard) {
      await expect(page.locator('#dashboard')).toBeVisible();
    } else {
      await expect(page.locator('#dashboard')).not.toBeVisible();
    }
  });
}
```

### 5.19 Code Generation with Codegen

Record interactions as test code automatically:

```bash
# Generate TypeScript test code
npx playwright codegen https://example.com

# Save to file
npx playwright codegen -o tests/recorded.spec.ts https://example.com

# Target a different language
npx playwright codegen --target python https://example.com
npx playwright codegen --target java https://example.com
npx playwright codegen --target csharp https://example.com

# Emulate a device
npx playwright codegen --device="iPhone 13" https://example.com

# Set viewport
npx playwright codegen --viewport-size=800,600 https://example.com

# Custom test ID attribute
npx playwright codegen --test-id-attribute=data-cy https://example.com
```

Available `--target` languages:

| Target | Output |
|---|---|
| `javascript` | Playwright library script |
| `playwright-test` | `@playwright/test` test file (default) |
| `python` | Python sync script |
| `python-async` | Python async script |
| `python-pytest` | pytest-playwright test |
| `csharp` | C# script |
| `csharp-mstest` | C# MSTest |
| `csharp-nunit` | C# NUnit |
| `java` | Java script |
| `java-junit` | Java JUnit test |

### 5.20 Browser Open and Inspect

Open a browser manually for exploration:

```bash
# Default browser (Chromium)
npx playwright open https://example.com

# Specific browser
npx playwright cr https://example.com    # Chromium
npx playwright ff https://example.com    # Firefox
npx playwright wk https://example.com    # WebKit

# With emulation
npx playwright open --device="iPhone 13" https://example.com
npx playwright open --color-scheme=dark https://example.com
npx playwright open --timezone="Asia/Tokyo" https://example.com
npx playwright open --geolocation="35.6762,139.6503" https://example.com
```

### 5.21 Waiting Strategies

Playwright auto-waits for elements before acting on them, but sometimes you need explicit waits:

```typescript
// Wait for a specific element
await page.waitForSelector('.results-loaded');

// Wait for navigation
await page.waitForURL('**/success');

// Wait for load state
await page.waitForLoadState('networkidle');

// Wait for a response
const response = await page.waitForResponse('**/api/data');

// Wait for a request
const request = await page.waitForRequest('**/api/submit');

// Wait for a function to return truthy
await page.waitForFunction(() => document.title.includes('Ready'));

// Explicit timeout
await page.waitForTimeout(1000);  // avoid in tests; prefer auto-wait
```

> [!WARNING]
> Avoid `page.waitForTimeout()` in tests. It introduces artificial delays and makes tests slow and fragile. Use locator assertions (`await expect(locator).toBeVisible()`) or event-based waits instead.

### 5.22 Console and Error Capture

```typescript
test('capture console errors', async ({ page }) => {
  const errors: string[] = [];
  page.on('console', (msg) => {
    if (msg.type() === 'error') errors.push(msg.text());
  });

  page.on('pageerror', (error) => {
    errors.push(error.message);
  });

  await page.goto('/');

  expect(errors).toEqual([]);
});
```

### 5.23 Accessibility Testing

```typescript
test('page is accessible', async ({ page }) => {
  await page.goto('/');

  // Check for accessibility violations using @axe-core/playwright
  // npm install -D @axe-core/playwright
  const { checkA11y } = require('@axe-core/playwright');

  const accessibilityScanResults = await new AxeBuilder({ page })
    .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});
```

### 5.24 Component Testing

Playwright supports component testing for React and Vue applications.

```bash
npm install -D @playwright/ct-react    # React 18+
npm install -D @playwright/ct-react17  # React 17
npm install -D @playwright/ct-vue      # Vue
```

```typescript
// src/Button.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './Button';

test('renders with text', async ({ mount }) => {
  const component = await mount(<Button>Click me</Button>);
  await expect(component).toContainText('Click me');
});

test('handles click', async ({ mount }) => {
  let clicked = false;
  const component = await mount(
    <Button onClick={() => (clicked = true)}>Click</Button>
  );
  await component.click();
  expect(clicked).toBeTruthy();
});
```

### 5.25 Running a Playwright Server

For remote browser connections:

```bash
npx playwright run-server --port=3001 --host=0.0.0.0
```

| Option | Description |
|---|---|
| `--port` | Port to listen on |
| `--host` | Host to bind to |
| `--path` | WebSocket path prefix |
| `--max-clients` | Maximum concurrent connections |
| `--mode` | Server mode |
| `--artifacts-dir` | Directory for artifacts |

Connect from a client:

```typescript
import { chromium } from 'playwright';

const browser = await chromium.connect('ws://server-host:3001');
const page = await browser.newPage();
await page.goto('https://example.com');
```

### 5.26 Storage State for Multi-User Scenarios

```typescript
test('admin and user interaction', async ({ browser }) => {
  // Admin context
  const adminContext = await browser.newContext({
    storageState: 'playwright/.auth/admin.json',
  });
  const adminPage = await adminContext.newPage();

  // User context
  const userContext = await browser.newContext({
    storageState: 'playwright/.auth/user.json',
  });
  const userPage = await userContext.newPage();

  // Admin creates content
  await adminPage.goto('/admin/posts');
  await adminPage.click('#create-post');
  await adminPage.fill('#title', 'New Announcement');
  await adminPage.click('#publish');

  // User sees it
  await userPage.goto('/posts');
  await expect(userPage.locator('.post-title')).toContainText('New Announcement');

  await adminContext.close();
  await userContext.close();
});
```

### 5.27 Handling HTTP Credentials and Proxies

```typescript
// Basic auth
use: {
  httpCredentials: {
    username: 'admin',
    password: 'secret',
  },
},

// Proxy
use: {
  proxy: {
    server: 'http://proxy.example.com:8080',
    username: 'proxyuser',
    password: 'proxypass',
  },
},
```

CLI with proxy:

```bash
npx playwright open --proxy-server=http://proxy:8080 https://example.com
```

### 5.28 Block Service Workers

```typescript
// In config
use: {
  serviceWorkers: 'block',
},
```

Or via CLI:

```bash
npx playwright open --block-service-workers https://example.com
```

### 5.29 Custom Test ID Attribute

If your app uses a custom attribute for test selectors (e.g., `data-cy` for Cypress migration):

```typescript
// playwright.config.ts
use: {
  testIdAttribute: 'data-cy',
},
```

Now `page.getByTestId('submit')` looks for `[data-cy="submit"]` instead of `[data-testid="submit"]`.

### 5.30 MCP Test Server

Playwright includes an MCP (Model Context Protocol) server for AI-assisted testing:

```bash
npx playwright run-test-mcp-server --headless --config=playwright.config.ts
```

| Option | Description |
|---|---|
| `--headless` | Run browsers in headless mode |
| `-c, --config` | Path to config file |
| `--host` | Host to bind to |
| `--port` | Port to listen on |

### 5.31 Test Agents

Initialize test agents for AI-driven testing:

```bash
npx playwright init-agents --config=playwright.config.ts --project=chromium
```

| Option | Description |
|---|---|
| `-c, --config` | Path to config file |
| `--project` | Target project |
| `--prompts` | Agent prompt configuration |

---

## 6. Troubleshooting

### 6.1 Installation Problems

#### Error: `Host system is missing dependencies to run browsers`

**Cause**: Linux system lacks required shared libraries for browser binaries.

**Solution**:

```bash
npx playwright install-deps
```

Or for a specific browser:

```bash
npx playwright install-deps chromium
```

**Prevention**: Always use `npx playwright install --with-deps` on fresh CI images.

---

#### Error: `browserType.launch: Executable doesn't exist at ...`

**Cause**: Browsers were not installed, or were installed for a different Playwright version.

**Solution**:

```bash
npx playwright install
```

**Prevention**: Add `npx playwright install` to your CI setup step. Pin your Playwright version in `package.json`.

---

#### Error: `Cannot find module '@playwright/test'`

**Cause**: Package not installed or not in the correct `node_modules`.

**Solution**:

```bash
npm install -D @playwright/test
```

---

### 6.2 Test Execution Problems

#### Error: `Timeout 30000ms exceeded`

**Cause**: Action or assertion took longer than the default 30-second timeout. Common with slow pages, network latency, or elements that never appear.

**Solution**:

1. Increase timeout for the specific test:

```typescript
test('slow operation', async ({ page }) => {
  test.setTimeout(60_000);
  // ...
});
```

2. Or increase globally in config:

```typescript
timeout: 60_000,
use: {
  actionTimeout: 15_000,
  navigationTimeout: 60_000,
},
```

3. Investigate whether the element actually appears — use `--debug` mode to watch.

---

#### Error: `strict mode violation: locator resolved to N elements`

**Cause**: A locator matched multiple elements when only one was expected.

**Solution**: Make the locator more specific:

```typescript
// Too broad
await page.click('button');

// Specific
await page.click('button:has-text("Submit")');
// or
await page.getByRole('button', { name: 'Submit' }).click();
```

---

#### Error: `Element is not visible`

**Cause**: Attempting to interact with a hidden element.

**Solution**:

```typescript
// Wait for visibility first
await expect(page.locator('#modal')).toBeVisible();
await page.locator('#modal button').click();

// Or force the action (use sparingly)
await page.click('#hidden-button', { force: true });
```

---

#### Error: `waiting for selector "..." to be visible`

**Cause**: The element exists in DOM but is not rendered (display: none, visibility: hidden, zero size).

**Solution**: Check that the element becomes visible before interacting. Use the browser DevTools or Trace Viewer to inspect the DOM state at the time of failure.

---

#### Error: `page.goto: net::ERR_CONNECTION_REFUSED`

**Cause**: The target URL is not reachable. The dev server may not have started yet.

**Solution**:

1. Verify the `webServer` config starts your app correctly.
2. Ensure the URL and port match between `webServer` and `baseURL`.
3. Add a longer `timeout` to `webServer`:

```typescript
webServer: {
  command: 'npm start',
  url: 'http://localhost:3000',
  timeout: 120_000,
},
```

---

#### Playwright stuck at `about:blank`

**Cause**: This is a known issue (GitHub #31050, 50+ comments) where the browser opens but never navigates. Often caused by browser extensions, corrupted profiles, or system-level browser interference.

**Solution**:

1. Clear the browser cache: `npx playwright clear-cache`
2. Ensure no other Playwright processes are running
3. Try a different browser: `--project=firefox`
4. On Linux, check that `--no-sandbox` is not needed (Docker/CI)

---

#### Tests pass locally but fail on CI

**Cause**: Timing differences, missing fonts, different screen resolution, or missing system dependencies.

**Solution**:

1. Use `npx playwright install --with-deps` in CI
2. Set `retries: 2` for CI in config
3. Use `trace: 'on-first-retry'` to debug CI failures
4. Set deterministic viewport and locale in config
5. Avoid `networkidle` waits — prefer element-based assertions

---

### 6.3 Browser-Specific Issues

#### WebKit WASM breakage (v1.59)

**Cause**: Known regression in v1.59 (GitHub #40268) where WebAssembly modules fail in WebKit.

**Solution**: Upgrade to v1.60+ where this is addressed, or skip WebKit for WASM-dependent tests:

```typescript
test('wasm feature', async ({ page, browserName }) => {
  test.skip(browserName === 'webkit', 'WebKit WASM issue #40268');
  // ...
});
```

---

#### Firefox cannot read response JSON from GET requests

**Cause**: Known issue with Firefox 146 (GitHub #39160).

**Solution**: Use `response.text()` and parse manually as a workaround, or check for Playwright updates that bundle a patched Firefox.

---

#### `Response.formData()` throws TypeError in WebKit

**Cause**: WebKit 26.4 regression (GitHub #40244).

**Solution**: Parse the response body manually instead of using `formData()`.

---

#### Proxy auth causes redirects to hang in Chromium

**Cause**: When using proxy authentication without `page.route()`, Chromium may hang on redirects (GitHub #40194).

**Solution**: Set proxy credentials in the browser context options rather than relying on HTTP authentication prompts:

```typescript
const context = await browser.newContext({
  proxy: {
    server: 'http://proxy:8080',
    username: 'user',
    password: 'pass',
  },
});
```

---

### 6.4 Dialog and Popup Issues

#### `page.on('dialog')` not working with external URL handlers

**Cause**: Known limitation (GitHub #11014, 37 comments) where dialogs triggered by external URL scheme handlers (e.g., `mailto:`, `tel:`) are not intercepted by Playwright's dialog handler.

**Solution**: Mock the navigation instead of handling the dialog:

```typescript
await page.route('**/*', (route) => {
  if (route.request().url().startsWith('mailto:')) {
    return route.abort();
  }
  return route.continue();
});
```

---

### 6.5 TypeScript Issues

#### Error: `Cannot run TypeScript files with "declare"`

**Cause**: TypeScript files using `declare` keyword (ambient declarations) can conflict with Playwright's test loader (GitHub #38586).

**Solution**: Move ambient declarations to a separate `.d.ts` file and ensure `tsconfig.json` includes it properly:

```json
{
  "compilerOptions": {
    "strict": true
  },
  "include": ["tests/**/*.ts", "types/**/*.d.ts"]
}
```

---

### 6.6 VS Code Extension Issues

#### VS Code extension showing "No tests have been found"

**Cause**: The extension may not detect tests if the config file is in a subdirectory, the test file pattern does not match, or the extension version is mismatched (GitHub #40226).

**Solution**:

1. Ensure `playwright.config.ts` is at the workspace root
2. Verify `testDir` in config points to the correct directory
3. Check that test files match the pattern `**/*.@(spec|test).?(c|m)[jt]s?(x)`
4. Restart the extension: Ctrl+Shift+P > "Playwright: Reload"
5. Ensure extension version matches your installed Playwright version

---

### 6.7 Performance Problems

#### Tests are too slow

**Solution**:

1. Run in parallel (default): ensure `fullyParallel: true` is set
2. Use more workers: `--workers=8`
3. Block unnecessary resources:

```typescript
await page.route('**/*.{png,jpg,css,font}', (route) => route.abort());
```

4. Reuse authentication state instead of logging in per test
5. Use `webServer.reuseExistingServer` in local development
6. Avoid `waitForLoadState('networkidle')` — it waits for 500ms of network silence

---

#### Out of memory in CI

**Solution**:

1. Reduce workers: `--workers=1` or `--workers=2`
2. Shard across machines instead of parallelizing on one
3. Close contexts explicitly in tests
4. Disable video and trace unless needed

---

### 6.8 Debugging Techniques

#### Debug a Specific Test

```bash
PWDEBUG=1 npx playwright test tests/login.spec.ts
```

The Playwright Inspector opens alongside the browser. Step through actions, inspect locators, and evaluate selectors in real time.

#### Pause Mid-Test

```typescript
test('debug this', async ({ page }) => {
  await page.goto('/');
  await page.pause();  // Inspector opens here
  await page.click('#next');
});
```

#### Verbose Logging

```bash
DEBUG=pw:api npx playwright test
```

Other debug channels: `pw:browser`, `pw:protocol`, `pw:channel`.

#### Review Traces After Failure

```bash
npx playwright show-trace test-results/my-test-chromium/trace.zip
```

---

## 7. FAQ

**Q: Which browsers does Playwright support?**
Playwright supports Chromium (Chrome, Edge), Firefox, and WebKit (Safari) — the three major browser engines. Each Playwright release bundles specific browser versions that are tested and supported.

**Q: Can I use Playwright with JavaScript instead of TypeScript?**
Yes. Use `playwright.config.js` instead of `.ts` and write tests as `.spec.js` files. TypeScript is optional but recommended for autocompletion and type safety.

**Q: How do I run tests on only one browser?**
Use `--project=chromium`, `--project=firefox`, or `--project=webkit`. These correspond to project names in your config.

**Q: What is the difference between `playwright` and `playwright-core`?**
`playwright` bundles browser download logic and defaults. `playwright-core` is the bare library without auto-download — intended for library authors or custom browser management. For testing, always use `@playwright/test` which includes everything.

**Q: How do I run tests in headed mode to see the browser?**
Use `npx playwright test --headed`. For debugging, `npx playwright test --debug` opens headed mode with the Inspector.

**Q: How do I test multiple viewports or devices?**
Define multiple projects in your config, each with different `devices` settings. Playwright includes device descriptors for dozens of phones and tablets.

**Q: Can I test a site that requires login?**
Yes. Use a setup project to authenticate once, save `storageState` to a JSON file, and load it in subsequent projects. See section 5.1.

**Q: How do I handle pop-ups and new tabs?**
Use `page.waitForEvent('popup')` to capture the new page object. See section 5.12.

**Q: How do I test file downloads?**
Set `acceptDownloads: true` (default in test fixtures), then use `page.waitForEvent('download')`. See section 5.8.

**Q: What is the `baseURL` setting for?**
It allows you to use relative URLs in `page.goto('/path')` instead of full URLs. Set it in config or override with the `PLAYWRIGHT_TEST_BASE_URL` environment variable.

**Q: How do I debug tests on CI?**
Enable `trace: 'on-first-retry'` in your config. Failed tests produce trace files you can download and view locally with `npx playwright show-trace`.

**Q: How do I test Web Components or Shadow DOM?**
Playwright pierces Shadow DOM automatically. Locators like `page.locator('my-component >> button')` work without extra configuration.

**Q: Can I run Playwright tests in parallel?**
Yes, parallelism is on by default. Test files run in parallel across workers (default: 50% of CPU cores). Use `test.describe.configure({ mode: 'parallel' })` for intra-file parallelism.

**Q: How do I shard tests across CI machines?**
Use `--shard=1/3`, `--shard=2/3`, `--shard=3/3` on each machine, then merge results with `npx playwright merge-reports`.

**Q: How do I retry flaky tests?**
Set `retries: 2` in your config. Playwright re-runs failed tests up to that many times. Use `trace: 'on-first-retry'` to automatically capture traces for debugging the first failure.

**Q: How do I test APIs without a browser?**
Use the `request` fixture which provides an API client. See section 5.2. No browser is launched for API-only tests using `request`.

**Q: Can I intercept and mock network requests?**
Yes. Use `page.route()` to intercept requests and `route.fulfill()` to return mock responses. See section 5.3.

**Q: What file naming convention should I use for tests?**
The default pattern matches `*.spec.ts`, `*.spec.js`, `*.test.ts`, `*.test.js` (and their `.mjs`, `.cjs`, `.mts`, `.cts`, `.tsx`, `.jsx` variants). Use `.spec.ts` by convention.

**Q: How do I update snapshot baselines?**
Run `npx playwright test --update-snapshots`. This regenerates all screenshot baselines.

**Q: How do I skip a test on a specific browser?**
Use conditional skip inside the test:

```typescript
test('chromium only feature', async ({ browserName }) => {
  test.skip(browserName !== 'chromium', 'Chromium-only feature');
  // ...
});
```

**Q: Can I use environment variables in tests?**
Yes. Access them via `process.env` in config or test files. Use `dotenv` for `.env` file loading, or pass them inline: `MY_VAR=value npx playwright test`.

**Q: How do I generate PDF output from a page?**
Use `page.pdf()` in Chromium only. See section 5.7. Firefox and WebKit do not support PDF generation.

**Q: What is UI Mode?**
`npx playwright test --ui` opens an interactive test runner with live-reload, time-travel debugging, and a visual timeline. It is the recommended way to author and debug tests during development.

**Q: How do I clear Playwright's cache?**
Run `npx playwright clear-cache`. This removes cached browser binaries and test artifacts.

**Q: What is the Trace Viewer?**
A web-based tool that visualizes test execution: DOM snapshots, network traffic, console logs, and action timeline. Open with `npx playwright show-trace <trace.zip>`.

**Q: Can I connect to a remote browser?**
Yes. Start a server with `npx playwright run-server` and connect with `browser.connect('ws://host:port')`. See section 5.25.

**Q: How do I test responsive design?**
Configure multiple projects with different `viewport` sizes or use the device descriptors (`devices['iPhone 13']`, `devices['iPad Pro 11']`, etc.).

---

_Generated May 2026 from microsoft/playwright at 5790370. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
