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
DOCS
npx playwright codegen
Run your script
node src/example.js # with JS
npx tsx src/example.ts # with TS

Launch Browsers Locally

Launch Chromium
API
const chromium = await pw.chromium.launch();
Launch Firefox
API
const firefox = await pw.firefox.launch();
Launch Webkit
API
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
API
const userDataDir = './userData';
const context = await pw.chromium
  .launchPersistentContext(userDataDir);
Configure persistent context options
API
// 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
API
const browser = await pw.chromium.launch({
  headless: false,
});
Open browser devtools on launch
API
const browser = await pw.chromium.launch({
  devtools: true,
});
Set custom downloads path
API
const browser = await pw.chromium.launch({
  downloadsPath: './downloads',
});
Set custom timeout for browser launch
API
const browser = await pw.chromium.launch({
  timeout: 1000 * 60,
});
Handle process termination signals
API
const browser = await pw.chromium.launch({
  // all default to true
  handleSIGHUP: false
  handleSIGINT: false // ctrl+c
  handleSIGTERM: false
});

Proxy

Proxy browser traffic
API
const browser = await pw.chromium.launch({
  proxy: {
    server: 'https://proxy.com:8080',
  },
});
Proxy browser traffic with authentication
API
const browser = await pw.chromium.launch({
  proxy: {
    server: 'https://proxy.com:8080',
    username: 'user',
    password: 'pass',
  },
});
Bypass browser proxy for specific domains
API
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
API
const browser = await pw.chromium.launch({
  // defaults to `process.env`
  env: {
    ...process.env,
    SOCKS_SERVER: 5,
  },
});
Launch a specific browser executable
API
const browser = await pw.chromium.launch({
  executablePath: '/path/to/brave-browser',
});
Launch browser with custom CLI args
API
const browser = await pw.chromium.launch({
  args: ['--disable-gl-drawing-for-tests'],
});
Disable specific default CLI args
API
const browser = await pw.chromium.launch({
  ignoreDefaultArgs: ['--hide-scrollbars'],
});
Disable all default CLI args
API
const browser = await pw.chromium.launch({
  ignoreDefaultArgs: true,
});

Chromium-Based Config

Enable Chromium sandboxing
API
const browser = await pw.chromium.launch({
  chromiumSandbox: true, // default: false
});
Set Chromium user preferences (chrome://flags)
DOCSAPI
const browser = await pw.chromium.launch({
  args: [
    '--enable-features=NetworkService,BackgroundFetch',
    '--disable-features=IsolateOrigins,BackForwardCache',
  ],
});
Set Chromium CLI args
DOCSAPI
const browser = await pw.chromium.launch({
  args: [
    '--aggressive-cache-discard',
  ],
  ignoreDefaultArgs: [
    '--mute-audio',
  ],
});
Enable “new” Chromium headless mode
DOCSAPI
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)
DOCSAPI
const browser = await pw.firefox.launch({
  firefoxUserPrefs: {
    'threads.use_low_power.enabled': true,
  },
});
Set Firefox CLI args
DOCSAPI
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
DOCSAPI
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
API
const browser = await pw.chromium.launchServer();
const wsEndpoint = await browser.wsEndpoint();
Connect to a browser server
API
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint);
Send custom headers to browser server
API
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
API
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
API
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  exposeNetwork: 'localhost',
});
Expose all loopbacks to browser server
API
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
  exposeNetwork: '<loopback>',
});
Expose specific IPs to browser server
API
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
API
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
API
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)
API
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
API
const chrome = await pw.chromium.launch({
  channel: 'chrome',
});
Launch Chrome Beta
API
const chrome = await pw.chromium.launch({
  channel: 'chrome-beta',
});
Launch Chrome Dev
API
const chrome = await pw.chromium.launch({
  channel: 'chrome-dev',
  executablePath: '/path/to/chrome-dev',
});
Launch Chrome Canary
API
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
API
const chrome = await pw.chromium.launch({
  channel: 'edge',
});
Launch Edge Beta
API
const chrome = await pw.chromium.launch({
  channel: 'edge-beta',
});
Launch Edge Dev
API
const chrome = await pw.chromium.launch({
  channel: 'edge-dev',
});
Launch Edge Canary
API
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
API
const chromium = await pw.firefox.launch();
Launch Firefox Beta
API
const chrome = await pw.firefox.launch({
  channel: 'firefox-beta',
});
Launch Firefox Dev
API
const chrome = await pw.firefox.launch({
  channel: 'firefox-beta', // yes, this is correct
  executablePath: '/path/to/firefox-dev',
});
Launch Firefox Nightly
API
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
API
await page.screenshot();
Screenshot entire page
API
await page.screenshot({fullPage: true});
Screenshot specific element
API
const $element = page.locator('h1');
await $element.screenshot();
Resize viewport before screenshot
API
await page.setViewportSize({
  width: 2000, 
  height: 1000,
});
await page.screenshot();
Screenshot custom HTML/CSS content
API
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
API
await page.screenshot({
  style: './path/to/screenshot.css',
});
Apply custom raw styles during screenshot
API
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
API
await page.screenshot({
  mask: [
    page.locator('input'),
    page.getByRole('button'),
    page.locator('.sensitive'),
  ],
});
Set custom mask color
API
await page.screenshot({
  // default: #FF00FF (pink)
  maskColor: '#00FF00',
});
Clip page screenshot to specific region
API
await page.screenshot({
  clip: {
    x: 0, 
    y: 0, 
    width: 100, 
    height: 100,
  },
});

