Playwright Cheatsheet for Javascript & Typescript

CMD+K

Automation Quick Start

Install Playwright and local browsers
npm i playwright
npx playwright install --with-deps
npm i -D typescript tsx # optional
Create a script file
touch src/example.js # with JS
touch src/example.ts # with TS
Write a basic script
import * as pw from 'playwright';

const browser = await pw.chromium.launch();
const page = await browser.newPage();
await page.goto('https://www.browsercat.com');
await browser.close();
Generate code from user behavior
npx playwright codegen
Run your script
node src/example.js # with JS
npx tsx src/example.ts # with TS

Launch Browsers Locally

Launch Chromium
const chromium = await pw.chromium.launch();
Launch Firefox
const firefox = await pw.firefox.launch();
Launch Webkit
const webkit = await pw.webkit.launch();

Persistent User Data

Browser contexts are normally flushed. But you can optionally store context between sessions.

Launch browser with persist user data
const userDataDir = './userData';
const context = await pw.chromium
  .launchPersistentContext(userDataDir);
Configure persistent context options
// accepts all options from Browser.newContext()
const context = await pw.chromium
  .launchPersistentContext(userDataDir, {
    acceptDownloads: true,
    ignoreHTTPSErrors: true,
  });

Browser Launch Config

Use the following options when launching local browsers or browser servers.

Open browser window on launch
const browser = await pw.chromium.launch({
  headless: false,
});
Open browser devtools on launch
const browser = await pw.chromium.launch({
  devtools: true,
});
Set custom downloads path
const browser = await pw.chromium.launch({
  downloadsPath: './downloads',
});
Set custom timeout for browser launch
const browser = await pw.chromium.launch({
  timeout: 1000 * 60,
});
Handle process termination signals
const browser = await pw.chromium.launch({
  // all default to true
  handleSIGHUP: false
  handleSIGINT: false // ctrl+c
  handleSIGTERM: false
});

Proxy

Proxy browser traffic
const browser = await pw.chromium.launch({
  proxy: {
    server: 'https://proxy.com:8080',
  },
});
Proxy browser traffic with authentication
const browser = await pw.chromium.launch({
  proxy: {
    server: 'https://proxy.com:8080',
    username: 'user',
    password: 'pass',
  },
});
Bypass browser proxy for specific domains
const browser = await pw.chromium.launch({
  proxy: {
    server: 'https://proxy.com:8080',
    bypass: '.dev.browsercat.com, .local',
  },
});

Browser Runtime Config

You can configure the browser runtime at launch. See browser-specific sections for common recommendations.

Set custom environment variables for browser
const browser = await pw.chromium.launch({
  // defaults to `process.env`
  env: {
    ...process.env,
    SOCKS_SERVER: 5,
  },
});
Launch a specific browser executable
const browser = await pw.chromium.launch({
  executablePath: '/path/to/brave-browser',
});
Launch browser with custom CLI args
const browser = await pw.chromium.launch({
  args: ['--disable-gl-drawing-for-tests'],
});
Disable specific default CLI args
const browser = await pw.chromium.launch({
  ignoreDefaultArgs: ['--hide-scrollbars'],
});
Disable all default CLI args
const browser = await pw.chromium.launch({
  ignoreDefaultArgs: true,
});

Chromium-Based Config

Enable Chromium sandboxing
const browser = await pw.chromium.launch({
  chromiumSandbox: true, // default: false
});
Set Chromium user preferences (chrome://flags)
const browser = await pw.chromium.launch({
  args: [
    '--enable-features=NetworkService,BackgroundFetch',
    '--disable-features=IsolateOrigins,BackForwardCache',
  ],
});
Set Chromium CLI args
const browser = await pw.chromium.launch({
  args: [
    '--aggressive-cache-discard',
  ],
  ignoreDefaultArgs: [
    '--mute-audio',
  ],
});
Enable “new” Chromium headless mode
const browser = await pw.chromium.launch({
  args: ['--headless=new'],
  ignoreDefaultArgs: ['--headless'],
});

Firefox Config

The following options are specific to Firefox.

Set Firefox user preferences (about:config)
const browser = await pw.firefox.launch({
  firefoxUserPrefs: {
    'threads.use_low_power.enabled': true,
  },
});
Set Firefox CLI args
const browser = await pw.firefox.launch({
  args: [
    '-no-remote',
    '--kiosk "https://www.browsercat.com"',
  ],
  ignoreDefaultArgs: [
    '-foreground',
  ],
});

Webkit Config

The following options are specific to Webkit.

Set Webkit CLI args
const browser = await pw.webkit.launch({
  args: [
    '--ios-simulator',
  ],
  ignoreDefaultArgs: [
    '--disable-accelerated-compositing',
  ],
});

Browser Servers

Browser servers externalize the memory and CPU footprint, massively improving the performance of your local machine or production server.

Install playwright without local browsers
npm i playwright-core
Launch a browser server
const browser = await pw.chromium.launchServer();
const wsEndpoint = await browser.wsEndpoint();
Connect to a browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint);
Send custom headers to browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  headers: {'Api-Key': '<YOUR_API_KEY>'},
});
Set custom timeout for browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  timeout: 1000 * 60,
});

Expose Network to Browser Servers

You can grant browser servers access to protected network locations such as localhost.

Expose localhost to browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  exposeNetwork: 'localhost',
});
Expose all loopbacks to browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  exposeNetwork: '<loopback>',
});
Expose specific IPs to browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  exposeNetwork: '127.0.0.1, 192.168.*.*',
});
Expose domains to browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  exposeNetwork: '*.dev.browsercat.com, *.local',
});
Expose entire network to browser server
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  exposeNetwork: '*',
});

Chrome DevTools Protocol

Use the Chrome DevTools Protocol to connect to already-running chromium-based browsers.

Connect using Chrome Devtools Protocol (CDP)
const httpEndpoint = 'http://localhost:9222';
const browser = await pw.chromium.connectOverCDP(
  httpEndpoint,
);

Chrome Browser Variants

Playwright supports all four Chrome variants, but you must download Chrome Dev and Chrome Canary manually.

Install Chrome
npx playwright install --with-deps chrome
Install Chrome Beta
npx playwright install --with-deps chrome-beta
Launch Chrome
const chrome = await pw.chromium.launch({
  channel: 'chrome',
});
Launch Chrome Beta
const chrome = await pw.chromium.launch({
  channel: 'chrome-beta',
});
Launch Chrome Dev
const chrome = await pw.chromium.launch({
  channel: 'chrome-dev',
  executablePath: '/path/to/chrome-dev',
});
Launch Chrome Canary
const chrome = await pw.chromium.launch({
  channel: 'chrome-canary',
  executablePath: '/path/to/chrome-canary',
});

Edge Browser Variants

Playwright supports all four Edge variants, but you must download Edge Canary manually.

Install Edge
npx playwright install --with-deps edge
Install Edge Beta
npx playwright install --with-deps edge-beta
Install Edge Dev
npx playwright install --with-deps msedge-dev
Launch Edge
const chrome = await pw.chromium.launch({
  channel: 'edge',
});
Launch Edge Beta
const chrome = await pw.chromium.launch({
  channel: 'edge-beta',
});
Launch Edge Dev
const chrome = await pw.chromium.launch({
  channel: 'edge-dev',
});
Launch Edge Canary
const chrome = await pw.chromium.launch({
  channel: 'edge-canary',
  executablePath: '/path/to/edge-canary',
});

Firefox Browser Variants

Playwright supports all four Firefox variants, but you must download Firefox Dev manually.

Install Firefox
npx playwright install --with-deps firefox
Install Firefox Beta
npx playwright install --with-deps firefox-beta
Install Firefox Nightly
npx playwright install --with-deps firefox-asan
Launch Firefox
const chromium = await pw.firefox.launch();
Launch Firefox Beta
const chrome = await pw.firefox.launch({
  channel: 'firefox-beta',
});
Launch Firefox Dev
const chrome = await pw.firefox.launch({
  channel: 'firefox-beta', // yes, this is correct
  executablePath: '/path/to/firefox-dev',
});
Launch Firefox Nightly
const chrome = await pw.firefox.launch({
  channel: 'firefox-asan',
});

Image Generation

Take screenshots of entire pages or specific elements. Most configuration options work in both cases.

Screenshot current viewport
await page.screenshot();
Screenshot entire page
await page.screenshot({fullPage: true});
Screenshot specific element
const $element = page.locator('h1');
await $element.screenshot();
Resize viewport before screenshot
await page.setViewportSize({
  width: 2000, 
  height: 1000,
});
await page.screenshot();
Screenshot custom HTML/CSS content
await page.setContent(`
<html>
  <head>
    <style>
      body { background-color: red; }
    </style>
  </head>

  <body>
    <h1>Hello, World!</h1>
  </body>
</html>
`);
await page.screenshot();
Apply custom stylesheet during screenshot
await page.screenshot({
  style: './path/to/screenshot.css',
});
Apply custom raw styles during screenshot
const $style = await page.addStyleTag({
  content: 'body { background-color: red; }',
});

await page.screenshot();

await $style.evaluate((node) => {
  node.remove();
});

Mask and Clip Screenshots

Mask elements to hide sensitive information. Clip page screenshots to specific regions.

Hide specific elements in screenshot
await page.screenshot({
  mask: [
    page.locator('input'),
    page.getByRole('button'),
    page.locator('.sensitive'),
  ],
});
Set custom mask color
await page.screenshot({
  // default: #FF00FF (pink)
  maskColor: '#00FF00',
});
Clip page screenshot to specific region
await page.screenshot({
  clip: {
    x: 0, 
    y: 0, 
    width: 100, 
    height: 100,
  },
});

Rendering Options

Scale image to device pixel ratio
await page.screenshot({
  scale: 'device', // default
});
Disable scaling to device pixel ratio
await page.screenshot({
  scale: 'css', // default: device
});
Enable animations during screenshot
await page.screenshot({
  animations: 'allow', // default: 'disabled'
});
Enable caret blinking during screenshot
await page.screenshot({
  caret: 'initial', // default: 'hide'
});

PNG Output

Generate image as PNG buffer
const image = await page.screenshot();
Save image to PNG file
await page.screenshot({
  path: 'screenshot.png',
});
Enable transparency in PNG images
await page.screenshot({
  // default: white background
  omitBackground: true,
});

JPEG Output

Output image as JPEG buffer
const image = await page.screenshot({
  type: 'jpeg',
});
Output JPEG image with custom quality
const image = await page.screenshot({
  type: 'jpeg',
  quality: 80,
});
Save image as JPEG file
await page.screenshot({
  path: 'screenshot.jpg',
});
Save JPEG image with custom quality
await page.screenshot({
  path: 'screenshot.jpg', 
  quality: 80,
});

PDF Generation

Generate a PDF from an entire web page. Control output with CSS and core print options.

Generate PDF as buffer
const pdf = await page.pdf();
Save PDF to file
await page.pdf({
  path: 'page.pdf',
});

Page Size

Set page size to conventional format
await page.pdf({
  // supported:
  // `Letter`, `Legal`, `Tabloid`, `Ledger`,
  // `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`
  format: 'Letter', // default
});
Enable landscape orientation for format
await page.pdf({
  landscape: true, // default: false
});
Set page size to custom dimensions
await page.pdf({
  // supported: `px`, `in`, `cm`, `mm`
  width: '8.5in',
  height: '11in',
});
Prefer CSS page size, if available
await page.pdf({
  preferCSSPageSize: true, // default: false
});
Set page size using CSS
const $style = await page.addStyleTag({
  preferCSSPageSize: true, // default: false
  content: '@page { size: A4 landscape; }',
});