Rendering Options

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

PNG Output

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

JPEG Output

Output image as JPEG buffer
API
const image = await page.screenshot({
  type: 'jpeg',
});
Output JPEG image with custom quality
API
const image = await page.screenshot({
  type: 'jpeg',
  quality: 80,
});
Save image as JPEG file
API
await page.screenshot({
  path: 'screenshot.jpg',
});
Save JPEG image with custom quality
API
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
API
const pdf = await page.pdf();
Save PDF to file
API
await page.pdf({
  path: 'page.pdf',
});

Page Size

Set page size to conventional format
API
await page.pdf({
  // supported:
  // `Letter`, `Legal`, `Tabloid`, `Ledger`,
  // `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`
  format: 'Letter', // default
});
Enable landscape orientation for format
API
await page.pdf({
  landscape: true, // default: false
});
Set page size to custom dimensions
API
await page.pdf({
  // supported: `px`, `in`, `cm`, `mm`
  width: '8.5in',
  height: '11in',
});
Prefer CSS page size, if available
API
await page.pdf({
  preferCSSPageSize: true, // default: false
});
Set page size using CSS
API
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
API
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
API
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
API
await page.pdf({
  displayHeaderFooter: true, // default: false
});
Set custom HTML/CSS header template
API
// 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
API
// 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
API
await page.pdf({
  printBackground: true, // default: false
});
Scale page before laying out content
API
await page.pdf({
  scale: 2, // default: 1
});

PDF Options

Output only certain page ranges
API
await page.pdf({
  // default: all pages
  pageRanges: '1-3, 5',
});
Enable tagging for accessibility and reflow
API
await page.pdf({
  tagged: true, // default: false
});
Enable outline for PDF navigation
API
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
API
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
API
const context = await browser.newContext({
  recordVideo: {
    dir: './videos',
    size: {
      width: 1920, // default: 800
      height: 1080, // default: 800
    },
  },
});
Get video output path for current page
API
const videoPath = page.video.path();
Copy video of page to custom location
API
await page.video.saveAs('custom.webm');
Delete video of current page
API
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
API
const context = await browser.newContext({
  recordVideo: {
    dir: './videos',
  },
});
(2) Create blackout overlay
API
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
API
const page = await context.newPage();
await page.goto('https://www.browsercat.com');
const $blackout = page.locator('#blackout');
(4) Toggle blackout to create slices
API
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
API
await page.video.saveAs('sliceable.webm');
(6) Save video files to disk
API
await context.close();
(7) Split video into slices using ffmpeg
WEB
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
DOCSAPI
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
DOCS
npx playwright codegen
Run your tests
DOCS
npx playwright test
Run your tests in UI mode
DOCS
npx playwright test --ui
Show test results in browser
DOCS
npx playwright show-report

Test Isolation

TODO: Working with TestInfo, workers, etc.

Parameterize Tests

Parameterize tests across multiple projects
DOCS
// 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
DOCS
import {defineConfig} from '@playwright/test';

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

Output Files

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

Configure Exit Criteria

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

Assertion Settings

Configure defaults for numerous expect methods.

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

Overrides

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

test('test', async () => {});
Override runner config for test group
API
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
API
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
API
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
API
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
API
export default defineConfig({
  use: {
    testIdAttribute: 'data-bcat-id',
  },
});
Show browser window during tests
API
export default defineConfig({
  use: {
    headless: false,
  },
});
Screenshot tests automatically
API
export default defineConfig({
  use: {
    screenshot: 'on', // or 'only-on-failure' | 'off'
  },
});
Record video of tests automatically
API
export default defineConfig({
  use: {
    video: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'off'
  },
});
Record trace data for tests automatically
API
export default defineConfig({
  use: {
    trace: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'off'
  },
});

Configure Behavior

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

Configure Network Traffic

Enable relative URLs with custom base URL
API
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
API
export default defineConfig({
  use: {
    httpCredentials: {
      username: 'user',
      password: 'pass',
    },
  },
});
Send custom default HTTP headers with requests
API
export default defineConfig({
  use: {
    extraHTTPHeaders: {
      'X-My-Header': 'value',
    },
  },
});
Emulate offline network conditions
API
export default defineConfig({
  use: {
    offline: true,
  },
});
Bypass Content Security Policy (CSP)
API
export default defineConfig({
  use: {
    // Use when you encounter security issues
    // injecting custom scripts or styles
    bypassCSP: true,
  },
});
Ignore HTTPS errors
API
export default defineConfig({
  use: {
    ignoreHTTPSErrors: process.env.NODE_ENV === 'development',
  },
});
Route all traffic through a proxy server
API
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
DOCS
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
API
export default defineConfig({
  use: {
    browserName: 'firefox', // or 'webkit' | 'chromium'
  },
});
Select userland browser to use for tests
API
export default defineConfig({
  use: {
    channel: 'chrome', // or 'chrome-beta' | 'chrome-dev' | 'msedge' | 'msedge-beta' | 'msedge-dev'
  },
});
Set user’s preferred color scheme
API
export default defineConfig({
  use: {
    colorScheme: 'dark', // or 'light' | 'no-preference'
  },
});
Set specific browser user agent
API
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
API
export default defineConfig({
  use: {
    viewport: { width: 1920, height: 1080 },
  },
});
Set browser CLI launch arguments
API
export default defineConfig({
  use: {
    launchOptions: {
      args: ['--no-sandbox'],
    },
  },
});
Configure additional browser context options
API
export default defineConfig({
  use: {
    contextOptions: {
      reducedMotion: 'reduce',
      strictSelectors: true,
    },
  },
});

Emulate Devices

Emulate specific devices quickly
DOCS
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
API
export default defineConfig({
  use: {
    deviceScaleFactor: 2,
  },
});
Emulate mobile device
API
export default defineConfig({
  use: {
    isMobile: true,
  },
});
Emulate touch screen support
API
export default defineConfig({
  use: {
    hasTouch: true,
  },
});

Configure Locale

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

Overrides

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

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

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

Writing Tests

Write a basic test
DOCS
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
API
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
API
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
API
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
API
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
API
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
API
test('long test', async () => {
  await step('step 1', async () => {
    throw new Error();
  }, {box: true});
});
Create reusable test steps
API
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
API
test.only('working on this', async () => {});
Only run a specific test groups
API
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
API
test('slow test', async () => {
  test.slow();
});
Mark a slow test conditionally
API
test('slow on webkit', async ({browserName}) => {
  test.slow(browserName === 'webkit');
});
Mark slow tests in file conditionally
API
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
API
test.skip('skipped for now', async () => {});

test('skipped for now', async () => {
  test.skip();
});
Skip a specific test group
API
test.describe.skip('skipped for now', async () => {});
Skip a test conditionally
API
test('skipped on webkit', async ({browserName}) => {
  test.skip(browserName === 'webkit', `Not supported.`);
});
Skip tests in file conditionally
API
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
API
test.fixme('broken for now', async () => {});