await page.pdf();

await $style.evaluate((node) => {
  node.remove();
});

Page Margins

Set page margins to custom dimensions
await page.pdf({
  // default: 0, 0, 0, 0
  // supported: `px`, `in`, `cm`, `mm`
  margin: {
    top: '1cm',
    right: '1cm',
    bottom: '1cm',
    left: '1cm',
  },
});
Set page margins using CSS
const $style = await page.addStyleTag({
  content: '@page { margin: 1cm; }',
});

await page.pdf();

await $style.evaluate((node) => {
  node.remove();
});

Headers and Footers

Enable print headers and footers
await page.pdf({
  displayHeaderFooter: true, // default: false
});
Set custom HTML/CSS header template
// supported: `date`, `title`, `url`, `pageNumber`, `totalPages`
const headerTemplate = `
<div style="display: flex; justify-content: space-between;">
  <span class="title"></span>
  <span class="date"></span>
</div>
`;

await page.pdf({
  headerTemplate,
  margin: {top: '1in'},
});
Set custom HTML/CSS footer template
// supported: `date`, `title`, `url`, `pageNumber`, `totalPages`
const footerTemplate = `
<div style="text-align: center;">
  <span class="pageNumber"></span>
  /
  <span class="totalPages"></span>
</div>
`;

await page.pdf({
  footerTemplate,
  margin: {bottom: '1in'},
});

Rendering Options

Enable background colors and images
await page.pdf({
  printBackground: true, // default: false
});
Scale page before laying out content
await page.pdf({
  scale: 2, // default: 1
});

PDF Options

Output only certain page ranges
await page.pdf({
  // default: all pages
  pageRanges: '1-3, 5',
});
Enable tagging for accessibility and reflow
await page.pdf({
  tagged: true, // default: false
});
Enable outline for PDF navigation
await page.pdf({
  outline: true, // default: false
});

Video Generation

Generate videos of page interactions. Playwright’s API is still rough around the edges.

Record video of current page
const context = await browser.newContext({
  recordVideo: {
    dir: './videos', // required
  },
});

const page = await context.newPage();
// do stuff...

await context.close(); // required
Scale video to fit custom dimensions
const context = await browser.newContext({
  recordVideo: {
    dir: './videos',
    size: {
      width: 1920, // default: 800
      height: 1080, // default: 800
    },
  },
});
Get video output path for current page
const videoPath = page.video.path();
Copy video of page to custom location
await page.video.saveAs('custom.webm');
Delete video of current page
await page.video.delete();

Selective Recording

While we wait for Playwright updates, this process can be used to record “slices” of page interactions.

(1) Enable video recording for context
const context = await browser.newContext({
  recordVideo: {
    dir: './videos',
  },
});
(2) Create blackout overlay
await context.addInitScript(() => {
  const blackout = document.createElement('div');

  blackout.id = 'blackout';
  blackout.style.position = 'fixed';
  blackout.style.top = '0';
  blackout.style.left = '0';
  blackout.style.width = '100%';
  blackout.style.height = '100%';
  blackout.style.backgroundColor = 'black';
  blackout.style.zIndex = '999999';

  blackout.addEventListener('ignore', () => {
    blackout.style.display = 'none';
  });

  blackout.addEventListener('record', () => {
    blackout.style.display = 'block';
  });

  document.body.appendChild(blackout);
});
(3) Setup page and locate blackout
const page = await context.newPage();
await page.goto('https://www.browsercat.com');
const $blackout = page.locator('#blackout');
(4) Toggle blackout to create slices
await $blackout.dispatchEvent('record');
// do stuff, recording slice 1...
await $blackout.dispatchEvent('ignore');

// do stuff in secret...

await $blackout.dispatchEvent('record');
// do stuff, recording slice 2...
await $blackout.dispatchEvent('ignore');
(5) Store page video in custom location
await page.video.saveAs('sliceable.webm');
(6) Save video files to disk
await context.close();
(7) Split video into slices using ffmpeg
INPUT_FILE="sliceable.webm"

# detect black frames
BLACKDETECT_OUTPUT=$(ffmpeg -i "$INPUT_FILE" -vf "blackdetect=d=0.1:pix_th=0.1" -an -f null - 2>&1 | grep blackdetect)

SLICE_NUM=1
PREV_END=0

# loop over slices
echo "$BLACKDETECT_OUTPUT" | while read -r line; do
  BLACK_START=$(echo $line | sed -n 's/.*black_start:\([^ ]*\).*/\1/p')
  BLACK_END=$(echo $line | sed -n 's/.*black_end:\([^ ]*\).*/\1/p')

  if (( $(echo "$PREV_END == 0" | bc -l) )); then
    DURATION=$(echo "$BLACK_START" | bc)
  else
    DURATION=$(echo "$BLACK_START - $PREV_END" | bc)
  fi

  BASE_NAME=$(basename "$INPUT_FILE" .webm)
  OUTPUT_SLICE="${BASE_NAME}-${SLICE_NUM}.webm"

  # create new video slice
  if (( $(echo "$DURATION > 0" | bc -l) )); then
    ffmpeg -ss "$PREV_END" -i "$INPUT_FILE" -t "$DURATION" -c copy "$OUTPUT_SLICE"
  fi

  SLICE_NUM=$((SLICE_NUM+1))
  PREV_END="$BLACK_END"
done

# handle file slice
TOTAL_DURATION=$(ffmpeg -i "$INPUT_FILE" 2>&1 | grep "Duration" | awk '{print $2}' | sed s/,// | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')
LAST_SLICE_DURATION=$(echo "$TOTAL_DURATION - $PREV_END" | bc)

if (( $(echo "$LAST_SLICE_DURATION > 0" | bc -l) )); then
  OUTPUT_SLICE="${BASE_NAME}-${SLICE_NUM}.webm"
  ffmpeg -ss "$PREV_END" -i "$INPUT_FILE" -t "$LAST_SLICE_DURATION" -c copy "$OUTPUT_SLICE"
fi

Testing Quick Start

Get started without any configuration required.

Install Playwright for testing
npm i -D @playwright/test
npx playwright install
npm i -D typescript # with TS
Create a test file
touch tests/example.spec.js # with JS
touch tests/example.spec.ts # with TS
Write a basic test
import {test, expect} from '@playwright/test';

test('has brand in <title>', async ({ page }) => {
  await page.goto('https://www.browsercat.com/');
  await expect(page).toHaveTitle(/BrowserCat/);
});
Generate code from user behavior
npx playwright codegen
Run your tests
npx playwright test
Run your tests in UI mode
npx playwright test --ui
Show test results in browser
npx playwright show-report

Test Isolation

TODO: Working with TestInfo, workers, etc.

Parameterize Tests

Parameterize tests across multiple projects
// playwright.config.ts
export type TestOptions = {
  cat: string;
};

export default defineConfig<TestOptions>({
  projects: [{
    name: 'cartoons',
    use: {cat: 'Garfield'},
  }, {
    name: 'sitcoms',
    use: {cat: 'Smelly Cat'},
  }]
});

// extended-test.ts
import {test} from '@playwright/test';
import type {TestOptions} from 'playwright.config.ts';

export default test.extend<TestOptions>({
  cat: ['Hello Kitty', {option: true}],
});

// profiles.spec.ts
import test from './extended-test.ts';

test('test', async ({page, cat}) => {
  await expect(page).toHaveTitle(cat);
});

Test Runner Configuration

Create a config file
touch playwright.config.ts
Write basic config
import {defineConfig} from '@playwright/test';

export default defineConfig({
  testMatch: 'tests/**/*.spec.{ts,tsx}',
});
Externalize heavy dependencies from build
export default defineConfig({
  build: {
    external: ['**/*bundle.js'],
  },
});
Minimize CLI output
export default defineConfig({
  quiet: !!process.env.CI,
});

Output Files

Select test results output path
export default defineConfig({
  outputDir: './.test/results',
});
Always preserve test results
export default defineConfig({
  preserveOutput: 'always',
});
Never preserve test results
export default defineConfig({
  preserveOutput: 'never',
});
Only preserve test results on failure
export default defineConfig({
  preserveOutput: 'failures-only',
});
Select snapshots output path
export default defineConfig({
  snapshotPathTemplate: './.test/snapshots/{projectName}/{testFilePath}/{arg}{ext}',
});

Configure Exit Criteria

Retry failed tests before marking as failed
export default defineConfig({
  retries: 3, // default 0
});
Repeat all tests before marking as passed
export default defineConfig({
  repeatEach: 3, // default 1
});
Fail individual tests if they exceed timeout
export default defineConfig({
  timeout: 1000 * 30,
});
Fail test suite if exceeds timeout
export default defineConfig({
  globalTimeout: 1000 * 60 * 60,
});
Fail test suite if .only is present
export default defineConfig({
  forbidOnly: !!process.env.CI,
});
Fail test suite early, after N failures
export default defineConfig({
  maxFailures: 10, // default 0
});

Assertion Settings

Configure defaults for numerous expect methods.

Set default timeout for all assertions
export default defineConfig({
  expect: {timeout: 1000 * 10}, // default 5s
});
Set default DOM screenshot config
export default defineConfig({
  toHaveScreenshot: {
    threshold: 0.1, // default 0.2
    maxDiffPixelRatio: 0.1, // default 0
  },
});
Set default image snapshot config
export default defineConfig({
  toMatchSnapshot: {
    threshold: 0.1, // default 0.2
    maxDiffPixelRatio: 0.1, // default 0
  },
});
Ignore all snapshot/screenshot assertions
export default defineConfig({
  ignoreSnapshots: !process.env.CI,
});
Set criteria for updating snapshots
export default defineConfig({
  updateSnapshots: 'missing', // or 'all' or 'none'
});

Overrides

Override runner config for test file
test.describe.configure({
  mode: 'parallel', // or 'serial'
  retries: 3,
  timeout: 1000 * 60,
});

test('test', async () => {});
Override runner config for test group
test.describe('group', async () => {
  test.describe.configure({
    mode: 'parallel', // or 'serial'
    retries: 3,
    timeout: 1000 * 60,
  });
  
  test('test', async () => {});
});

Local Web Server

Launch web server during tests
export default defineConfig({
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: true,
  },
  use: {
    baseURL: 'http://localhost:3000',
  },
});
Launch multiple web servers during tests
export default defineConfig({
  webServer: [{
    command: 'npm run start:app',
    url: 'http://localhost:3000',
  }, {
    command: 'npm run start:api',
    url: 'http://localhost:3030',
  }],
});

Test Environment Options

Configure your test environment, browser, and emulated device. Set test options globally, per project, per test file, or per test group.

Connect to browser server server
export default defineConfig({
  use: {
    connectOptions: {
      wsEndpoint: 'wss://api.browsercat.com/connect',
      headers: {'Api-Key': '<YOUR_API_KEY>'},
      exposeNetwork: 'localhost, *.browsercat.com',
    },
  },
});

Test Debugging Tools

Replace data-testid with custom attribute
export default defineConfig({
  use: {
    testIdAttribute: 'data-bcat-id',
  },
});
Show browser window during tests
export default defineConfig({
  use: {
    headless: false,
  },
});
Screenshot tests automatically
export default defineConfig({
  use: {
    screenshot: 'on', // or 'only-on-failure' | 'off'
  },
});
Record video of tests automatically
export default defineConfig({
  use: {
    video: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'off'
  },
});
Record trace data for tests automatically
export default defineConfig({
  use: {
    trace: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'off'
  },
});