test('broken for now', async () => {
  test.fixme();
});
Mark a test group as todo
API
test.describe.fixme('broken for now', async () => {});
Mark a todo test conditionally
API
test('broken on webkit', async ({browserName}) => {
  test.fixme(browserName === 'webkit', `Not supported.`);
});
Mark todo tests in file conditionally
API
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
API
test('always', async () => {
  test.fail();
});
Mark a failing test conditionally
API
test('broken on webkit', async ({browserName}) => {
  test.fail(browserName === 'webkit', `Not supported.`);
});
Mark failing tests in file conditionally
API
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
API
test('test', {
  tags: ['@slow', '@feature-auth'],
}, async () => {});
Add custom tags to test group
API
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
API
test('test', {
  annotation: [{
    type: 'issue', 
    description: '@browsercat/cookbook/issues/1',
  }],
}, async () => {});
Add custom annotions to test group
API
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
DOCSAPI
// 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
DOCSAPI
// 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
DOCSAPI
// 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
API
test.beforeAll(async () => {
  // runs once before all tests
});

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

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

test('test', async () => {});
Run code after each test in file
API
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
API
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
API
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
API
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
API
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
API
test('test', async ({page}) => {
  // `page` is exclusive to this test
  await page.goto('https://www.browsercat.com');
});
Access context fixture in test
API
test('test', async ({context}) => {
  // `context` is exclusive to this test
  const page = await context.newPage();
});
Access browser fixture in test
API
test('test', async ({browser}) => {
  // `browser` is shared across worker thread
  const context = await browser.newContext();
});
Access request fixture in test
API
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
API
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
API
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
API
export const test = base.extend({
  page: [async ({page}, use) => {
    page.addInitScript('tests/init.js');
    await use(page);
  }],
});
Combine multiple custom fixture assignments
DOCSAPI
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
API
export const test = base.extend({
  fixture: [async (ctx, use) => {
    await use(null);
  }, {auto: true}],
});
Set custom timeout for fixture execution
API
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
API
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
API
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
API
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
API
type WorkerOptions = {
  siteUrl: string;
};

export const test = base.extend<{}, WorkerOptions>({
  siteUrl: [
    'https://www.browsercat.com', 
    {scope: 'worker', option: true},
  ],
});
Customize fixture for specific tests
API
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)
API
expect(1).toBe(1);
Assert truthy value
API
expect(1).toBeTruthy();
Assert falsy value
API
expect(0).toBeFalsy();

Type Assertions

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

Value Assertions

Number Assertions

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

String Assertions

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

Object Assertions

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

Array Assertions

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

Set Assertions

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

Error Assertions

Assert function throws error with message
API
expect(() => { throw new Error('hello') })
  .toThrow('hello');
Assert function throws error with message pattern
API
expect(() => { throw new Error('hello') })
  .toThrow(/^hell/);
Assert function throws error type
API
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
DOCS
await expect.poll(async () => {
  return Math.random();
}).toBeGreaterThan(0.1);
Poll function value with custom intervals
DOCS
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
DOCS
await expect.poll(async () => {
  return Math.random();
}, {
  // default: 1000 * 5
  timeout: 1000 * 60,
}).toBeGreaterThan(0.1);
Poll function success (returns with no errors)
DOCS
await expect(async () => {
  expect(Math.random()).toBeGreaterThan(0.1);
}).toPass();
Poll function success with custom intervals
DOCS
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
DOCS
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
API
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
API
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
API
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
API
expect(0).toBe(expect.anything());
Assert number roughly equals match
API
expect(0.1 + 0.2).toBe(expect.closeTo(0.3));
Assert string includes substring
API
expect('hello')
  .toEqual(expect.stringContaining('hell'));
Assert string matches pattern
API
expect('hello')
  .toEqual(expect.stringMatching(/^hell/));
Assert array includes subset of values
API
expect([1, 2, 3])
  .toEqual(expect.arrayContaining([1, 2]));
Assert object includes subset of properties
API
expect({a: 1, b: 2})
  .toEqual(expect.objectContaining({a: 1}));
Assert typeof value
API
expect(1).toBe(expect.any(Number));
expect(page).toBe(expect.any(Page));

Page Assertions

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

Element Property Assertions

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

ID Assertions

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

Class Assertions

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

Attribute Assertions

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

CSS Property Assertions

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

Text Content Assertions

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

Element Interaction Assertions

Element Visibility Assertions

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

Form & Input Assertions

Assert element is focused
API
await expect(locator).toBeFocused();
Assert element is enabled
API
await expect(locator).toBeEnabled();
Assert element is disabled
API
await expect(locator).toBeEnabled({enabled: false});
Assert element is checked
API
await expect(locator).toBeChecked();
Assert element is not checked
API
await expect(locator).toBeChecked({checked: false});
Assert element is editable
API
await expect(locator).toBeEditable();
Assert element is not editable
API
await expect(locator).toBeEditable({editable: false});
Assert element input value
API
await expect(locator).toHaveValue('123-45-6789');
Assert element input value matches pattern
API
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
API
await expect(locators).toHaveCount(3);
Assert list of elements has exact text
API
await expect(locators).toHaveText(['hello', /goodbye/]);
Assert list of elements contains substrings
API
await expect(locators).toContainText(['hell', /^good/]);
Assert list of element input values
API
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
API
await expect(page).toHaveScreenshot();
Assert image matches named screenshot
API
// compare to a single image shared across tests
await expect(page).toHaveScreenshot('home-page.png');
Assert cropped page matches screenshot
API
await expect(page).toHaveScreenshot({
  clip: {x: 0, y: 0, width: 100, height: 100},
});
Assert page roughly matches screenshot
API
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
API
await expect(locator).toHaveScreenshot();
Assert element matches named screenshot
API
// compare to a single image shared across tests
await expect(locator).toHaveScreenshot('home-hero.png');
Assert cropped element matches screenshot
API
await expect(locator).toHaveScreenshot({
  clip: {x: 0, y: 0, width: 100, height: 100},
});
Assert element roughly matches screenshot
API
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
API
expect(customImage).toMatchSnapshot();
Assert image matches named snapshot
API
// compare to a single image shared across tests
expect(customImage).toMatchSnapshot('saturated.png');
Assert image roughly matches snapshot
API
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
API
test('send fetch request', async ({request}) => {
  const res = await request.fetch('/api/logout', {
    method: 'GET',
  });

  expect(res.ok()).toBeTruthy();
});
Assert cookies after fetch request
API
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
API
expect(1).not.toBe(2);
Assert value without stopping execution
API
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
API
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
DOCS
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
DOCS
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
DOCS
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
DOCSDOCS
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
API
export default defineConfig({
  testDir: './tests', // default './'
});
Include test files matching glob
API
export default defineConfig({
  testMatch: '**/*.spec.{ts,js}',
});
Include test files matching regex
API
export default defineConfig({
  testMatch: /\.spec\.(ts|js)$/,
});
Include test files matching any pattern
API
export default defineConfig({
  testMatch: [
    '**/*.spec.{ts,js}',
    /\.spec\.(ts|js)$/,
  ],
});
Ignore test files matching glob
API
export default defineConfig({
  testIgnore: '**/*.ignore.*',
});
Ignore test files matching regex
API
export default defineConfig({
  testIgnore: /(\.ignore\.|\/ignore\/)/,
});
Ignore test files matching any pattern
API
export default defineConfig({
  testIgnore: [
    '**/*.ignore.*',
    '**/ignore/**/*',
    /(\.ignore\.|\/ignore\/)/,
  ],
});

Filter Tests by Titles