Configure Behavior

Configure auto-accepting download requests
export default defineConfig({
  use: {
    acceptDownloads: false, // default: true
  },
});
Set delay between user actions
export default defineConfig({
  use: {
    actionTimeout: 1000 * 3, // default: 0
  },
});
Set delay between page navigations
export default defineConfig({
  use: {
    navigationTimeout: 1000 * 3, // default: 0
  },
});
Enable or disable JavaScript
export default defineConfig({
  use: {
    javaScriptEnabled: false, // default: true
  },
});
Enable or disable service workers
export default defineConfig({
  use: {
    serviceWorkers: 'block', // default: 'allow'
  },
});
Grant custom browser permissions automatically
export default defineConfig({
  use: {
    permissions: ['geolocation', 'notifications'],
  },
});
Set custom browser cookies
export default defineConfig({
  use: {
    storageState: {
      cookies: [{
        name: 'name',
        value: 'value',
        domain: 'browsercat.com',
        // and other cookie properties...
      }],
    },
  },
});
Set custom localStorage state
export default defineConfig({
  use: {
    storageState: {
      origins: [{
        origin: 'browsercat.com',
        localStorage: [{
          name: 'name',
          value: 'value',
        }],
      }],
    },
  },
});
Load browser cookies and storage from file
export default defineConfig({
  use: {
    storageState: 'path/to/storage.json',
  },
});

Configure Network Traffic

Enable relative URLs with custom base URL
export default defineConfig({
  use: {
    // Allows tests to use relative URLs
    // e.g. `page.goto('/login');`
    baseURL: isDev ? 
      'https://localhost:8080' :
      'https://www.example.com',
  },
});
Set custom HTTP credentials
export default defineConfig({
  use: {
    httpCredentials: {
      username: 'user',
      password: 'pass',
    },
  },
});
Send custom default HTTP headers with requests
export default defineConfig({
  use: {
    extraHTTPHeaders: {
      'X-My-Header': 'value',
    },
  },
});
Emulate offline network conditions
export default defineConfig({
  use: {
    offline: true,
  },
});
Bypass Content Security Policy (CSP)
export default defineConfig({
  use: {
    // Use when you encounter security issues
    // injecting custom scripts or styles
    bypassCSP: true,
  },
});
Ignore HTTPS errors
export default defineConfig({
  use: {
    ignoreHTTPSErrors: process.env.NODE_ENV === 'development',
  },
});
Route all traffic through a proxy server
export default defineConfig({
  use: {
    proxy: {
      server: 'http://localhost:8080',
      username: 'user',
      password: 'pass',
      // bypass proxy for specific domain patterns
      bypass: 'browsercat.com, .example.com',
    },
  },
});

Configure Browsers

Emulate specific browsers quickly
import {defineConfig, devices} from '@playwright/test';

export default defineConfig({
  projects: [{
    name: 'Desktop Chrome',
    use: {
      ...devices['Desktop Chrome'],
    },
  }, {
    name: 'Mobile Chrome',
    use: {
      ...devices['Mobile Chrome'],
    },
  }],
});
Select browser to use for tests
export default defineConfig({
  use: {
    browserName: 'firefox', // or 'webkit' | 'chromium'
  },
});
Select userland browser to use for tests
export default defineConfig({
  use: {
    channel: 'chrome', // or 'chrome-beta' | 'chrome-dev' | 'msedge' | 'msedge-beta' | 'msedge-dev'
  },
});
Set user’s preferred color scheme
export default defineConfig({
  use: {
    colorScheme: 'dark', // or 'light' | 'no-preference'
  },
});
Set specific browser user agent
export default defineConfig({
  use: {
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
  },
});
Set browser viewport size
export default defineConfig({
  use: {
    viewport: { width: 1920, height: 1080 },
  },
});
Set browser CLI launch arguments
export default defineConfig({
  use: {
    launchOptions: {
      args: ['--no-sandbox'],
    },
  },
});
Configure additional browser context options
export default defineConfig({
  use: {
    contextOptions: {
      reducedMotion: 'reduce',
      strictSelectors: true,
    },
  },
});

Emulate Devices

Emulate specific devices quickly
import {defineConfig, devices} from '@playwright/test';

export default defineConfig({
  projects: [{
    name: 'iPhone Landscape',
    use: {
      ...devices['iPhone 14 Pro Max landscape'],
    },
  }, {
    name: 'Kindle Fire',
    use: {
      ...devices['Kindle Fire HDX'],
    },
  }],
});
Emulate device display scale factor
export default defineConfig({
  use: {
    deviceScaleFactor: 2,
  },
});
Emulate mobile device
export default defineConfig({
  use: {
    isMobile: true,
  },
});
Emulate touch screen support
export default defineConfig({
  use: {
    hasTouch: true,
  },
});

Configure Locale

Emulate custom locale
export default defineConfig({
  use: {
    locale: 'en-US',
  },
});
Emulate custom timezone
export default defineConfig({
  use: {
    timezoneId: 'America/New_York',
  },
});
Emulate custom geolocation
export default defineConfig({
  use: {
    geolocation: {
      latitude: 40.730610, 
      longitude: -73.935242,
      accuracy: 11.08,
    },
  },
});

Overrides

Override test options for project
export default defineConfig({
  use: {offline: false},

  projects: [{
    name: 'Offline Support',
    testMatch: '**/offline/**/*.spec.ts',
    use: {offline: true},
  }]
});
Override test options for test file
test.use({offline: true});

test('test', async () => {});
Override test options for test group
test.describe('group', async () => {
  test.use({offline: true});
  
  test('test', async () => {});
});

Writing Tests

Write a basic test
import {test, expect} from '@playwright/test';

test('has brand in <title>', async ({ page }) => {
  await page.goto('https://www.browsercat.com/');
  await expect(page).toHaveTitle(/BrowserCat/);
});
Attach screenshot to a test
test('with screenshot', async ({page}) => {
  await test.info().attach('screenshot', {
    contentType: 'image/png',
    body: await page.screenshot(),
  });
});

Test Groups

Test groups cluster tests for shared configuration and organized reporting.

Create a test group
test.describe('group', async () => {
  test('test', async () => {});
});
Create nested test groups
test.describe('group', async () => {
  test.describe('subgroup', async () => {
    test('test', async () => {});
  });
});
Create a test group without nested reporting
test.describe(async () => {
  // will not be nested in reports
  test('test', async () => {});
});

Test Steps

Test steps improve report readability. They can also be reused across multiple tests.

Break a test into steps
test('long test', async () => {
  await step('step 1', async () => {});
  await step('step 2', async () => {});
  await step('step 3', async () => {});
});
Break a test into nested steps
test('long test', async () => {
  await step('step 1', async () => {
    await step('substep 1', async () => {});
    await step('substep 2', async () => {});
  });
});
Attribute errors to step rather than line
test('long test', async () => {
  await step('step 1', async () => {
    throw new Error();
  }, {box: true});
});
Create reusable test steps
async function login(user: string, pass: string) {
  return test.step('login', async ({page}) => {
    await page.fill('input[name="username"]', user);
    await page.fill('input[name="password"]', pass);
    await page.click('button[type="submit"]');
  });
}

test('login, then interact', async () => {
  await login('user', 'pass');
});

Test Annotations

.only() Tests

If any tests or test groups are marked .only, only those will run. Use only during development.

Only run a specific test
test.only('working on this', async () => {});
Only run a specific test groups
test.describe.only('working on this', async () => {});
Only run some specific tests
test.only('working on this', async () => {});
test.describe.only('and this', async () => {});

test('but not this', async () => {});
test.describe('or this', async () => {});

.slow() Tests

Slow tests will run with 3x the default timeout. Use when the test is slow, but it’s not broke.

Mark test as slow
test('slow test', async () => {
  test.slow();
});
Mark a slow test conditionally
test('slow on webkit', async ({browserName}) => {
  test.slow(browserName === 'webkit');
});
Mark slow tests in file conditionally
test.slow(({browserName}) => browserName === 'webkit');

test('fast sometimes', async () => {});

.skip() Tests

Skipped tests will not run. Use when the tests pass, but they slow development.

Skip a specific test
test.skip('skipped for now', async () => {});

test('skipped for now', async () => {
  test.skip();
});
Skip a specific test group
test.describe.skip('skipped for now', async () => {});
Skip a test conditionally
test('skipped on webkit', async ({browserName}) => {
  test.skip(browserName === 'webkit', `Not supported.`);
});
Skip tests in file conditionally
test.skip(({browserName}) => browserName === 'webkit');

test('possibly skipped', async () => {});
test('possibly skipped', async () => {});

.fixme() Tests

Fixme tests will not run. Use when the test is correct, but the code needs to be fixed.

Mark a test as todo
test.fixme('broken for now', async () => {});

test('broken for now', async () => {
  test.fixme();
});
Mark a test group as todo
test.describe.fixme('broken for now', async () => {});
Mark a todo test conditionally
test('broken on webkit', async ({browserName}) => {
  test.fixme(browserName === 'webkit', `Not supported.`);
});
Mark todo tests in file conditionally
test.fixme(({browserName}) => browserName === 'webkit');

test('works sometimes', async () => {});
test('works sometimes', async () => {});

.fail() Tests

Failing tests will run, and will throw an error if they actually pass. Use to assert expected behavior.

Mark test as expected to fail
test('always', async () => {
  test.fail();
});
Mark a failing test conditionally
test('broken on webkit', async ({browserName}) => {
  test.fail(browserName === 'webkit', `Not supported.`);
});
Mark failing tests in file conditionally
test.fail(({browserName}) => browserName === 'webkit');

test('works sometimes', async () => {});
test('works sometimes', async () => {});

Custom Test Annotations

Test Tags

Organize your tests using free-form tags. Filter on them to speed development and reuse tests across contexts.

Add custom tags to test
test('test', {
  tags: ['@slow', '@feature-auth'],
}, async () => {});
Add custom tags to test group
test.describe('group', {
  tags: ['@feature-dashboard'],
}, async () => {
  test('test', async () => {});
});
Run only tests matching tag
npx playwright test --grep @feature-auth
Run only tests matching multiple tags
npx playwright test --grep "@feature-.+"
Exclude tests matching tag
npx playwright test --grep-invert @feature-auth
Exclude tests matching multiple tags
npx playwright test --grep-invert "@feature-.+"

Custom Test Annotations

Custom annotations appear in reports and can be accessed programmatically for custom behavior.

Add custom annotations to test
test('test', {
  annotation: [{
    type: 'issue', 
    description: '@browsercat/cookbook/issues/1',
  }],
}, async () => {});
Add custom annotions to test group
test.describe('group', {
  annotation: [{
    type: 'flaky', 
    description: 'Upstream browser vendor bug.',
  }],
}, async () => {
  test('test', async () => {});
});

Test Setup & Teardown

Global Setup & Teardown

Use global setup and teardown to configure the local machine or the browser environment.

Run setup code before any tests run
// playwright.config.ts
export default defineConfig({
  globalSetup: 'tests/global-setup.ts',
});

// tests/global-setup.ts
import type {FullConfig} from '@playwright/test';

export default async function setup(cfg: FullConfig) {
  // runs once before all tests
}
Run teardown code after all tests run
// playwright.config.ts
export default defineConfig({
  globalTeardown: 'tests/global-teardown.ts',
});

// tests/global-teardown.ts
import type {FullConfig} from '@playwright/test';