Only run tests with titles matching regex
API
export default defineConfig({
  grep: /contact/i,
});
Only run tests with titles matching any regex
API
export default defineConfig({
  grep: [/button/i, /input/i],
});
Only run tests with titles not matching regex
API
export default defineConfig({
  grepInvert: /@flaky/i,
});
Only run tests with titles not matching any regex
API
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
API
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
API
export default defineConfig({
  project: [{
    name: 'projects',
    testDir: './tests/feature-1',
  }],
});
Include test files matching patterns
API
export default defineConfig({
  project: [{
    name: 'projects',
    testMatch: [
      'feature-1/**/*.spec.{ts,js}',
      /feature-1\/.+\.spec\.(ts|js)$/,
    ],
  }],
});
Only run tests with titles matching patterns
API
export default defineConfig({
  project: [{
    name: 'auth',
    grep: [/^Auth/, /(login|logout)/i],
  }],
});
Override test environment options
API
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
API
export default defineConfig({
  project: [{
    name: 'before',
  }, {
    name: 'tests',
    dependencies: ['before'],
  }],
});
Run target project after multiple projects
API
export default defineConfig({
  project: [{
    name: 'tasks',
  }, {
    name: 'users',
  }, {
    name: 'workflows',
    dependencies: ['tasks', 'users'],
  }],
});
Run cleanup after project dependencies
DOCSAPI
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
DOCSAPI
export default defineConfig({
  fullyParallel: false, // default
});
Run all tests in all files in parallel
DOCSAPI
export default defineConfig({
  fullyParallel: true, // default: false
});
Set number of parallel workers
DOCSAPI
export default defineConfig({
  workers: 3, // default: 50% logical CPUs
});
Set relative number of parallel workers
DOCSAPI
export default defineConfig({
  workers: '100%', // percentage of logical CPUs
});
Disable all parallelism (forcefully)
DOCSAPI
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
API
test.describe.configure({
  mode: 'parallel', // default 'serial'
});

test('test', async () => {});
Run a test group’s tests in parallel
API
test.describe('group', async () => {
  test.describe.configure({
    mode: 'parallel', // default 'serial'
  });
  
  test('test', async () => {});
});
Run test group serially, even after retries
API
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
DOCSAPI
export default defineConfig({
  shard: {
    total: process.env.PW_SHARD_TOTAL ?? 1, 
    current: process.env.PW_SHARD_CURRENT ?? 1,
  },
});
Configure reporters for sharded test results
DOCS
export default defineConfig({
  reporter: [
    ['blob', {
      outputDir: 'test-results',
      fileName: `report-${process.env.CI_BUILD_ID}.zip`,
    }],
  ],
});
Combine sharded test results into single report
DOCS
npx playwright merge-reports \
  --reporter html \
  ./reports

Test Reporting & Configuration

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

Reporter Configuration

Configure test reporters
API
export default defineConfig({
  reporter: [
    ['list'],
    ['json', {outputFile: 'test-results.json'}],
    ['junit', {outputFile: 'test-results.xml'}],
  ],
});
Attach JSON metadata to test results
API
export default defineConfig({
  metadata: {
    region: 'us-east-1',
    date: '2021-12-21',
  },
});
Automatically capture test screenshots
API
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
API
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
API
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
DOCS
export default defineConfig({
  reporter: [
    ['list', {
      printSteps: false, // print test steps
      omitFailures: false, // skip detailed failure messages
    }],
  ],
});
Output single line summary of test run
DOCS
export default defineConfig({
  reporter: [
    ['line', {
      omitFailures: false, // skip detailed failure messages
    }],
  ],
});
Output row of dots indicating each test’s status
DOCS
export default defineConfig({
  reporter: [
    ['dot', {
      omitFailures: false, // skip detailed failure messages
    }],
  ],
});
Annotate Github workflow with test results
DOCS
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
DOCS
export default defineConfig({
  reporter: [
    ['html', {
      outputFolder: 'test-results',
      open: 'never', // or 'always' | 'on-failure'
    }],
  ],
});
Output JSON report of test run
DOCS
export default defineConfig({
  reporter: [
    ['json', {
      outputFile: 'results.json',
    }],
  ],
});
Output JUnit report of test run
DOCS
export default defineConfig({
  reporter: [
    ['junit', {
      outputFile: 'results.xml',
    }],
  ],
});
Output test result “blob” for post-processing
DOCS
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
DOCS
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
DOCSAPI
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
DOCS
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
DOCS
npx playwright merge-reports \
  .test/reports
Merge sharded reports with custom reporters
DOCS
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
API
const browserType = browser.browserType();
Get browser version
API
const version = await browser.version();
Check if browser is connected
API
const isConnected = browser.isConnected();
Close browser (force)
API
await browser.close();
Close browser (gentle)
API
await Promise.all(
  browser.contexts()
    .map((context) => context.close()),
);
await browser.close();
Close browser with a reason
API
await browser.close({reason: 'success'});
Listen for browser disconnection event
API
browser.on('disconnected', (browser) => {});

Contexts (aka User Sessions)

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

Pages

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

Page Navigation

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

Working with Navigation

Wait for the page to navigate to a new URL
API
await page.waitForURL('https://www.browsercat.com');
Navigate to URL and wait for content to load
API
await page.goto('https://www.browsercat.com', {
  waitUntil: 'domcontentloaded', // default: 'load'
});
Reload page and wait for content to load
API
await page.reload({
  waitUntil: 'domcontentloaded', // default: 'load'
});
Catch page that opens in new tab
API
page.locator('a[target="_blank"]').first().click();
const newTabPage = await page.waitForEvent('popup');
Catch page that opens in pop-up window
API
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
API
const frame = page.mainFrame();
Get frame by name attribute
API
const frame = page.frame({
  name: /^footer-ad$/, // or exact string match
});
Get frame by url attribute
API
const frame = page.frame({
  url: /\/footer-ad\.html$/, // or exact string match
});
Get all frames for current page
API
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
API
const $frame = page.frameLocator('#soundcloud-embed');
Create frame locator from locator
API
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
DOCSAPI
const $alert = await page.getByRole('alert');
Select element by label
DOCSAPI
const $input = await page.getByLabel('Username');
Select element by placeholder
DOCSAPI
const $input = await page.getByPlaceholder('Search');
Select element by title
DOCSAPI
const $el = await page.getByTitle('Welcome');
Select element by alt text
DOCSAPI
const $image = await page.getByAltText('Logo');
Select element by text
DOCSAPI
const $button = await page.getByText('Submit');
Select element by test id
DOCSAPI
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
DOCSAPI
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
API
const $button = await page.getByRole('button', { 
  name: /(submit|save)/i, // or string
  exact: true, // default: false
});
Select elements by checked state
API
const $checkbox = await page.getByRole('checkbox', { 
  checked: true, // or false
});
Select elements by selected state
API
const $option = await page.getByRole('option', { 
  selected: true, // or false
});
Select elements by expanded state
API
const $menu = await page.getByRole('menu', { 
  expanded: true, // or false
});
Select elements by pressed state
API
const $button = await page.getByRole('button', { 
  pressed: true, // or false
});
Select elements by disabled state
API
const $input = await page.getByRole('textbox', { 
  disabled: true, // or false
});
Select elements by depth level
API
const $heading = await page.getByRole('heading', { 
  level: 2, // etc.
});
Match hidden elements with ARIA locators
API
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
API
const $icon = await page.locator('button > svg[width]');
Select elements by tag name
API
const $header = await page.locator('header');
Select elements by tag attribute
API
const $absLinks = await page.locator('[href^="https://"]');
Select elements by CSS class
API
const $buttons = await page.locator('.btn');
Select elements by CSS id
API
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
API
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
API
const res = await page.request.fetch(
  'https://browsercat.com',
  {method: 'GET'},
);
Flush network traffic cache
API
await page.request.dispose();
Set default HTTP headers on all requests
API
await page.setExtraHTTPHeaders({
  'X-Agent': 'production-test-bot',
});

Wait for Network Traffic

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

Network Events

Listen for new network requests
API
page.on('request', (req) => {});
Listen for successful network requests
API
page.on('requestfinished', (req) => {});
Listen for failed network requests
API
page.on('requestfailed', (req) => {});
Listen for network responses
API
page.on('response', (res) => {});
Listen for new websocket requests (page only)
API
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
API
await page.route('**/*', (route) => {
  route.continue();
});
Route requests matching glob
API
await page.route('**/*.png', (route) => {
  route.continue();
});
Route requests matching regex
API
await page.route(/\.json$/i, (route) => {
  route.continue();
});
Route request only once
API
await page.route('**/*.png', (route) => {
  route.continue();
}, {times: 1}); // or any number
Remove all handlers from route
API
await page.unroute('**/*.png');
Remove specific handler from route
API
await page.unroute('**/*.png', pngHandler);
await page.unroute(/\.json$/i, jsonHandler);
Remove all network routes immediately
API
await page.unrouteAll();
Remove all network routes as they complete
API
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
API
await page.route('**/*', (route) => {
  route.continue();
});
Modify request before allowing to proceed
API
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
API
await page.route('**/*', (route) => {
  const response = await route.fetch();
  
  route.fulfill({
    response,
    json: {
      ...await response.json(),
      test: true,
    },
  })
});
Fulfill routed request with custom response
API
await page.route('**/*', (route) => {
  route.fulfill({
    status: 404,
    json: {message: 'not found'},
  });
});
Fulfill routed request with local file
API
await page.route('**/*.png', (route) => {
  route.fulfill({
    path: './1-pixel.png',
  });
});
Fallback to earlier-defined matching route handler
API
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
API
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
API
await page.route('**/*', (route) => {
  route.abort();
});
Abort routed request with custom error
API
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
API
await page.routeFromHAR('./recorded.har');
Respond using HAR, allowing unknown requests
API
await page.routeFromHAR('./recorded.har', {
  notFound: 'fallback',
});
Respond using HAR, for requests matching pattern
API
await page.routeFromHAR('./recorded.har', {
  url: /\.png$/i,
});
Record HAR file using network traffic
API
await page.routeFromHAR('./recorded.har', {
  update: true,
});