export default async function teardown(cfg: FullConfig) {
  // runs once after all tests
}
Run setup and teardown within browser
// playwright.config.ts
export default defineConfig({
  projects: [{
    // runs before all projects that depend on 'setup'
    name: 'setup',
    testMatch: 'tests/setup.ts',
    teardown: 'teardown', // <-- project reference
  }, {
    name: 'tests',
    dependencies: ['setup'],
  }, {
    // runs after all projects that depend on 'setup'
    name: 'teardown',
    testMatch: 'tests/teardown.ts',
  }],
});

Within a File

Use .before*() and .after*() methods to streamline setup across tests within a single file or group.

Run code before all tests in file
test.beforeAll(async () => {
  // runs once before all tests
});

test('test', async () => {});
Run code after all tests in file
test.afterAll(async () => {
  // runs once after all tests
});

test('test', async () => {});
Run code before each test in file
test.beforeEach(async () => {
  // runs once before each test
});

test('test', async () => {});
Run code after each test in file
test.afterEach(async () => {
  // runs once after each test
});

test('test', async () => {});
Label setup and teardown steps
test.beforeAll('create mocks', async () => {});
test.beforeEach('generate values', async () => {});
test.afterEach('reset cache', async () => {});
test.afterAll('delete files', async () => {});

Within a Test Group

Setup and teardown methods can be used within groups and subgroups for full control across multiple tests.

Run code before all tests in test group
test.describe('group', async () => {
  test.beforeAll(async () => {
    // runs once before all tests in group
  });

  test('test', async () => {});
});
Run code after all tests in test group
test.describe('group', async () => {
  test.afterAll(async () => {
    // runs once after all tests in group
  });

  test('test', async () => {});
});
Run code before each test in test group
test.describe('group', async () => {
  test.beforeEach(async () => {
    // runs once before each test in group
  });

  test('test', async () => {});
});
Run code after each test in test group
test.describe('group', async () => {
  test.afterEach(async () => {
    // runs once after each test in group
  });

  test('test', async () => {});
});
Run nested setup and teardown steps
test.describe('group', async () => {
  test.beforeAll(async () => {});

  test.describe('subgroup', async () => {
    test.beforeEach(async () => {});

    test('test', async () => {});
  });
});

Test Fixtures

Use Playwright’s built-in fixtures to save time on setup and teardown.

Access page fixture in test
test('test', async ({page}) => {
  // `page` is exclusive to this test
  await page.goto('https://www.browsercat.com');
});
Access context fixture in test
test('test', async ({context}) => {
  // `context` is exclusive to this test
  const page = await context.newPage();
});
Access browser fixture in test
test('test', async ({browser}) => {
  // `browser` is shared across worker thread
  const context = await browser.newContext();
});
Access request fixture in test
test('test', async ({request}) => {
  // `request` is exclusive to this test
  await request.get('https://api.browsercat.com');
});

Custom Test Fixtures

Create fixtures to share behavior and improve readability.

Create fixture with static value
import {test as base} from '@playwright/test';
import {nanoid} from 'nanoid';

type TestFixtures = {
  nanoid: typeof nanoid;
  getUserId: () => `user_${string}`;
};

export const test = base.extend<TestFixtures>({
  nanoid: nanoid,
  getUserId: () => `user_${nanoid()}`;
});
Create fixture with setup and teardown
import {test as base, type BrowserContext} from '@playwright/test';

type TestFixtures = {
  authContext: BrowserContext;
};

export const test = base.extend<TestFixtures>({
  authContext: async ({browser}, use) => {
    // setup before each test
    const authContext = await browser.newContext({
      storageState: 'auth.json',
    });

    // run test
    await use(authContext);
    
    // teardown after each test
    await authContext.close();
  },
});
Override fixture implementation
export const test = base.extend({
  page: [async ({page}, use) => {
    page.addInitScript('tests/init.js');
    await use(page);
  }],
});
Combine multiple custom fixture assignments
import {test, mergeTests} from '@playwright/test';

const test1 = test.extend({});
const test2 = test.extend({});

export const test = mergeTests(test1, test2);

Fixture Customization

Always run fixture, even if unused
export const test = base.extend({
  fixture: [async (ctx, use) => {
    await use(null);
  }, {auto: true}],
});
Set custom timeout for fixture execution
export const test = base.extend({
  fixture: [async (ctx, use) => {
    // fixture will not count toward test timeout
    await use(await slowRequest());
  }, {timeout: 1000 * 10}],
});
Share fixture across all tests in worker
import {test as base, type BrowserContext} from '@playwright/test';

type WorkerFixtures = {
  authContext: BrowserContext;
};

export const test = base.extend<{}, WorkerFixtures>({
  authContext: [async ({browser}, use) => {
    // setup before all tests
    const authContext = await browser.newContext({
      storageState: 'auth.json',
    });

    // run all tests
    await use(authContext);
    
    // teardown after all tests
    await authContext.close();
  }, {scope: 'worker'}],
});
Define both test and worker fixtures at once
import {test as base, type Page} from '@playwright/test';

type TestFixtures = {
  perTest: Page;
};
type WorkerFixtures = {
  perWorker: Page;
};

export const test = base
  .extend<TestFixtures, WorkerFixtures>({
    perTest: [async ({context}, use) => {
      const ephemeralPage = await context.newPage();
      await use(ephemeralPage);
    }, {scope: 'test'}],

    perWorker: [async ({browser}, use) => {
      const context = await browser.newContext();
      const persistentPage = await context.newPage();
      await use(persistentPage);
    }, {scope: 'worker'}],
  });

Customize Fixtures at Runtime

Static fixture values can be configured at runtime. This allows you to repeat test suites across multiple contexts.

Allow fixture to be customized at runtime
type TestOptions = {
  domain: string;
};

export const test = base.extend<TestOptions>({
  domain: [
    'https://www.browsercat.com', 
    {scope: 'test', option: true},
  ],
});
Allow worker fixture to be customized at runtime
type WorkerOptions = {
  siteUrl: string;
};

export const test = base.extend<{}, WorkerOptions>({
  siteUrl: [
    'https://www.browsercat.com', 
    {scope: 'worker', option: true},
  ],
});
Customize fixture for specific tests
type Options = {
  siteUrl: string;
};

export default defineConfig<Options>({
  projects: [{
    name: 'site-amazon',
    use: {
      siteUrl: 'https://www.amazon.com',
    },
  }, {
    name: 'site-producthunt',
    use: {
      siteUrl: 'https://www.etsy.com',
    },
  }],
});

Generic Assertions

Assert strict equality (identity)
expect(1).toBe(1);
Assert truthy value
expect(1).toBeTruthy();
Assert falsy value
expect(0).toBeFalsy();

Type Assertions

Assert the value is a string
expect('a').toBe(expect.any(String));
Assert the value is a number
expect(1).toBe(expect.any(Number));
Assert the value is a boolean
expect(true).toBe(expect.any(Boolean));
Assert the value is a function
expect(() => {}).toBe(expect.any(Function));
Assert the value is NaN
expect(NaN).toBeNaN();
Assert the value is null
expect(null).toBeNull();
Assert the value is undefined
expect(undefined).toBeUndefined();
Assert the value is not undefined
expect(1).toBeDefined();
Assert the instanceof the value
expect(page).toBeInstanceOf(Page);

Value Assertions

Number Assertions

Assert number is greater than match
expect(1).toBeGreaterThan(0);
Assert number is greater or equal to match
expect(1).toBeGreaterThanOrEqual(1);
Assert number is less than match
expect(0).toBeLessThan(1);
Assert number is less or equal to match
expect(1).toBeLessThanOrEqual(1);

String Assertions

Assert string includes substring
expect('hello').toContain('hell');
Assert string has specific length
expect('hello').toHaveLength(5);
Assert string matches pattern
expect('hello').toMatch(/^hell/);

Object Assertions

Assert object has specific property
expect({a: 1, b: 2}).toHaveProperty('a');
Assert object property has specific value
expect({a: 1, b: 2}).toHaveProperty('a', 1);
Assert nested object has property
expect({a: [{b: 2}]}).toHaveProperty('a[0].b', 2);

Array Assertions

Assert array includes value
expect([1, 2, 3]).toContain(1);
Assert array includes equivalent value
expect([
  {a: 1},
  {b: 2},
  {c: 3},
]).toContainEqual({a: 1});
Assert array has specific length
expect([1, 2, 3]).toHaveLength(3);

Set Assertions

Assert set includes value
expect(new Set([1, 2, 3])).toContain(1);
Assert set includes equivalent value
expect(new Set([
  {a: 1},
  {b: 2},
  {c: 3},
])).toContainEqual({a: 1});

Error Assertions

Assert function throws error with message
expect(() => { throw new Error('hello') })
  .toThrow('hello');
Assert function throws error with message pattern
expect(() => { throw new Error('hello') })
  .toThrow(/^hell/);
Assert function throws error type
expect(() => { throw new Error('hello') })
  .toThrow(Error);

Polling Assertions

Poll any function until it succeeds. expect.poll() tests the return value, while expect().toPass() tests for thrown errors.

Poll function value until it matches test
await expect.poll(async () => {
  return Math.random();
}).toBeGreaterThan(0.1);
Poll function value with custom intervals
await expect.poll(async () => {
  return Math.random();
}, {
  // default: [100, 250, 500, 1000]
  intervals: [1000, 2000, 3000, 4000, 5000],
}).toBeGreaterThan(0.1);
Poll function value with custom timeout
await expect.poll(async () => {
  return Math.random();
}, {
  // default: 1000 * 5
  timeout: 1000 * 60,
}).toBeGreaterThan(0.1);
Poll function success (returns with no errors)
await expect(async () => {
  expect(Math.random()).toBeGreaterThan(0.1);
}).toPass();
Poll function success with custom intervals
await expect(async () => {
  expect(Math.random()).toBeGreaterThan(0.1);
}).toPass({
  // default: [100, 250, 500, 1000]
  intervals: [1000, 2000, 3000, 4000, 5000],
});
Poll function success with custom timeout
await expect(async () => {
  expect(Math.random()).toBeGreaterThan(0.1);
}).toPass({
  // default: 1000 * 5
  timeout: 1000 * 60,
});

Pattern Assertions

Pattern assertions allow you to test value for rough equivalence. Especially powerful on deeply nested objects.

Assert value loosely deep-equals match
const loose = {a: [{b: 2}, undefined], c: undefined};
const tight = {a: [{b: 2}]};
const match = {a: [{b: 2}]};

expect(loose).toEqual(match);
expect(tight).toEqual(match);
Assert value strictly deep-equals match
const tight = {a: [{b: 2}]};
const loose = {a: [{b: 2}, undefined], c: undefined};
const match = {a: [{b: 2}]};

expect(tight).toStrictEqual(match);
expect(loose).not.toStrictEqual(match);
Assert deep object matches pattern
expect({
  a: 'hello',
  b: [1, 2, 3],
  c: {d: 4, e: 5},
  y: new Map(),
  z: null,
}).toEqual({
  a: expect.stringContaining('hell'),
  b: expect.arrayContaining([1, 2])
  c: expect.objectContaining({d: 4}),
  y: expect.anything(),
  z: expect.any(),
});

Pattern Matchers

Assert defined, non-null value
expect(0).toBe(expect.anything());
Assert number roughly equals match
expect(0.1 + 0.2).toBe(expect.closeTo(0.3));
Assert string includes substring
expect('hello')
  .toEqual(expect.stringContaining('hell'));
Assert string matches pattern
expect('hello')
  .toEqual(expect.stringMatching(/^hell/));
Assert array includes subset of values
expect([1, 2, 3])
  .toEqual(expect.arrayContaining([1, 2]));
Assert object includes subset of properties
expect({a: 1, b: 2})
  .toEqual(expect.objectContaining({a: 1}));
Assert typeof value
expect(1).toBe(expect.any(Number));
expect(page).toBe(expect.any(Page));

Page Assertions

Assert the page title
await expect(page).toHaveTitle('BrowserCat');
Assert the page title matches pattern
await expect(page).toHaveTitle(/browsercat/i);
Assert the page URL
await expect(page).toHaveURL('https://www.browsercat.com/');
Assert the page URL matches pattern
await expect(page).toHaveURL(/browsercat\.com/i);

Element Property Assertions

Assert element has JS DOM property
await expect(locator).toHaveJSProperty('open', true);

ID Assertions

Assert element id matches string
await expect(locator).toHaveId('unique');
Assert element id matches pattern
await expect(locator).toHaveId(/item-\d+/);

Class Assertions

Assert element has class
await expect(locator).toHaveClass('active');
Assert element class matches pattern
await expect(locator).toHaveClass(/active/);
Assert element has multiple classes
await expect(locator).toHaveClass(['active', /visible-.+/]);

Attribute Assertions

Assert element has attribute
await expect(locator).toHaveAttribute('href');
Assert element attribute has value
await expect(locator).toHaveAttribute('href', '/');
Assert element attribute matches pattern
await expect(locator).toHaveAttribute('href', /browsercat\.com/);

CSS Property Assertions