Websockets

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

Wait for Websocket Events

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

Websocket Events

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

Keyboard Control

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

Low-level Keyboard Events

Dispatch keydown event
API
await page.keyboard.down('Shift');
await page.keyboard.down('a');
Dispatch keyup event
API
await page.keyboard.up('a');
await page.keyboard.up('Shift');
Dispatch keyboard events on specific element
API
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
API
await page.mouse.click(100, 100);
Move mouse and click with delay between events
API
await page.mouse.click(100, 100, {
  delay: 100,
});
Click element with modifier keys
API
await page.locator('button').click({
  modifiers: ['Shift'],
});
Click element with custom X/Y coordinates
API
await page.locator('button').click({
  position: {x: 10, y: 10},
});
Move mouse and double-click at specific coordinates
API
await page.mouse.dblclick(100, 100);
Move mouse and double-click with delay between events
API
await page.mouse.dblclick(100, 100, {
  delay: 100,
});
Double-click element with modifier keys
API
await page.locator('button').dblclick({
  modifiers: ['Shift'],
});
Double-click element with custom X/Y coordinates
API
await page.locator('button').dblclick({
  position: {x: 10, y: 10},
});
Scroll mouse wheel horizontally
API
await page.mouse.wheel(0, 100);
Scroll mouse wheel vertically
API
await page.mouse.wheel(100, 0);
Hover element
API
await page.locator('button').hover();
Hover element with custom options
API
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)
DOCSAPI
const $source = page.locator('#source');
const $target = page.locator('#target');
await $source.dragTo($target);
Drag and drop with custom X/Y coordinates (easy method)
DOCSAPI
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)
DOCSAPI
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
API
await page.mouse.down();
Dispatch mouseup event
API
await page.mouse.up();
Dispatch mousemove event
API
await page.mouse.move(100, 100);
Dispatch mousemove event across smooth steps
API
await page.mouse.move(100, 100, {
  steps: 5, // default: 1
});
Dispatch mouse events to specific element
API
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
API
await page.touchscreen.tap(100, 100);

Low-level Touchscreen Events

Dispatch touchscreen events to specific element
API
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
API
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
API
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
API
// emitted when browser connection lost
broser.on('disconnected', (browser) => {});
Listen for browser server events
API
// emitted when browser server is closed
server.on('close', (browserServer) => {});
Listen for context events
API
// 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
API
// 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
API
// 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
API
// 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
API
// 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
API
// 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
API
// 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
API
await context.tracing.start();
Record context trace files with custom prefix
API
await context.tracing.start({
  name: 'checkout-process',
});
Record screenshots for trace
API
await context.tracing.start({
  screenshots: true,
});
Record snapshots of all actions for trace
API
await context.tracing.start({
  snapshots: true,
});
Include source files in trace
API
await context.tracing.start({
  sources: true,
});
Stop recording context trace
API

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

Chromium Browser Trace

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

Record browser trace
API
await browser.startTracing();
Record browser trace for specific page
API
await browser.startTracing(page);
Record browser trace to file
API
await browser.startTracing(null, {
  path: './trace.json',
  // default: browser.launch({tracesDir})
});
Record screenshots for trace
API
await browser.startTracing(null, {
  screenshots: true,
});
Stop recording browser trace
API
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!