Assert element CSS property value
await expect(locator).toHaveCSS('color', 'red');
Assert element CSS property matches pattern
await expect(locator).toHaveCSS('color', /#3366.{2}/);

Text Content Assertions

Assert element text exactly matches string
await expect(locator).toHaveText('hello');
Assert element text exactly matches pattern
await expect(locator).toHaveText(/hello/);
Assert element text contains substring
await expect(locator).toContainText('hello');
Assert element text contains pattern
await expect(locator).toContainText(/hell/);

Element Interaction Assertions

Element Visibility Assertions

Assert element is visible
await expect(locator).toBeVisible();
Assert element is not visible
await expect(locator).toBeVisible({visible: false});
Assert element is in viewport
await expect(locator).toBeInViewport();
Assert element is fully in viewport
await expect(locator).toBeInViewport({ratio: 1});
Assert element is attached to the DOM
await expect(locator).toBeAttached();
Assert element is detached from the DOM
await expect(locator).toBeAttached({attached: false});

Form & Input Assertions

Assert element is focused
await expect(locator).toBeFocused();
Assert element is enabled
await expect(locator).toBeEnabled();
Assert element is disabled
await expect(locator).toBeEnabled({enabled: false});
Assert element is checked
await expect(locator).toBeChecked();
Assert element is not checked
await expect(locator).toBeChecked({checked: false});
Assert element is editable
await expect(locator).toBeEditable();
Assert element is not editable
await expect(locator).toBeEditable({editable: false});
Assert element input value
await expect(locator).toHaveValue('123-45-6789');
Assert element input value matches pattern
await expect(locator).toHaveValue(/^\d{3}-\d{2}-\d{4}$/);

Element List Assertions

Most element assertions will fail if a locator resolves to multiple elements. The following assertions support multiple elements.

Assert list of elements has length
await expect(locators).toHaveCount(3);
Assert list of elements has exact text
await expect(locators).toHaveText(['hello', /goodbye/]);
Assert list of elements contains substrings
await expect(locators).toContainText(['hell', /^good/]);
Assert list of element input values
await expect(locators).toHaveValues([
  '123-45-6789', 
  /^\d{3}-\d{2}-\d{4}$/,
]);

Visual Assertions

Playwright can compare images to previously-stored values, ensuring your app’s visual appearance doesn’t change unexpectedly.

Page Screenshot Assertions

Assert page matches screenshot
await expect(page).toHaveScreenshot();
Assert image matches named screenshot
// compare to a single image shared across tests
await expect(page).toHaveScreenshot('home-page.png');
Assert cropped page matches screenshot
await expect(page).toHaveScreenshot({
  clip: {x: 0, y: 0, width: 100, height: 100},
});
Assert page roughly matches screenshot
await expect(page).toHaveScreenshot({
  // pixel color can vary by 10% in YIQ space
  threshold: 0.1,
  // 10% of pixels can exceed threshold
  maxDiffPixelRatio: 0.1, 
});

Element Screenshot Assertions

Assert element matches screenshot
await expect(locator).toHaveScreenshot();
Assert element matches named screenshot
// compare to a single image shared across tests
await expect(locator).toHaveScreenshot('home-hero.png');
Assert cropped element matches screenshot
await expect(locator).toHaveScreenshot({
  clip: {x: 0, y: 0, width: 100, height: 100},
});
Assert element roughly matches screenshot
await expect(locator).toHaveScreenshot({
  // pixel color can vary by 10% in YIQ space
  threshold: 0.1,
  // 10% of pixels can exceed threshold
  maxDiffPixelRatio: 0.1, 
});

Image Assertions

Image assertions will work with any image, even those created using custom post-processing.

Assert image matches snapshot
expect(customImage).toMatchSnapshot();
Assert image matches named snapshot
// compare to a single image shared across tests
expect(customImage).toMatchSnapshot('saturated.png');
Assert image roughly matches snapshot
expect(customImage).toMatchSnapshot({
  // pixel color can vary by 10% in YIQ space
  threshold: 0.1,
  // 10% of pixels can exceed threshold
  maxDiffPixelRatio: 0.1, 
});

API Testing

You can test your APIs from within the browser context. Use to validate CORS, cookies, auth, and other contextual issues.

Assert fetch response within browser context
test('send fetch request', async ({request}) => {
  const res = await request.fetch('/api/logout', {
    method: 'GET',
  });

  expect(res.ok()).toBeTruthy();
});
Assert cookies after fetch request
test('send fetch request', async ({request}) => {
  const res = await request.fetch('/api/login', {
    method: 'POST',
  });

  const {cookies} = await request.storageState();
  const sessionCookie = cookies.find((c) => {
    return c.name === 'session';
  });

  expect(sessionCookie).toEqual({
    value: expect.stringMatching(/^\w{32}$/),
  });
});
Assert background request is made
test('background request sent', async (page) => {
  const pingProm = page.waitForRequest(/\/api\/ping$/);
  await page.goto('https://www.browsercat.com');

  await expect(pingProm).not.toThrow();
});

Assertion Modifiers

Assert value does not match test
expect(1).not.toBe(2);
Assert value without stopping execution
expect.soft(1).toBe(1);
Emit custom error message when test fails
expect(1, {
  message: '1 is not 2',
}).toBe(2);
Assert promise resolve value matches test
await expect(Promise.resolve(1))
  .resolves.toBe(1);
Assert promise throws value matching test
await expect(Promise.reject(1))
  .rejects.toBe(1);

Expect Configuration

Set custom timeout for expect
const slowExpect = expect.configure({timeout: 1000 * 60});
Set expect to not throw on failure
const softExpect = expect.configure({soft: true});

Custom Expect Assertions

Extend expect with custom matchers to share behavior and improve readability.

Create custom value matcher
import {expect as base} from '@playwright/test';

export const expect = base.extend({
  async toBeBetween(
    value: number, 
    min: number,
    max: number,
  ) {
    if (value < min || value > max) {
      throw new Error(`Expected ${value} to be between ${min} - ${max}`);
    }
  },
});
Create custom page matcher
import {expect as base, type Page} from '@playwright/test';

export const expect = base.extend({
  async toHaveDescription(value: Page, desc: string) {
    const value = await page
      .locator('meta[name="description"]')
      .first()
      .getAttribute('content') ?? '';

    if (!value.includes(desc)) {
      throw new Error(`Expected "${value}" to include "${desc}"`);
    }
  },
});
Create custom element matcher
import {expect as base, type Locator} from '@playwright/test';

export const expect = base.extend({
  async toBeAriaVisible($loc: Locator) {
    const hidden = await $loc.getAttribute('aria-hidden');

    if (!!hidden) {
      throw new Error(`Expected ${value} to be ARIA visible`);
    }
  },
});
Combine custom expect extensions
import {expect, mergeExpects} from '@playwright/test';

const expect1 = expect.extend({});
const expect2 = expect.extend({});

export const expect = mergeExpects(expect1, expect2);

Test Filtering Configuration

Filter Tests by Filename

Include only tests from specific directory
export default defineConfig({
  testDir: './tests', // default './'
});
Include test files matching glob
export default defineConfig({
  testMatch: '**/*.spec.{ts,js}',
});
Include test files matching regex
export default defineConfig({
  testMatch: /\.spec\.(ts|js)$/,
});
Include test files matching any pattern
export default defineConfig({
  testMatch: [
    '**/*.spec.{ts,js}',
    /\.spec\.(ts|js)$/,
  ],
});
Ignore test files matching glob
export default defineConfig({
  testIgnore: '**/*.ignore.*',
});
Ignore test files matching regex
export default defineConfig({
  testIgnore: /(\.ignore\.|\/ignore\/)/,
});
Ignore test files matching any pattern
export default defineConfig({
  testIgnore: [
    '**/*.ignore.*',
    '**/ignore/**/*',
    /(\.ignore\.|\/ignore\/)/,
  ],
});

Filter Tests by Titles

Only run tests with titles matching regex
export default defineConfig({
  grep: /contact/i,
});
Only run tests with titles matching any regex
export default defineConfig({
  grep: [/button/i, /input/i],
});
Only run tests with titles not matching regex
export default defineConfig({
  grepInvert: /@flaky/i,
});
Only run tests with titles not matching any regex
export default defineConfig({
  grepInvert: [/@flaky/i, /@deprecated/i],
});

Test Projects

Projects allow you to reuse tests across browsers and environments, override global config, and to specify run order.

Run specific test project via CLI
npx playwright test --project firefox
Run multiple test projects via CLI
npx playwright test --project firefox chrome
Run test projects matching glob via CLI
npx playwright test --project "feature-*"
Run all test projects via CLI
npx playwright test # default: all projects

Test Project Configuration

Projects can override most global configuration options. This subset documents the most common applications.

Configure cross-browser test projects
import {defineConfig, devices} from '@playwright/test';

export default defineConfig({
  project: [{
    name: 'firefox',
    use: {...devices['Desktop Firefox']},
  }, {
    name: 'chrome',
    use: {...devices['Desktop Chrome']},
  }, {
    name: 'safari',
    use: {...devices['Desktop Safari']},
  }],
});
Include only tests from specific directory
export default defineConfig({
  project: [{
    name: 'projects',
    testDir: './tests/feature-1',
  }],
});
Include test files matching patterns
export default defineConfig({
  project: [{
    name: 'projects',
    testMatch: [
      'feature-1/**/*.spec.{ts,js}',
      /feature-1\/.+\.spec\.(ts|js)$/,
    ],
  }],
});
Only run tests with titles matching patterns
export default defineConfig({
  project: [{
    name: 'auth',
    grep: [/^Auth/, /(login|logout)/i],
  }],
});
Override test environment options
export default defineConfig({
  project: [{
    name: 'Project',
    use: {
      browserName: 'firefox',
      extraHTTPHeaders: {
        'x-custom-header': 'value',
      },
      // etc...
    },
  }],
});

Test Project Dependencies

Use project dependencies to execute a series of projects in a specific order.

Run target project after another project
export default defineConfig({
  project: [{
    name: 'before',
  }, {
    name: 'tests',
    dependencies: ['before'],
  }],
});
Run target project after multiple projects
export default defineConfig({
  project: [{
    name: 'tasks',
  }, {
    name: 'users',
  }, {
    name: 'workflows',
    dependencies: ['tasks', 'users'],
  }],
});
Run cleanup after project dependencies
export default defineConfig({
  projects: [{
    // runs before all projects that depend on 'setup'
    name: 'setup',
    teardown: 'teardown', // <-- project reference
  }, {
    name: 'tests',
    dependencies: ['setup'],
  }, {
    // runs after all projects that depend on 'setup'
    name: 'teardown',
  }],
});

Test Parallelization

By default, Playwright executes test files in parallel, but it executes a file’s tests in order. This can be configured globally, per file, or per test group.

Run test files in parallel, tests sequentially
export default defineConfig({
  fullyParallel: false, // default
});
Run all tests in all files in parallel
export default defineConfig({
  fullyParallel: true, // default: false
});
Set number of parallel workers
export default defineConfig({
  workers: 3, // default: 50% logical CPUs
});
Set relative number of parallel workers
export default defineConfig({
  workers: '100%', // percentage of logical CPUs
});
Disable all parallelism (forcefully)
export default defineConfig({
  workers: 1,
});

Test File & Group Parallelism

Note that parallelizing tests within a file prohibits tests from sharing state. Use wisely.

Run a single file’s tests in parallel
test.describe.configure({
  mode: 'parallel', // default 'serial'
});

test('test', async () => {});
Run a test group’s tests in parallel
test.describe('group', async () => {
  test.describe.configure({
    mode: 'parallel', // default 'serial'
  });
  
  test('test', async () => {});
});
Run test group serially, even after retries
test.describe('group', async () => {
  test.describe.configure({
    mode: 'serial',
  });
  
  test('test', async () => {});
});

Test Sharding

Shard your test suite to reduce run time. Playwright supports merging results later for easy viewing.

Enable test sharding via CLI (recommended)
npx playwright test --shard "1/10"
Enable test sharding via config file
export default defineConfig({
  shard: {
    total: process.env.PW_SHARD_TOTAL ?? 1, 
    current: process.env.PW_SHARD_CURRENT ?? 1,
  },
});
Configure reporters for sharded test results
export default defineConfig({
  reporter: [
    ['blob', {
      outputDir: 'test-results',
      fileName: `report-${process.env.CI_BUILD_ID}.zip`,
    }],
  ],
});
Combine sharded test results into single report
npx playwright merge-reports \
  --reporter html \
  ./reports

Test Reporting & Configuration

Add custom annotation to test’s result
test('test', {
  annotation: [{
    type: 'issue', 
    description: '@browsercat/cookbook/issues/1',
  }],
}, async () => {});
Attach screenshot to test’s result
test('with screenshot', async ({page}) => {
  await test.info().attach('screenshot', {
    contentType: 'image/png',
    body: await page.screenshot(),
  });
});

Reporter Configuration

Configure test reporters
export default defineConfig({
  reporter: [
    ['list'],
    ['json', {outputFile: 'test-results.json'}],
    ['junit', {outputFile: 'test-results.xml'}],
  ],
});
Attach JSON metadata to test results
export default defineConfig({
  metadata: {
    region: 'us-east-1',
    date: '2021-12-21',
  },
});
Automatically capture test screenshots
export default defineConfig({
  use: {
    screenshot: {
      mode: 'only-on-failure', 
      // or 'on' | 'off' (default)
      fullPage: false, 
      // captures full page, not just viewport
    }
  },
});
Automatically capture test video recordings
export default defineConfig({
  use: {
    video: {
      mode: 'retain-on-failure', 
      // or 'on' | 'on-first-retry' | 'off' (default)
      size: {width: 1920, height: 1080}, 
      // scales viewport to fit (default 800x800)
    },
  },
});
Output list of slowest tests to stdin
export default defineConfig({
  reportSlowTests: {
    max: 10, // default: 5
    threshold: 1000 * 15, // default: 15 seconds
  },
});

CLI Reporter Overrides

Some report config can be overridden on the command line.

Override selected reporters via CLI
npx playwright test \
  --reporter list,json,junit
Override report output directory via CLI
npx playwright test \
  --output .test/results

Test Reporters

CLI Reporters

These reporters output exclusively to stdin.

List each test and its status
export default defineConfig({
  reporter: [
    ['list', {
      printSteps: false, // print test steps
      omitFailures: false, // skip detailed failure messages
    }],
  ],
});
Output single line summary of test run
export default defineConfig({
  reporter: [
    ['line', {
      omitFailures: false, // skip detailed failure messages
    }],
  ],
});
Output row of dots indicating each test’s status
export default defineConfig({
  reporter: [
    ['dot', {
      omitFailures: false, // skip detailed failure messages
    }],
  ],
});
Annotate Github workflow with test results
export default defineConfig({
  reporter: [
    ['github', {
      omitFailures: false, // skip detailed failure messages
    }],
  ],
});

File Reporters

Generate reports for external use. For these reporters, outputFolder/outputFile is always required.

Output HTML report of test run
export default defineConfig({
  reporter: [
    ['html', {
      outputFolder: 'test-results',
      open: 'never', // or 'always' | 'on-failure'
    }],
  ],
});
Output JSON report of test run
export default defineConfig({
  reporter: [
    ['json', {
      outputFile: 'results.json',
    }],
  ],
});
Output JUnit report of test run
export default defineConfig({
  reporter: [
    ['junit', {
      outputFile: 'results.xml',
    }],
  ],
});
Output test result “blob” for post-processing
export default defineConfig({
  reporter: [
    ['blob', {
      outputDir: 'test-results',
      fileName: `report-${process.env.CI_BUILD_ID}.zip`,
    }],
  ],
});

Playwright CLI

Playwright’s CLI provides a wide range of first-class utilities, as well as access to numerous powerful GUI tools.

Show all CLI commands
npx playwright --help

CLI Quick Start

This is a summary. Refer to subsequent cheatsheet sections for deep dives on these commands.

Install browsers
npx playwright install --with-deps
Uninstall browsers
npx playwright uninstall
Open inspector
npx playwright open
Generate code from user actions
npx playwright codegen
Run tests via CLI
npx playwright test
Run tests in UI mode
npx playwright test --ui
Run tests in debug mode
npx playwright test --debug
Open test report in browser
npx playwright show-report
Open trace explorer
npx playwright show-trace
Screenshot URL via current viewport
npx playwright screenshot \
  https://www.browsercat.com \
  browsercat.png
Generate a PDF from URL
npx playwright pdf \
  https://www.browsercat.com \
  browsercat.pdf

Manage Browsers via CLI

Playwright recommends using bundled versions of chromium, firefox, and webkit. It also supports recent userland browsers if specified.

List supported, installable browsers
npx playwright install --help

Install Browsers

Install default browsers
# installs chromium, firefox, webkit
npx playwright install --with-deps
Install specific browsers
npx playwright install --with-deps \
  chromium firefox webkit
Install userland browsers
npx playwright install --with-deps \
  chrome msedge firefox-beta
Force reinstall browsers
npx playwright install --with-deps --force

Uninstall Browsers

Uninstall default browsers
# uninstalls chromium, firefox, webkit
npx playwright uninstall
Uninstall specific browsers
npx playwright uninstall \
  chromium firefox webkit
Uninstall all browsers
npx playwright uninstall --all

Open Playwright Inspector

Use the Playwright Inspector GUI to select locators, debug tests, and generate code based on user actions.

Open inspector to new tab
npx playwright open
List all inspector CLI options
npx playwright open --help

Inspector Configuration

Open inspector to specific URL
npx playwright open \
  https://www.browsercat.com
Open inspector in a specific browser
npx playwright open \
  --browser chromium # or firefox, webkit
Open inspector in a userland browser
npx playwright open \
  --channel chrome # or chrome-beta, msedge, etc.
Open inspector with device emulation
npx playwright open \
  --device "BlackBerry Z30"
Open inspector with specific viewport size
npx playwright open \
  --viewport 1280,720

Generate Code & Tests

Generate code based on your behavior within a live browser. Output test files or standalone automations.

Generate test code from user behavior
npx playwright codegen
Generate automation code from user behavior
npx playwright codegen --target javascript
Save generated code to a file
npx playwright codegen \
  --output tests/recording.spec.js
Generate code in existing script
const browser = await pw.chromium.launch({
  headless: false, // required
});
const page = await browser.newPage();

await page.pause(); // will launch codegen here
List all codegen CLI options
npx playwright codegen --help

Codegen Configuration

Generate code for specific URL
npx playwright codegen \
  https://www.browsercat.com
Generate code in a specific browser
npx playwright codegen \
  --browser chromium # or firefox, webkit
Generate code in a userland browser
npx playwright codegen \
  --channel chrome # or chrome-beta, msedge, etc
Generate code with device emulation
npx playwright codegen \
  --device "BlackBerry Z30"
Generate code with specific viewport size
npx playwright codegen \
  --viewport 1280,720

Run tests via CLI

Run tests via CLI
npx playwright test
Run tests in UI mode
npx playwright test --ui
Run tests in debug mode
npx playwright test --debug
Run tests in headed browser
npx playwright test --headed
List all tests
npx playwright test --list
Show all test CLI options
npx playwright test --help

Filter Tests

Run specific test file
npx playwright test tests/example.spec.ts
Run test files matching regex pattern
npx playwright test ".+/example"
Run tests with specific name
npx playwright test --name "test name"
Run tests with name matching regex pattern
npx playwright test --name "t.{1}st"
Exclude tests with specific name
npx playwright test --grep-invert "test name"
Exclude tests with name matching regex pattern
npx playwright test --grep-invert "n[aeiou]me"

Test Runner Configuration

Use CLI config flags for one-time adjustments, but set project defaults within your playwright.config.ts file.

Run tests with custom config file
npx playwright test --config custom.config.ts
Run tests with custom concurrent worker threads
npx playwright test --workers 5
Run all tests with maximum parallelization
npx playwright test --fully-parallel
Update snapshots for selected tests
npx playwright test --update-snapshots
Ignore all snapshot tests
npx playwright test --ignore-snapshots
Output test artifacts to specific directory
npx playwright test --output ./.test
Run tests with custom reporters
npx playwright test --reporter list,html,markdown

CLI Exit Options

Return success error code when no tests found
npx playwright test --pass-with-no-tests
Stop after first failure
npx playwright test -x # default: infinite
Stop after specific failure count
npx playwright test --max-failures 5 # default: infinite
Run tests with quiet output
npx playwright test --quiet

Other CLI Tools

Playwright offers CLI access to generate images and PDFs. You can also open the report viewer and trace view GUIs.

Generate Screenshots via CLI

Screenshot URL via current viewport
npx playwright screenshot \
  https://www.browsercat.com \
  browsercat.png
Screenshot URL as full page
npx playwright screenshot \
  --full-page \
  https://www.browsercat.com \
  browsercat.png
Screenshot URL with custom viewport size
npx playwright screenshot \
  --viewport-size 800,600 \
  https://www.browsercat.com \
  browsercat.png
Show all screenshot CLI options
npx playwright screenshot --help

Generate PDFs via CLI

Generate PDF from URL
npx playwright pdf \
  https://www.browsercat.com \
  browsercat.pdf
Generate PDF with custom viewport size
npx playwright pdf \
  --viewport-size 800,600 \
  https://www.browsercat.com \
  browsercat.png
Show all PDF CLI options
npx playwright pdf --help

Open Report Viewer

Open most recent test report
npx playwright show-report
Open specific test report
npx playwright show-report \
  .test/results
Merge sharded reports into one
npx playwright merge-reports \
  .test/reports
Merge sharded reports with custom reporters
npx playwright merge-reports \
  --reporter list,html,markdown \
  .test/reports

Open Trace Viewer

Open trace explorer without trace
npx playwright show-trace
Open specific trace file
npx playwright show-trace \
  .test/traces/trace.zip
Open multiple trace files
npx playwright show-trace \
  .test/traces/trace1.zip \
  .test/traces/trace2.zip
Open trace file via stdin
cat .test/traces/trace.zip | \
  npx playwright show-trace --stdin

Browser Actions

Get browser type
const browserType = browser.browserType();
Get browser version
const version = await browser.version();
Check if browser is connected
const isConnected = browser.isConnected();
Close browser (force)
await browser.close();
Close browser (gentle)
await Promise.all(
  browser.contexts()
    .map((context) => context.close()),
);
await browser.close();
Close browser with a reason
await browser.close({reason: 'success'});
Listen for browser disconnection event
browser.on('disconnected', (browser) => {});

Contexts (aka User Sessions)

Create new context
const context = await browser.newContext();
Create new context with custom options
const context = await browser.newContext({
  bypassCSP: true,
  colorScheme: 'dark',
  deviceScaleFactor: 1,
  permissions: ['geolocation'],
  // etc.
});
List all browser’s contexts
const contexts = browser.contexts();
Get the current page’s context
const context = page.context();
Close context
await context.close();
Close context with a reason
await context.close({reason: 'success'});

Pages

Create new page in context
const page = await context.newPage();
Create new page in new context
const page = await browser.newPage();
Create new page in new context with custom options
const page = await browser.newPage({
  bypassCSP: true,
  colorScheme: 'dark',
  deviceScaleFactor: 1,
  permissions: ['geolocation'],
  // etc.
});

Page Navigation

Create new page in context
const page = await context.newPage();
Create new page in default context
const page = await browser.newPage();
Navigate to specific URL
await page.goto('https://browsercat.com');
Navigate via page actions
await page.locator('a[href]').first().click();
await page.waitForEvent('load');
Reload the page
await page.reload();
Navigate to previous page
await page.goBack();
Navigate to next page
await page.goForward();
Close the page
await page.close();
Check if the page is closed
const isClosed = page.isClosed();

Working with Navigation

Wait for the page to navigate to a new URL
await page.waitForURL('https://www.browsercat.com');
Navigate to URL and wait for content to load
await page.goto('https://www.browsercat.com', {
  waitUntil: 'domcontentloaded', // default: 'load'
});
Reload page and wait for content to load
await page.reload({
  waitUntil: 'domcontentloaded', // default: 'load'
});
Catch page that opens in new tab
page.locator('a[target="_blank"]').first().click();
const newTabPage = await page.waitForEvent('popup');
Catch page that opens in pop-up window
page.evaluate(() => {
  window.open('https://www.browsercat.com', null, {
    popup: true,
  });
});
const popupPage = await page.waitForEvent('popup');

Page Frames

Web pages are made up of frames. The main frame is the viewport, which can contain numerous nested iframe elements.

Get parent frame for current page
const frame = page.mainFrame();
Get frame by name attribute
const frame = page.frame({
  name: /^footer-ad$/, // or exact string match
});
Get frame by url attribute
const frame = page.frame({
  url: /\/footer-ad\.html$/, // or exact string match
});
Get all frames for current page
const frames = page.frames();

Frame Locators

Use frame locators to find elements within a specific frame. Normal locators stop at the frame boundary.

Create frame locator from CSS selector
const $frame = page.frameLocator('#soundcloud-embed');
Create frame locator from locator
const $frame = page.locator('#soundcloud-embed');
const $frameLoc = $frame.frameLocator(':scope');

Locate Frame Elements

Locate Elements

All locator methods are available on the page and frame objects. They’re also available on existing locators for selecting child elements.

Contract-based Locators

These helpers target elements using properties unlikely to change. Use whenever possible for readability and durability.

Select element by role
const $alert = await page.getByRole('alert');
Select element by label
const $input = await page.getByLabel('Username');
Select element by placeholder
const $input = await page.getByPlaceholder('Search');
Select element by title
const $el = await page.getByTitle('Welcome');
Select element by alt text
const $image = await page.getByAltText('Logo');
Select element by text
const $button = await page.getByText('Submit');
Select element by test id
const $button = await page.getByTestId('submit-button');

Role-based Selectors

These locators support ARIA roles, states, and properties. Very useful for shorthand selection of most DOM elements.

Select element by role
const $alert = await page.getByRole('alert');
const $heading = await page.getByRole('heading');
const $button = await page.getByRole('button');
const $link = await page.getByRole('link');
// etc.
Select element by accessible name
const $button = await page.getByRole('button', { 
  name: /(submit|save)/i, // or string
  exact: true, // default: false
});
Select elements by checked state
const $checkbox = await page.getByRole('checkbox', { 
  checked: true, // or false
});
Select elements by selected state
const $option = await page.getByRole('option', { 
  selected: true, // or false
});
Select elements by expanded state
const $menu = await page.getByRole('menu', { 
  expanded: true, // or false
});
Select elements by pressed state
const $button = await page.getByRole('button', { 
  pressed: true, // or false
});
Select elements by disabled state
const $input = await page.getByRole('textbox', { 
  disabled: true, // or false
});
Select elements by depth level
const $heading = await page.getByRole('heading', { 
  level: 2, // etc.
});
Match hidden elements with ARIA locators
const $alert = await page.getByRole('alert', { 
  includeHidden: true, // default: false
});

CSS Selectors

Playwright supports all CSS selectors using document.querySelector().

Select elements by CSS selector
const $icon = await page.locator('button > svg[width]');
Select elements by tag name
const $header = await page.locator('header');
Select elements by tag attribute
const $absLinks = await page.locator('[href^="https://"]');
Select elements by CSS class
const $buttons = await page.locator('.btn');
Select elements by CSS id
const $captcha = await page.locator('#captcha');

XPath Selectors

Playwright supports all XPath selectors using document.evaluate(). However, XPath selectors are brittle and highly discouraged.

Select element by XPath selector
const $button = await page.locator('//button[text()="Submit"]');

Network Traffic

Intercept network traffic on the page or context objects using any method listed below. Prefer page for narrow targeting.

Send fetch request
const res = await page.request.fetch(
  'https://browsercat.com',
  {method: 'GET'},
);
Flush network traffic cache
await page.request.dispose();
Set default HTTP headers on all requests
await page.setExtraHTTPHeaders({
  'X-Agent': 'production-test-bot',
});

Wait for Network Traffic

Wait for request matching test
const req = await page.waitForEvent('request', (req) => {
  return req.method() === 'PUT' && 
    req.headers()['content-type'] === 'application/json';
});
Wait for response matching test
const res = await page.waitForEvent('response', (res) => {
  return res.status() === 201 && 
    Array.isArray(await res.json());
});
Wait for page request by URL
const req = await page.waitForRequest(/browsercat\.com/);
Wait for context request by URL
const req = await context.waitForEvent('request', (req) => {
  return req.url().includes('browsercat.com');
});
Wait for page response by URL
const res = await page.waitForResponse(/browsercat\.com/);
Wait for context response by URL
const res = await context.waitForEvent('response', (res) => {
  return res.url().includes('browsercat.com');
});

Network Events

Listen for new network requests
page.on('request', (req) => {});
Listen for successful network requests
page.on('requestfinished', (req) => {});
Listen for failed network requests
page.on('requestfailed', (req) => {});
Listen for network responses
page.on('response', (res) => {});
Listen for new websocket requests (page only)
page.on('websocket', (ws) => {});

Intercept Network Traffic

Note: Playwright can’t currently intercept traffic to webworkers. If needed, disable webworkers.

Route all requests through handler
await page.route('**/*', (route) => {
  route.continue();
});
Route requests matching glob
await page.route('**/*.png', (route) => {
  route.continue();
});
Route requests matching regex
await page.route(/\.json$/i, (route) => {
  route.continue();
});
Route request only once
await page.route('**/*.png', (route) => {
  route.continue();
}, {times: 1}); // or any number
Remove all handlers from route
await page.unroute('**/*.png');
Remove specific handler from route
await page.unroute('**/*.png', pngHandler);
await page.unroute(/\.json$/i, jsonHandler);
Remove all network routes immediately
await page.unrouteAll();
Remove all network routes as they complete
await page.unrouteAll({
  behavior: 'wait', // or 'ignoreErrors' | 'default'
});

Transforming Network Traffic

All routed requests must be handled using either .continue(), .abort(), or .fulfill().

Allow routed request to proceed
await page.route('**/*', (route) => {
  route.continue();
});
Modify request before allowing to proceed
await page.route('**/*', (route, req) => {
  route.continue({
    method: 'GET',
    url: 'http://localhost:8080/test',
    headers: {
      ...req.headers(),
      'X-Test': 'true',
    },
    postData: JSON.stringify({
      ...req.postDataJSON(),
      test: true,
    }),
  });
});
Proceed with request, but intercept response
await page.route('**/*', (route) => {
  const response = await route.fetch();
  
  route.fulfill({
    response,
    json: {
      ...await response.json(),
      test: true,
    },
  })
});
Fulfill routed request with custom response
await page.route('**/*', (route) => {
  route.fulfill({
    status: 404,
    json: {message: 'not found'},
  });
});
Fulfill routed request with local file
await page.route('**/*.png', (route) => {
  route.fulfill({
    path: './1-pixel.png',
  });
});
Fallback to earlier-defined matching route handler
await page.route('**/*', (route) => {
  // blocked unless later-defined matching routes call `.fallback()`
  route.abort();
});

await page.route('**/*', (route) => {
  // allows earlier-defined matching routes to handle request
  route.fallback();
});
Modify request before fallback to earlier-defined handler
await page.route('**/*', (route, req) => {
  console.log(req.postDataJSON().isImage === true);

  route.continue();
});

await page.route('**/*.png', (route, req) => {
  route.fallback({
    postData: JSON.stringify({
      ...req.postDataJSON(),
      isImage: true,
    }),
    // etc.
  });
});
Abort routed request
await page.route('**/*', (route) => {
  route.abort();
});
Abort routed request with custom error
await page.route('**/*', (route) => {
  route.abort('connectionrefused'); // see docs for values
});

HAR Replay Network Traffic

HAR (HTTP Archive format) records network traffic to replay later. Use for exact reproduction of test conditions or in advanced workflows.

Respond using HAR, aborting unknown requests
await page.routeFromHAR('./recorded.har');
Respond using HAR, allowing unknown requests
await page.routeFromHAR('./recorded.har', {
  notFound: 'fallback',
});
Respond using HAR, for requests matching pattern
await page.routeFromHAR('./recorded.har', {
  url: /\.png$/i,
});
Record HAR file using network traffic
await page.routeFromHAR('./recorded.har', {
  update: true,
});

Websockets

Listen for new websocket requests
page.on('websocket', (ws) => {});
Get websocket URL
page.on('websocket', (ws) => {
  const url = ws.url();
});
Check if websocket is closed
page.on('websocket', (ws) => {
  const isClosed = ws.isClosed();
});

Wait for Websocket Events

Wait for websocket message sent event
page.on('websocket', (ws) => {
  const payload = await ws.waitForEvent('framesent');
});
Wait for websocket message received event
page.on('websocket', (ws) => {
  const payload = await ws.waitForEvent('framereceived');
});
Wait for websocket close event
page.on('websocket', (ws) => {
  const ws = await ws.waitForEvent('close');
});
Wait for websocket error event
page.on('websocket', (ws) => {
  const error = await ws.waitForEvent('socketerror');
});

Websocket Events

Listen for websocket close event
page.on('websocket', (ws) => {
  ws.on('close', (ws) => {});
});
Listen for websocket message sent event
page.on('websocket', (ws) => {
  ws.on('framesent', (payload) => {});
});
Listen for websocket message received event
page.on('websocket', (ws) => {
  ws.on('framereceived', (payload) => {});
});
Listen for websocket error event
page.on('websocket', (ws) => {
  ws.on('socketerror', (error) => {});
});

Keyboard Control

Type text with keyboard
await page.locator('input').focus();
await page.keyboard.type('Hello');
Type text with delay between presses
await page.keyboard.type('Hello', {
  delay: 100,
});
Press key combination
await page.keyboard.press('F12');
await page.keyboard.press('Control+c');
Press key combination with delay between presses
await page.keyboard.press('Control+v', {
  delay: 100,
});
Press key combination in specific element
await page.locator('textarea').press('Control+Z');

Low-level Keyboard Events

Dispatch keydown event
await page.keyboard.down('Shift');
await page.keyboard.down('a');
Dispatch keyup event
await page.keyboard.up('a');
await page.keyboard.up('Shift');
Dispatch keyboard events on specific element
const $input = page.locator('input');

await $input.dispatchEvent('keydown', {key: 'a'});
await $input.dispatchEvent('keyup', {key: 'b'});
await $input.dispatchEvent('keypress', {key: 'c'});

Mouse Control

Move mouse and click at specific coordinates
await page.mouse.click(100, 100);
Move mouse and click with delay between events
await page.mouse.click(100, 100, {
  delay: 100,
});
Click element with modifier keys
await page.locator('button').click({
  modifiers: ['Shift'],
});
Click element with custom X/Y coordinates
await page.locator('button').click({
  position: {x: 10, y: 10},
});
Move mouse and double-click at specific coordinates
await page.mouse.dblclick(100, 100);
Move mouse and double-click with delay between events
await page.mouse.dblclick(100, 100, {
  delay: 100,
});
Double-click element with modifier keys
await page.locator('button').dblclick({
  modifiers: ['Shift'],
});
Double-click element with custom X/Y coordinates
await page.locator('button').dblclick({
  position: {x: 10, y: 10},
});
Scroll mouse wheel horizontally
await page.mouse.wheel(0, 100);
Scroll mouse wheel vertically
await page.mouse.wheel(100, 0);
Hover element
await page.locator('button').hover();
Hover element with custom options
await page.locator('button').hover({
  modifiers: ['Meta'],
  position: {x: 10, y: 10},
});

Drag and Drop

Playwright supports manual and automatic drag-and-drop behavior. Use the automatic method, if possible.

Test drag and drop support
Drag and drop element (easy method)
const $source = page.locator('#source');
const $target = page.locator('#target');
await $source.dragTo($target);
Drag and drop with custom X/Y coordinates (easy method)
const $source = page.locator('#source');
const $target = page.locator('#target');
await $source.dragTo($target, {
  // relative to top-left corner
  sourcePosition: {x: 10, y: 10},
  targetPosition: {x: 10, y: 10},
});
Drag and drop element (manual method)
const $source = page.locator('#source');
const $target = page.locator('#target');

await $source.scrollIntoViewIfNeeded();
await $source.hover();
const sourceBox = await $source.boundingBox();
await page.mouse.move(
  sourceBox.x + sourceBox.width / 2, 
  sourceBox.y + sourceBox.height / 2,
);
await page.mouse.down();

await $target.scrollIntoViewIfNeeded();
await $target.hover();
await $target.hover(); // needed for some browsers
const targetBox = await $target.boundingBox();
await page.mouse.move(
  targetBox.x + targetBox.width / 2, 
  targetBox.y + targetBox.height / 2,
);
await page.mouse.up();

Low-level Mouse Events

Dispatch mousedown event
await page.mouse.down();
Dispatch mouseup event
await page.mouse.up();
Dispatch mousemove event
await page.mouse.move(100, 100);
Dispatch mousemove event across smooth steps
await page.mouse.move(100, 100, {
  steps: 5, // default: 1
});
Dispatch mouse events to specific element
const $button = page.locator('button');

await $button.dispatchEvent('click');
await $button.dispatchEvent('dblclick');
await $button.dispatchEvent('mousedown');
await $button.dispatchEvent('mouseup');

await $button.dispatchEvent('mousemove');

await $button.dispatchEvent('mouseover');
await $button.dispatchEvent('mouseleave');

await $button.dispatchEvent('mouseenter');
await $button.dispatchEvent('mouseout');

await $button.dispatchEvent('wheel');

Touchscreen Control

Tap screen at specific coordinates
await page.touchscreen.tap(100, 100);

Low-level Touchscreen Events

Dispatch touchscreen events to specific element
const $link = page.locator('a');

await $link.dispatchEvent('touchstart');
await $link.dispatchEvent('touchmove');
await $link.dispatchEvent('touchend');
await $link.dispatchEvent('touchcancel');

Pointer Control

Pointers refer to mouse, touch, and pen input devices. Use pointer events to simulate modern browser input support.

Dispatch pointer events to specific element
const $canvas = page.locator('canvas');

await $canvas.dispatchEvent('pointerdown');
await $canvas.dispatchEvent('pointerup');

await $canvas.dispatchEvent('pointermove');
await $canvas.dispatchEvent('pointercancel');

await $canvas.dispatchEvent('pointerenter');
await $canvas.dispatchEvent('pointerout');

await $canvas.dispatchEvent('pointerover');
await $canvas.dispatchEvent('pointerleave');

await $canvas.dispatchEvent('gotpointercapture');
await $canvas.dispatchEvent('lostpointercapture');

await $canvas.dispatchEvent('pointerrawupdate');

Events and Listeners

Playwright emits events on Browser, BrowserServer, BrowserContext, Page, WebSocket, and Worker objects. The following methods work across all.

Listen for events of a specific type
emitter.on('event', () => {});
Listen for event once
emitter.once('event', () => {});
Remove event listener
const listener = () => {};
emitter.off('event', listener);

Available Events

See dedicated sections for more details on these events.

Listen for browser events
// emitted when browser connection lost
broser.on('disconnected', (browser) => {});
Listen for browser server events
// emitted when browser server is closed
server.on('close', (browserServer) => {});
Listen for context events
// emitted when new page is created
context.on('page', (page) => {});

// emitted when new background page is created
context.on('backgroundpage', (page) => {});

// emitted when basic dialog appears
// e.g. alert, prompt, confirm, or beforeunload
context.on('dialog', (dialog) => {});

// emitted on each new console message
context.on('console', (msg) => {});

// emitted when new web worker is created
context.on('serviceworker', (worker) => {});

// emitted on unhandled page error
context.on('weberror', (error) => {});

// emitted when browser context is closed
context.on('close', (context) => {});
Listen for context network events
// emitted on new network request
context.on('request', (request) => {});

// emitted on failed network request
context.on('requestfailed', (request) => {});

// emitted on successful network request
context.on('requestfinished', (request) => {});

// emitted on new network response
context.on('response', (response) => {});
Listen for page events
// emitted when new URL begins to load
page.on('load', (page) => {});

// emitted when DOM is loaded, but not all resources
page.on('domcontentloaded', (page) => {});

// emitted when basic dialog appears
// e.g. alert, prompt, confirm, or beforeunload
page.on('dialog', (dialog) => {});

// emitted when new file chooser appears
page.on('filechooser', (chooser) => {});

// emitted when new download begins
page.on('download', (download) => {});

// emitted on each new console message
page.on('console', (msg) => {});

// emitted when new popup page is created
page.on('popup', (page) => {});

// emitted when new web worker is created
page.on('worker', (worker) => {});

// emitted on unhandled page error
page.on('pageerror', (error) => {});

// emitted when page is closed
page.on('close', (page) => {});

// emitted when page crashes
page.on('crash', (page) => {});
Listen for page frame events
// emitted when new frame is attached
page.on('frameattached', (frame) => {});

// emitted when frame is detached
page.on('framedetached', (frame) => {});

// emitted when frame navigates to new URL
page.on('framenavigated', (frame) => {});
Listen for page network events
// emitted on new network request
page.on('request', (request) => {});

// emitted on failed network request
page.on('requestfailed', (request) => {});

// emitted on successful network request
page.on('requestfinished', (request) => {});

// emitted on new network response
page.on('response', (response) => {});

// emitted when new websocket is created
page.on('websocket', (ws) => {});
Listen for websocket events
// emitted when websocket is closed
ws.on('close', (ws) => {});

// emitted when websocket message is received
ws.on('framereceived', (frame) => {});

// emitted when websocket message is sent
ws.on('framesent', (frame) => {});

// emitted when websocket error occurs
ws.on('socketerror', (error) => {});
Listen for web worker events
// emitted when worker is closed
worker.on('close', (worker) => {});

Debugging

Do slowMo on .connect() and .launch()
Do logger on .connect() and .launch()
Do devtools on .connect() and .launch()
Do headless on .connect() and .launch()
Do tracesDir on .connect() and .launch()
Configure custom logging
const browser = await pw.chromium.launch({
  logger: {
    isEnabled: (name, sev) => sev === 'error',
    log: (name, sev, msg, args) => console.debug(name, msg),
  },
});

Tracing

Record context trace
await context.tracing.start();
Record context trace files with custom prefix
await context.tracing.start({
  name: 'checkout-process',
});
Record screenshots for trace
await context.tracing.start({
  screenshots: true,
});
Record snapshots of all actions for trace
await context.tracing.start({
  snapshots: true,
});
Include source files in trace
await context.tracing.start({
  sources: true,
});
Stop recording context trace

const trace = await context.tracing.stop();
Save context trace to specific file
await context.tracing.stop({
  path: './trace.json', 
  // default: browser.launch({tracesDir})
});

Chunk Tracing

Playwright supports saving select chunks from a tracing session, rather than the whole thing.

Record trace chunk
await context.tracing.start(); // required
await context.tracing.startChunk();
Record trace chunk with custom prefix
await context.tracing.start(); // required
await context.tracing.startChunk({
  name: 'shopping-cart',
});
Stop recording trace chunk
await context.tracing.stopChunk();
Save trace chunk to specific file
await context.tracing.stopChunk({
  path: './trace.json',
  // default: browser.launch({tracesDir})
});
End chunk tracing session
await context.tracing.stop();

Chromium Browser Trace

These methods will trace the entire browser process. Only supported by Chromium-based browsers.

Record browser trace
await browser.startTracing();
Record browser trace for specific page
await browser.startTracing(page);
Record browser trace to file
await browser.startTracing(null, {
  path: './trace.json',
  // default: browser.launch({tracesDir})
});
Record screenshots for trace
await browser.startTracing(null, {
  screenshots: true,
});
Stop recording browser trace
const trace = await browser.stopTracing();

Automate Everything.

Tired of managing a fleet of fickle browsers? Sick of skipping e2e tests and paying the piper later?

Sign up now for free access to our headless browser fleet…

Get started today!