Playwright Cheatsheet for Javascript & Typescript
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 behaviorDOCS
npx playwright codegen
Run your script
node src/example.js # with JS
npx tsx src/example.ts # with TS
Launch Browsers Locally
Launch ChromiumAPI
const chromium = await pw.chromium.launch();
Launch FirefoxAPI
const firefox = await pw.firefox.launch();
Launch WebkitAPI
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 dataAPI
const userDataDir = './userData';
const context = await pw.chromium
.launchPersistentContext(userDataDir);
Configure persistent context optionsAPI
// 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 launchAPI
const browser = await pw.chromium.launch({
headless: false,
});
Open browser devtools on launchAPI
const browser = await pw.chromium.launch({
devtools: true,
});
Set custom downloads pathAPI
const browser = await pw.chromium.launch({
downloadsPath: './downloads',
});
Set custom timeout for browser launchAPI
const browser = await pw.chromium.launch({
timeout: 1000 * 60,
});
Handle process termination signalsAPI
const browser = await pw.chromium.launch({
// all default to true
handleSIGHUP: false
handleSIGINT: false // ctrl+c
handleSIGTERM: false
});
Proxy
Proxy browser trafficAPI
const browser = await pw.chromium.launch({
proxy: {
server: 'https://proxy.com:8080',
},
});
Proxy browser traffic with authenticationAPI
const browser = await pw.chromium.launch({
proxy: {
server: 'https://proxy.com:8080',
username: 'user',
password: 'pass',
},
});
Bypass browser proxy for specific domainsAPI
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 browserAPI
const browser = await pw.chromium.launch({
// defaults to `process.env`
env: {
...process.env,
SOCKS_SERVER: 5,
},
});
Launch a specific browser executableAPI
const browser = await pw.chromium.launch({
executablePath: '/path/to/brave-browser',
});
Launch browser with custom CLI argsAPI
const browser = await pw.chromium.launch({
args: ['--disable-gl-drawing-for-tests'],
});
Disable specific default CLI argsAPI
const browser = await pw.chromium.launch({
ignoreDefaultArgs: ['--hide-scrollbars'],
});
Disable all default CLI argsAPI
const browser = await pw.chromium.launch({
ignoreDefaultArgs: true,
});
Chromium-Based Config
Enable Chromium sandboxingAPI
const browser = await pw.chromium.launch({
chromiumSandbox: true, // default: false
});
Set Chromium user preferences (chrome://flags
)DOCSAPI
chrome://flags
)const browser = await pw.chromium.launch({
args: [
'--enable-features=NetworkService,BackgroundFetch',
'--disable-features=IsolateOrigins,BackForwardCache',
],
});
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 serverAPI
const browser = await pw.chromium.launchServer();
const wsEndpoint = await browser.wsEndpoint();
Connect to a browser serverAPI
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint);
Send custom headers to browser serverAPI
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 serverAPI
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 serverAPI
localhost
to browser serverconst wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
exposeNetwork: 'localhost',
});
Expose all loopbacks to browser serverAPI
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
exposeNetwork: '<loopback>',
});
Expose specific IPs to browser serverAPI
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 serverAPI
const wsEndpoint = 'wss://api.browsercat.com/connect';
const browser = await pw.chromium.connect(wsEndpoint, {
exposeNetwork: '*.dev.browsercat.com, *.local',
});
Expose entire network to browser serverAPI
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 ChromeAPI
const chrome = await pw.chromium.launch({
channel: 'chrome',
});
Launch Chrome BetaAPI
const chrome = await pw.chromium.launch({
channel: 'chrome-beta',
});
Launch Chrome DevAPI
const chrome = await pw.chromium.launch({
channel: 'chrome-dev',
executablePath: '/path/to/chrome-dev',
});
Launch Chrome CanaryAPI
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 EdgeAPI
const chrome = await pw.chromium.launch({
channel: 'edge',
});
Launch Edge BetaAPI
const chrome = await pw.chromium.launch({
channel: 'edge-beta',
});
Launch Edge DevAPI
const chrome = await pw.chromium.launch({
channel: 'edge-dev',
});
Launch Edge CanaryAPI
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 FirefoxAPI
const chromium = await pw.firefox.launch();
Launch Firefox BetaAPI
const chrome = await pw.firefox.launch({
channel: 'firefox-beta',
});
Launch Firefox DevAPI
const chrome = await pw.firefox.launch({
channel: 'firefox-beta', // yes, this is correct
executablePath: '/path/to/firefox-dev',
});
Launch Firefox NightlyAPI
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 viewportAPI
await page.screenshot();
Screenshot entire pageAPI
await page.screenshot({fullPage: true});
Screenshot specific elementAPI
const $element = page.locator('h1');
await $element.screenshot();
Resize viewport before screenshotAPI
await page.setViewportSize({
width: 2000,
height: 1000,
});
await page.screenshot();
Screenshot custom HTML/CSS contentAPI
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 screenshotAPI
await page.screenshot({
style: './path/to/screenshot.css',
});
Apply custom raw styles during screenshotAPI
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 screenshotAPI
await page.screenshot({
mask: [
page.locator('input'),
page.getByRole('button'),
page.locator('.sensitive'),
],
});
Set custom mask colorAPI
await page.screenshot({
// default: #FF00FF (pink)
maskColor: '#00FF00',
});
Clip page screenshot to specific regionAPI
await page.screenshot({
clip: {
x: 0,
y: 0,
width: 100,
height: 100,
},
});
Rendering Options
Scale image to device pixel ratioAPI
await page.screenshot({
scale: 'device', // default
});
Disable scaling to device pixel ratioAPI
await page.screenshot({
scale: 'css', // default: device
});
Enable animations during screenshotAPI
await page.screenshot({
animations: 'allow', // default: 'disabled'
});
Enable caret blinking during screenshotAPI
await page.screenshot({
caret: 'initial', // default: 'hide'
});
PNG Output
Generate image as PNG bufferAPI
const image = await page.screenshot();
Save image to PNG fileAPI
await page.screenshot({
path: 'screenshot.png',
});
Enable transparency in PNG imagesAPI
await page.screenshot({
// default: white background
omitBackground: true,
});
JPEG Output
Output image as JPEG bufferAPI
const image = await page.screenshot({
type: 'jpeg',
});
Output JPEG image with custom qualityAPI
const image = await page.screenshot({
type: 'jpeg',
quality: 80,
});
Save image as JPEG fileAPI
await page.screenshot({
path: 'screenshot.jpg',
});
Save JPEG image with custom qualityAPI
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 bufferAPI
const pdf = await page.pdf();
Save PDF to fileAPI
await page.pdf({
path: 'page.pdf',
});
Page Size
Set page size to conventional formatAPI
await page.pdf({
// supported:
// `Letter`, `Legal`, `Tabloid`, `Ledger`,
// `A0`, `A1`, `A2`, `A3`, `A4`, `A5`, `A6`
format: 'Letter', // default
});
Enable landscape orientation for formatAPI
await page.pdf({
landscape: true, // default: false
});
Set page size to custom dimensionsAPI
await page.pdf({
// supported: `px`, `in`, `cm`, `mm`
width: '8.5in',
height: '11in',
});
Prefer CSS page size, if availableAPI
await page.pdf({
preferCSSPageSize: true, // default: false
});
Set page size using CSSAPI
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 dimensionsAPI
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 CSSAPI
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 footersAPI
await page.pdf({
displayHeaderFooter: true, // default: false
});
Set custom HTML/CSS header templateAPI
// 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 templateAPI
// 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'},
});
Video Generation
Generate videos of page interactions. Playwright’s API is still rough around the edges.
Record video of current pageAPI
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 dimensionsAPI
const context = await browser.newContext({
recordVideo: {
dir: './videos',
size: {
width: 1920, // default: 800
height: 1080, // default: 800
},
},
});
Get video output path for current pageAPI
const videoPath = page.video.path();
Copy video of page to custom locationAPI
await page.video.saveAs('custom.webm');
Delete video of current pageAPI
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 contextAPI
const context = await browser.newContext({
recordVideo: {
dir: './videos',
},
});
(2) Create blackout overlayAPI
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 blackoutAPI
const page = await context.newPage();
await page.goto('https://www.browsercat.com');
const $blackout = page.locator('#blackout');
(4) Toggle blackout to create slicesAPI
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 locationAPI
await page.video.saveAs('sliceable.webm');
(6) Save video files to diskAPI
await context.close();
(7) Split video into slices using ffmpeg
WEB
ffmpeg
INPUT_FILE="sliceable.webm"
# detect black frames
BLACKDETECT_OUTPUT=$(ffmpeg -i "$INPUT_FILE" -vf "blackdetect=d=0.1:pix_th=0.1" -an -f null - 2>&1 | grep blackdetect)
SLICE_NUM=1
PREV_END=0
# loop over slices
echo "$BLACKDETECT_OUTPUT" | while read -r line; do
BLACK_START=$(echo $line | sed -n 's/.*black_start:\([^ ]*\).*/\1/p')
BLACK_END=$(echo $line | sed -n 's/.*black_end:\([^ ]*\).*/\1/p')
if (( $(echo "$PREV_END == 0" | bc -l) )); then
DURATION=$(echo "$BLACK_START" | bc)
else
DURATION=$(echo "$BLACK_START - $PREV_END" | bc)
fi
BASE_NAME=$(basename "$INPUT_FILE" .webm)
OUTPUT_SLICE="${BASE_NAME}-${SLICE_NUM}.webm"
# create new video slice
if (( $(echo "$DURATION > 0" | bc -l) )); then
ffmpeg -ss "$PREV_END" -i "$INPUT_FILE" -t "$DURATION" -c copy "$OUTPUT_SLICE"
fi
SLICE_NUM=$((SLICE_NUM+1))
PREV_END="$BLACK_END"
done
# handle file slice
TOTAL_DURATION=$(ffmpeg -i "$INPUT_FILE" 2>&1 | grep "Duration" | awk '{print $2}' | sed s/,// | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')
LAST_SLICE_DURATION=$(echo "$TOTAL_DURATION - $PREV_END" | bc)
if (( $(echo "$LAST_SLICE_DURATION > 0" | bc -l) )); then
OUTPUT_SLICE="${BASE_NAME}-${SLICE_NUM}.webm"
ffmpeg -ss "$PREV_END" -i "$INPUT_FILE" -t "$LAST_SLICE_DURATION" -c copy "$OUTPUT_SLICE"
fi
Testing Quick Start
Get started without any configuration required.
Install Playwright for testing
npm i -D @playwright/test
npx playwright install
npm i -D typescript # with TS
Create a test file
touch tests/example.spec.js # with JS
touch tests/example.spec.ts # with TS
Write a basic testDOCSAPI
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 behaviorDOCS
npx playwright codegen
Run your testsDOCS
npx playwright test
Run your tests in UI modeDOCS
npx playwright test --ui
Show test results in browserDOCS
npx playwright show-report
Test Isolation
TODO: Working with TestInfo, workers, etc.
Parameterize Tests
Parameterize tests across multiple projectsDOCS
// 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 configDOCS
import {defineConfig} from '@playwright/test';
export default defineConfig({
testMatch: 'tests/**/*.spec.{ts,tsx}',
});
Externalize heavy dependencies from buildAPI
export default defineConfig({
build: {
external: ['**/*bundle.js'],
},
});
Minimize CLI outputAPI
export default defineConfig({
quiet: !!process.env.CI,
});
Output Files
Select test results output pathAPI
export default defineConfig({
outputDir: './.test/results',
});
Always preserve test resultsAPI
export default defineConfig({
preserveOutput: 'always',
});
Never preserve test resultsAPI
export default defineConfig({
preserveOutput: 'never',
});
Only preserve test results on failureAPI
export default defineConfig({
preserveOutput: 'failures-only',
});
Select snapshots output pathAPI
export default defineConfig({
snapshotPathTemplate: './.test/snapshots/{projectName}/{testFilePath}/{arg}{ext}',
});
Configure Exit Criteria
Retry failed tests before marking as failedDOCSAPI
export default defineConfig({
retries: 3, // default 0
});
Repeat all tests before marking as passedAPI
export default defineConfig({
repeatEach: 3, // default 1
});
Fail individual tests if they exceed timeoutAPI
export default defineConfig({
timeout: 1000 * 30,
});
Fail test suite if exceeds timeoutAPI
export default defineConfig({
globalTimeout: 1000 * 60 * 60,
});
Fail test suite if .only
is presentAPI
.only
is presentexport default defineConfig({
forbidOnly: !!process.env.CI,
});
Fail test suite early, after N failuresAPI
export default defineConfig({
maxFailures: 10, // default 0
});
Assertion Settings
Configure defaults for numerous expect
methods.
Set default timeout for all assertionsAPI
export default defineConfig({
expect: {timeout: 1000 * 10}, // default 5s
});
Set default DOM screenshot configAPI
export default defineConfig({
toHaveScreenshot: {
threshold: 0.1, // default 0.2
maxDiffPixelRatio: 0.1, // default 0
},
});
Set default image snapshot configAPI
export default defineConfig({
toMatchSnapshot: {
threshold: 0.1, // default 0.2
maxDiffPixelRatio: 0.1, // default 0
},
});
Ignore all snapshot/screenshot assertionsAPI
export default defineConfig({
ignoreSnapshots: !process.env.CI,
});
Set criteria for updating snapshotsAPI
export default defineConfig({
updateSnapshots: 'missing', // or 'all' or 'none'
});
Overrides
Override runner config for test fileAPI
test.describe.configure({
mode: 'parallel', // or 'serial'
retries: 3,
timeout: 1000 * 60,
});
test('test', async () => {});
Override runner config for test groupAPI
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 testsAPI
export default defineConfig({
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: true,
},
use: {
baseURL: 'http://localhost:3000',
},
});
Launch multiple web servers during testsAPI
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 serverAPI
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 attributeAPI
data-testid
with custom attributeexport default defineConfig({
use: {
testIdAttribute: 'data-bcat-id',
},
});
Show browser window during testsAPI
export default defineConfig({
use: {
headless: false,
},
});
Screenshot tests automaticallyAPI
export default defineConfig({
use: {
screenshot: 'on', // or 'only-on-failure' | 'off'
},
});
Record video of tests automaticallyAPI
export default defineConfig({
use: {
video: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'off'
},
});
Record trace data for tests automaticallyAPI
export default defineConfig({
use: {
trace: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'off'
},
});
Configure Behavior
Configure auto-accepting download requestsAPI
export default defineConfig({
use: {
acceptDownloads: false, // default: true
},
});
Set delay between user actionsAPI
export default defineConfig({
use: {
actionTimeout: 1000 * 3, // default: 0
},
});
Set delay between page navigationsAPI
export default defineConfig({
use: {
navigationTimeout: 1000 * 3, // default: 0
},
});
Enable or disable JavaScriptAPI
export default defineConfig({
use: {
javaScriptEnabled: false, // default: true
},
});
Enable or disable service workersAPI
export default defineConfig({
use: {
serviceWorkers: 'block', // default: 'allow'
},
});
Grant custom browser permissions automaticallyAPI
export default defineConfig({
use: {
permissions: ['geolocation', 'notifications'],
},
});
Set custom browser cookiesAPI
export default defineConfig({
use: {
storageState: {
cookies: [{
name: 'name',
value: 'value',
domain: 'browsercat.com',
// and other cookie properties...
}],
},
},
});
Set custom localStorage
stateAPI
localStorage
stateexport default defineConfig({
use: {
storageState: {
origins: [{
origin: 'browsercat.com',
localStorage: [{
name: 'name',
value: 'value',
}],
}],
},
},
});
Load browser cookies and storage from fileAPI
export default defineConfig({
use: {
storageState: 'path/to/storage.json',
},
});
Configure Network Traffic
Enable relative URLs with custom base URLAPI
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 credentialsAPI
export default defineConfig({
use: {
httpCredentials: {
username: 'user',
password: 'pass',
},
},
});
Send custom default HTTP headers with requestsAPI
export default defineConfig({
use: {
extraHTTPHeaders: {
'X-My-Header': 'value',
},
},
});
Emulate offline network conditionsAPI
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 errorsAPI
export default defineConfig({
use: {
ignoreHTTPSErrors: process.env.NODE_ENV === 'development',
},
});
Route all traffic through a proxy serverAPI
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 quicklyDOCS
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 testsAPI
export default defineConfig({
use: {
browserName: 'firefox', // or 'webkit' | 'chromium'
},
});
Select userland browser to use for testsAPI
export default defineConfig({
use: {
channel: 'chrome', // or 'chrome-beta' | 'chrome-dev' | 'msedge' | 'msedge-beta' | 'msedge-dev'
},
});
Set user’s preferred color schemeAPI
export default defineConfig({
use: {
colorScheme: 'dark', // or 'light' | 'no-preference'
},
});
Set specific browser user agentAPI
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 sizeAPI
export default defineConfig({
use: {
viewport: { width: 1920, height: 1080 },
},
});
Set browser CLI launch argumentsAPI
export default defineConfig({
use: {
launchOptions: {
args: ['--no-sandbox'],
},
},
});
Configure additional browser context optionsAPI
export default defineConfig({
use: {
contextOptions: {
reducedMotion: 'reduce',
strictSelectors: true,
},
},
});
Emulate Devices
Emulate specific devices quicklyDOCS
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 factorAPI
export default defineConfig({
use: {
deviceScaleFactor: 2,
},
});
Emulate mobile deviceAPI
export default defineConfig({
use: {
isMobile: true,
},
});
Emulate touch screen supportAPI
export default defineConfig({
use: {
hasTouch: true,
},
});
Configure Locale
Overrides
Override test options for projectAPI
export default defineConfig({
use: {offline: false},
projects: [{
name: 'Offline Support',
testMatch: '**/offline/**/*.spec.ts',
use: {offline: true},
}]
});
Override test options for test fileAPI
test.use({offline: true});
test('test', async () => {});
Override test options for test groupAPI
test.describe('group', async () => {
test.use({offline: true});
test('test', async () => {});
});
Writing Tests
Write a basic testDOCS
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 testAPI
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 groupAPI
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 reportingAPI
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 stepsAPI
test('long test', async () => {
await step('step 1', async () => {});
await step('step 2', async () => {});
await step('step 3', async () => {});
});
Break a test into nested stepsAPI
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 lineAPI
test('long test', async () => {
await step('step 1', async () => {
throw new Error();
}, {box: true});
});
Create reusable test stepsAPI
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 testAPI
test.only('working on this', async () => {});
Only run a specific test groupsAPI
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 slowAPI
test('slow test', async () => {
test.slow();
});
Mark a slow test conditionallyAPI
test('slow on webkit', async ({browserName}) => {
test.slow(browserName === 'webkit');
});
Mark slow tests in file conditionallyAPI
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 testAPI
test.skip('skipped for now', async () => {});
test('skipped for now', async () => {
test.skip();
});
Skip a specific test groupAPI
test.describe.skip('skipped for now', async () => {});
Skip a test conditionallyAPI
test('skipped on webkit', async ({browserName}) => {
test.skip(browserName === 'webkit', `Not supported.`);
});
Skip tests in file conditionallyAPI
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 todoAPI
test.fixme('broken for now', async () => {});
test('broken for now', async () => {
test.fixme();
});
Mark a test group as todoAPI
test.describe.fixme('broken for now', async () => {});
Mark a todo test conditionallyAPI
test('broken on webkit', async ({browserName}) => {
test.fixme(browserName === 'webkit', `Not supported.`);
});
Mark todo tests in file conditionallyAPI
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 failAPI
test('always', async () => {
test.fail();
});
Mark a failing test conditionallyAPI
test('broken on webkit', async ({browserName}) => {
test.fail(browserName === 'webkit', `Not supported.`);
});
Mark failing tests in file conditionallyAPI
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 testAPI
test('test', {
tags: ['@slow', '@feature-auth'],
}, async () => {});
Add custom tags to test groupAPI
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 testAPI
test('test', {
annotation: [{
type: 'issue',
description: '@browsercat/cookbook/issues/1',
}],
}, async () => {});
Add custom annotions to test groupAPI
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 runDOCSAPI
// 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 runDOCSAPI
// 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 browserDOCSAPI
// 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 fileAPI
test.beforeAll(async () => {
// runs once before all tests
});
test('test', async () => {});
Run code after all tests in fileAPI
test.afterAll(async () => {
// runs once after all tests
});
test('test', async () => {});
Run code before each test in fileAPI
test.beforeEach(async () => {
// runs once before each test
});
test('test', async () => {});
Run code after each test in fileAPI
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 groupAPI
test.describe('group', async () => {
test.beforeAll(async () => {
// runs once before all tests in group
});
test('test', async () => {});
});
Run code after all tests in test groupAPI
test.describe('group', async () => {
test.afterAll(async () => {
// runs once after all tests in group
});
test('test', async () => {});
});
Run code before each test in test groupAPI
test.describe('group', async () => {
test.beforeEach(async () => {
// runs once before each test in group
});
test('test', async () => {});
});
Run code after each test in test groupAPI
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 testAPI
page
fixture in testtest('test', async ({page}) => {
// `page` is exclusive to this test
await page.goto('https://www.browsercat.com');
});
Access context
fixture in testAPI
context
fixture in testtest('test', async ({context}) => {
// `context` is exclusive to this test
const page = await context.newPage();
});
Access browser
fixture in testAPI
browser
fixture in testtest('test', async ({browser}) => {
// `browser` is shared across worker thread
const context = await browser.newContext();
});
Access request
fixture in testAPI
request
fixture in testtest('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 valueAPI
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 teardownAPI
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 implementationAPI
export const test = base.extend({
page: [async ({page}, use) => {
page.addInitScript('tests/init.js');
await use(page);
}],
});
Fixture Customization
Always run fixture, even if unusedAPI
export const test = base.extend({
fixture: [async (ctx, use) => {
await use(null);
}, {auto: true}],
});
Set custom timeout for fixture executionAPI
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 workerAPI
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 onceAPI
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 runtimeAPI
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 runtimeAPI
type WorkerOptions = {
siteUrl: string;
};
export const test = base.extend<{}, WorkerOptions>({
siteUrl: [
'https://www.browsercat.com',
{scope: 'worker', option: true},
],
});
Customize fixture for specific testsAPI
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 valueAPI
expect(1).toBeTruthy();
Assert falsy valueAPI
expect(0).toBeFalsy();
Type Assertions
Assert the value is a stringAPI
expect('a').toBe(expect.any(String));
Assert the value is a numberAPI
expect(1).toBe(expect.any(Number));
Assert the value is a booleanAPI
expect(true).toBe(expect.any(Boolean));
Assert the value is a functionAPI
expect(() => {}).toBe(expect.any(Function));
Assert the value is NaN
API
NaN
expect(NaN).toBeNaN();
Assert the value is null
API
null
expect(null).toBeNull();
Assert the value is undefined
API
undefined
expect(undefined).toBeUndefined();
Assert the value is not undefined
API
undefined
expect(1).toBeDefined();
Assert the instanceof
the valueAPI
instanceof
the valueexpect(page).toBeInstanceOf(Page);
Value Assertions
Number Assertions
Assert number is greater than matchAPI
expect(1).toBeGreaterThan(0);
Assert number is greater or equal to matchAPI
expect(1).toBeGreaterThanOrEqual(1);
Assert number is less than matchAPI
expect(0).toBeLessThan(1);
Assert number is less or equal to matchAPI
expect(1).toBeLessThanOrEqual(1);
String Assertions
Assert string includes substringAPI
expect('hello').toContain('hell');
Assert string has specific lengthAPI
expect('hello').toHaveLength(5);
Assert string matches patternAPI
expect('hello').toMatch(/^hell/);
Object Assertions
Assert object has specific propertyAPI
expect({a: 1, b: 2}).toHaveProperty('a');
Assert object property has specific valueAPI
expect({a: 1, b: 2}).toHaveProperty('a', 1);
Assert nested object has propertyAPI
expect({a: [{b: 2}]}).toHaveProperty('a[0].b', 2);
Array Assertions
Assert array includes valueAPI
expect([1, 2, 3]).toContain(1);
Assert array includes equivalent valueAPI
expect([
{a: 1},
{b: 2},
{c: 3},
]).toContainEqual({a: 1});
Assert array has specific lengthAPI
expect([1, 2, 3]).toHaveLength(3);
Set Assertions
Assert set includes valueAPI
expect(new Set([1, 2, 3])).toContain(1);
Assert set includes equivalent valueAPI
expect(new Set([
{a: 1},
{b: 2},
{c: 3},
])).toContainEqual({a: 1});
Error Assertions
Assert function throws error with messageAPI
expect(() => { throw new Error('hello') })
.toThrow('hello');
Assert function throws error with message patternAPI
expect(() => { throw new Error('hello') })
.toThrow(/^hell/);
Assert function throws error typeAPI
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 testDOCS
await expect.poll(async () => {
return Math.random();
}).toBeGreaterThan(0.1);
Poll function value with custom intervalsDOCS
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 timeoutDOCS
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 intervalsDOCS
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 timeoutDOCS
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 matchAPI
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 matchAPI
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 patternAPI
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 valueAPI
expect(0).toBe(expect.anything());
Assert number roughly equals matchAPI
expect(0.1 + 0.2).toBe(expect.closeTo(0.3));
Assert string includes substringAPI
expect('hello')
.toEqual(expect.stringContaining('hell'));
Assert string matches patternAPI
expect('hello')
.toEqual(expect.stringMatching(/^hell/));
Assert array includes subset of valuesAPI
expect([1, 2, 3])
.toEqual(expect.arrayContaining([1, 2]));
Assert object includes subset of propertiesAPI
expect({a: 1, b: 2})
.toEqual(expect.objectContaining({a: 1}));
Assert typeof
valueAPI
typeof
valueexpect(1).toBe(expect.any(Number));
expect(page).toBe(expect.any(Page));
Page Assertions
Assert the page titleAPI
await expect(page).toHaveTitle('BrowserCat');
Assert the page title matches patternAPI
await expect(page).toHaveTitle(/browsercat/i);
Assert the page URLAPI
await expect(page).toHaveURL('https://www.browsercat.com/');
Assert the page URL matches patternAPI
await expect(page).toHaveURL(/browsercat\.com/i);
Element Property Assertions
Assert element has JS DOM propertyAPI
await expect(locator).toHaveJSProperty('open', true);
ID Assertions
Assert element id matches stringAPI
await expect(locator).toHaveId('unique');
Assert element id matches patternAPI
await expect(locator).toHaveId(/item-\d+/);
Class Assertions
Assert element has classAPI
await expect(locator).toHaveClass('active');
Assert element class matches patternAPI
await expect(locator).toHaveClass(/active/);
Assert element has multiple classesAPI
await expect(locator).toHaveClass(['active', /visible-.+/]);
Attribute Assertions
Assert element has attributeAPI
await expect(locator).toHaveAttribute('href');
Assert element attribute has valueAPI
await expect(locator).toHaveAttribute('href', '/');
Assert element attribute matches patternAPI
await expect(locator).toHaveAttribute('href', /browsercat\.com/);
CSS Property Assertions
Assert element CSS property valueAPI
await expect(locator).toHaveCSS('color', 'red');
Assert element CSS property matches patternAPI
await expect(locator).toHaveCSS('color', /#3366.{2}/);
Text Content Assertions
Assert element text exactly matches stringAPI
await expect(locator).toHaveText('hello');
Assert element text exactly matches patternAPI
await expect(locator).toHaveText(/hello/);
Assert element text contains substringAPI
await expect(locator).toContainText('hello');
Assert element text contains patternAPI
await expect(locator).toContainText(/hell/);
Element Interaction Assertions
Element Visibility Assertions
Assert element is visibleAPI
await expect(locator).toBeVisible();
Assert element is not visibleAPI
await expect(locator).toBeVisible({visible: false});
Assert element is in viewportAPI
await expect(locator).toBeInViewport();
Assert element is fully in viewportAPI
await expect(locator).toBeInViewport({ratio: 1});
Assert element is attached to the DOMAPI
await expect(locator).toBeAttached();
Assert element is detached from the DOMAPI
await expect(locator).toBeAttached({attached: false});
Form & Input Assertions
Assert element is focusedAPI
await expect(locator).toBeFocused();
Assert element is enabledAPI
await expect(locator).toBeEnabled();
Assert element is disabledAPI
await expect(locator).toBeEnabled({enabled: false});
Assert element is checkedAPI
await expect(locator).toBeChecked();
Assert element is not checkedAPI
await expect(locator).toBeChecked({checked: false});
Assert element is editableAPI
await expect(locator).toBeEditable();
Assert element is not editableAPI
await expect(locator).toBeEditable({editable: false});
Assert element input valueAPI
await expect(locator).toHaveValue('123-45-6789');
Assert element input value matches patternAPI
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 lengthAPI
await expect(locators).toHaveCount(3);
Assert list of elements has exact textAPI
await expect(locators).toHaveText(['hello', /goodbye/]);
Assert list of elements contains substringsAPI
await expect(locators).toContainText(['hell', /^good/]);
Assert list of element input valuesAPI
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 screenshotAPI
await expect(page).toHaveScreenshot();
Assert image matches named screenshotAPI
// compare to a single image shared across tests
await expect(page).toHaveScreenshot('home-page.png');
Assert cropped page matches screenshotAPI
await expect(page).toHaveScreenshot({
clip: {x: 0, y: 0, width: 100, height: 100},
});
Assert page roughly matches screenshotAPI
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 screenshotAPI
await expect(locator).toHaveScreenshot();
Assert element matches named screenshotAPI
// compare to a single image shared across tests
await expect(locator).toHaveScreenshot('home-hero.png');
Assert cropped element matches screenshotAPI
await expect(locator).toHaveScreenshot({
clip: {x: 0, y: 0, width: 100, height: 100},
});
Assert element roughly matches screenshotAPI
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 snapshotAPI
expect(customImage).toMatchSnapshot();
Assert image matches named snapshotAPI
// compare to a single image shared across tests
expect(customImage).toMatchSnapshot('saturated.png');
Assert image roughly matches snapshotAPI
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 contextAPI
fetch
response within browser contexttest('send fetch request', async ({request}) => {
const res = await request.fetch('/api/logout', {
method: 'GET',
});
expect(res.ok()).toBeTruthy();
});
Assert cookies after fetch
requestAPI
fetch
requesttest('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 testAPI
expect(1).not.toBe(2);
Assert value without stopping executionAPI
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 expectAPI
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 matcherDOCS
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 matcherDOCS
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 matcherDOCS
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`);
}
},
});
Test Filtering Configuration
Filter Tests by Filename
Include only tests from specific directoryAPI
export default defineConfig({
testDir: './tests', // default './'
});
Include test files matching globAPI
export default defineConfig({
testMatch: '**/*.spec.{ts,js}',
});
Include test files matching regexAPI
export default defineConfig({
testMatch: /\.spec\.(ts|js)$/,
});
Include test files matching any patternAPI
export default defineConfig({
testMatch: [
'**/*.spec.{ts,js}',
/\.spec\.(ts|js)$/,
],
});
Ignore test files matching globAPI
export default defineConfig({
testIgnore: '**/*.ignore.*',
});
Ignore test files matching regexAPI
export default defineConfig({
testIgnore: /(\.ignore\.|\/ignore\/)/,
});
Ignore test files matching any patternAPI
export default defineConfig({
testIgnore: [
'**/*.ignore.*',
'**/ignore/**/*',
/(\.ignore\.|\/ignore\/)/,
],
});
Filter Tests by Titles
Only run tests with titles matching regexAPI
export default defineConfig({
grep: /contact/i,
});
Only run tests with titles matching any regexAPI
export default defineConfig({
grep: [/button/i, /input/i],
});
Only run tests with titles not matching regexAPI
export default defineConfig({
grepInvert: /@flaky/i,
});
Only run tests with titles not matching any regexAPI
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 projectsAPI
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 directoryAPI
export default defineConfig({
project: [{
name: 'projects',
testDir: './tests/feature-1',
}],
});
Include test files matching patternsAPI
export default defineConfig({
project: [{
name: 'projects',
testMatch: [
'feature-1/**/*.spec.{ts,js}',
/feature-1\/.+\.spec\.(ts|js)$/,
],
}],
});
Only run tests with titles matching patternsAPI
export default defineConfig({
project: [{
name: 'auth',
grep: [/^Auth/, /(login|logout)/i],
}],
});
Override test environment optionsAPI
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 projectAPI
export default defineConfig({
project: [{
name: 'before',
}, {
name: 'tests',
dependencies: ['before'],
}],
});
Run target project after multiple projectsAPI
export default defineConfig({
project: [{
name: 'tasks',
}, {
name: 'users',
}, {
name: 'workflows',
dependencies: ['tasks', 'users'],
}],
});
Run cleanup after project dependenciesDOCSAPI
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 sequentiallyDOCSAPI
export default defineConfig({
fullyParallel: false, // default
});
Run all tests in all files in parallelDOCSAPI
export default defineConfig({
fullyParallel: true, // default: false
});
Set number of parallel workersDOCSAPI
export default defineConfig({
workers: 3, // default: 50% logical CPUs
});
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 parallelAPI
test.describe.configure({
mode: 'parallel', // default 'serial'
});
test('test', async () => {});
Run a test group’s tests in parallelAPI
test.describe('group', async () => {
test.describe.configure({
mode: 'parallel', // default 'serial'
});
test('test', async () => {});
});
Run test group serially, even after retriesAPI
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 fileDOCSAPI
export default defineConfig({
shard: {
total: process.env.PW_SHARD_TOTAL ?? 1,
current: process.env.PW_SHARD_CURRENT ?? 1,
},
});
Configure reporters for sharded test resultsDOCS
export default defineConfig({
reporter: [
['blob', {
outputDir: 'test-results',
fileName: `report-${process.env.CI_BUILD_ID}.zip`,
}],
],
});
Combine sharded test results into single reportDOCS
npx playwright merge-reports \
--reporter html \
./reports
Test Reporting & Configuration
Add custom annotation to test’s resultAPI
test('test', {
annotation: [{
type: 'issue',
description: '@browsercat/cookbook/issues/1',
}],
}, async () => {});
Attach screenshot to test’s resultAPI
test('with screenshot', async ({page}) => {
await test.info().attach('screenshot', {
contentType: 'image/png',
body: await page.screenshot(),
});
});
Reporter Configuration
Configure test reportersAPI
export default defineConfig({
reporter: [
['list'],
['json', {outputFile: 'test-results.json'}],
['junit', {outputFile: 'test-results.xml'}],
],
});
Attach JSON metadata to test resultsAPI
export default defineConfig({
metadata: {
region: 'us-east-1',
date: '2021-12-21',
},
});
Automatically capture test screenshotsAPI
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 recordingsAPI
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
stdin
export default defineConfig({
reportSlowTests: {
max: 10, // default: 5
threshold: 1000 * 15, // default: 15 seconds
},
});
CLI Reporter Overrides
Some report config can be overridden on the command line.
Override selected reporters via CLI
npx playwright test \
--reporter list,json,junit
Override report output directory via CLI
npx playwright test \
--output .test/results
Test Reporters
CLI Reporters
These reporters output exclusively to stdin
.
List each test and its statusDOCS
export default defineConfig({
reporter: [
['list', {
printSteps: false, // print test steps
omitFailures: false, // skip detailed failure messages
}],
],
});
Output single line summary of test runDOCS
export default defineConfig({
reporter: [
['line', {
omitFailures: false, // skip detailed failure messages
}],
],
});
Output row of dots indicating each test’s statusDOCS
export default defineConfig({
reporter: [
['dot', {
omitFailures: false, // skip detailed failure messages
}],
],
});
Annotate Github workflow with test resultsDOCS
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 runDOCS
export default defineConfig({
reporter: [
['html', {
outputFolder: 'test-results',
open: 'never', // or 'always' | 'on-failure'
}],
],
});
Output JSON report of test runDOCS
export default defineConfig({
reporter: [
['json', {
outputFile: 'results.json',
}],
],
});
Output JUnit report of test runDOCS
export default defineConfig({
reporter: [
['junit', {
outputFile: 'results.xml',
}],
],
});
Output test result “blob” for post-processingDOCS
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 emulationDOCS
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 scriptDOCSAPI
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 emulationDOCS
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 oneDOCS
npx playwright merge-reports \
.test/reports
Merge sharded reports with custom reportersDOCS
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
stdin
cat .test/traces/trace.zip | \
npx playwright show-trace --stdin
Browser Actions
Get browser typeAPI
const browserType = browser.browserType();
Get browser versionAPI
const version = await browser.version();
Check if browser is connectedAPI
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 reasonAPI
await browser.close({reason: 'success'});
Listen for browser disconnection eventAPI
browser.on('disconnected', (browser) => {});
Contexts (aka User Sessions)
Create new contextAPI
const context = await browser.newContext();
Create new context with custom optionsAPI
const context = await browser.newContext({
bypassCSP: true,
colorScheme: 'dark',
deviceScaleFactor: 1,
permissions: ['geolocation'],
// etc.
});
List all browser’s contextsAPI
const contexts = browser.contexts();
Get the current page’s contextAPI
const context = page.context();
Close contextAPI
await context.close();
Close context with a reasonAPI
await context.close({reason: 'success'});
Pages
Create new page in contextAPI
const page = await context.newPage();
Create new page in new contextAPI
const page = await browser.newPage();
Create new page in new context with custom optionsAPI
const page = await browser.newPage({
bypassCSP: true,
colorScheme: 'dark',
deviceScaleFactor: 1,
permissions: ['geolocation'],
// etc.
});
Page Navigation
Create new page in contextAPI
const page = await context.newPage();
Create new page in default contextAPI
const page = await browser.newPage();
Navigate to specific URLAPI
await page.goto('https://browsercat.com');
Navigate via page actions
await page.locator('a[href]').first().click();
await page.waitForEvent('load');
Reload the pageAPI
await page.reload();
Navigate to previous pageAPI
await page.goBack();
Navigate to next pageAPI
await page.goForward();
Close the pageAPI
await page.close();
Check if the page is closedAPI
const isClosed = page.isClosed();
Working with Navigation
Wait for the page to navigate to a new URLAPI
await page.waitForURL('https://www.browsercat.com');
Navigate to URL and wait for content to loadAPI
await page.goto('https://www.browsercat.com', {
waitUntil: 'domcontentloaded', // default: 'load'
});
Reload page and wait for content to loadAPI
await page.reload({
waitUntil: 'domcontentloaded', // default: 'load'
});
Catch page that opens in new tabAPI
page.locator('a[target="_blank"]').first().click();
const newTabPage = await page.waitForEvent('popup');
Catch page that opens in pop-up windowAPI
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 pageAPI
const frame = page.mainFrame();
Get frame by name
attributeAPI
name
attributeconst frame = page.frame({
name: /^footer-ad$/, // or exact string match
});
Get frame by url
attributeAPI
url
attributeconst frame = page.frame({
url: /\/footer-ad\.html$/, // or exact string match
});
Get all frames for current pageAPI
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 selectorAPI
const $frame = page.frameLocator('#soundcloud-embed');
Create frame locator from locatorAPI
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.
Role-based Selectors
These locators support ARIA roles, states, and properties. Very useful for shorthand selection of most DOM elements.
Select element by roleDOCSAPI
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 nameAPI
const $button = await page.getByRole('button', {
name: /(submit|save)/i, // or string
exact: true, // default: false
});
Select elements by checked
stateAPI
checked
stateconst $checkbox = await page.getByRole('checkbox', {
checked: true, // or false
});
Select elements by selected
stateAPI
selected
stateconst $option = await page.getByRole('option', {
selected: true, // or false
});
Select elements by expanded
stateAPI
expanded
stateconst $menu = await page.getByRole('menu', {
expanded: true, // or false
});
Select elements by pressed
stateAPI
pressed
stateconst $button = await page.getByRole('button', {
pressed: true, // or false
});
Select elements by disabled
stateAPI
disabled
stateconst $input = await page.getByRole('textbox', {
disabled: true, // or false
});
Select elements by depth levelAPI
const $heading = await page.getByRole('heading', {
level: 2, // etc.
});
Match hidden elements with ARIA locatorsAPI
const $alert = await page.getByRole('alert', {
includeHidden: true, // default: false
});
CSS Selectors
Playwright supports all CSS selectors using document.querySelector()
.
Select elements by CSS selectorAPI
const $icon = await page.locator('button > svg[width]');
Select elements by tag nameAPI
const $header = await page.locator('header');
Select elements by tag attributeAPI
const $absLinks = await page.locator('[href^="https://"]');
Select elements by CSS classAPI
const $buttons = await page.locator('.btn');
Select elements by CSS idAPI
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 selectorAPI
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
requestAPI
fetch
requestconst res = await page.request.fetch(
'https://browsercat.com',
{method: 'GET'},
);
Flush network traffic cacheAPI
await page.request.dispose();
Set default HTTP headers on all requestsAPI
await page.setExtraHTTPHeaders({
'X-Agent': 'production-test-bot',
});
Wait for Network Traffic
Wait for request matching testAPI
const req = await page.waitForEvent('request', (req) => {
return req.method() === 'PUT' &&
req.headers()['content-type'] === 'application/json';
});
Wait for response matching testAPI
const res = await page.waitForEvent('response', (res) => {
return res.status() === 201 &&
Array.isArray(await res.json());
});
Wait for page request by URLAPI
const req = await page.waitForRequest(/browsercat\.com/);
Wait for context request by URLAPI
const req = await context.waitForEvent('request', (req) => {
return req.url().includes('browsercat.com');
});
Wait for page response by URLAPI
const res = await page.waitForResponse(/browsercat\.com/);
Wait for context response by URLAPI
const res = await context.waitForEvent('response', (res) => {
return res.url().includes('browsercat.com');
});
Network Events
Listen for new network requestsAPI
page.on('request', (req) => {});
Listen for successful network requestsAPI
page.on('requestfinished', (req) => {});
Listen for failed network requestsAPI
page.on('requestfailed', (req) => {});
Listen for network responsesAPI
page.on('response', (res) => {});
Listen for new websocket requests (page
only)API
page
only)page.on('websocket', (ws) => {});
Intercept Network Traffic
Note: Playwright can’t currently intercept traffic to webworkers. If needed, disable webworkers.
Route all requests through handlerAPI
await page.route('**/*', (route) => {
route.continue();
});
Route requests matching globAPI
await page.route('**/*.png', (route) => {
route.continue();
});
Route requests matching regexAPI
await page.route(/\.json$/i, (route) => {
route.continue();
});
Route request only onceAPI
await page.route('**/*.png', (route) => {
route.continue();
}, {times: 1}); // or any number
Remove all handlers from routeAPI
await page.unroute('**/*.png');
Remove specific handler from routeAPI
await page.unroute('**/*.png', pngHandler);
await page.unroute(/\.json$/i, jsonHandler);
Remove all network routes immediatelyAPI
await page.unrouteAll();
Remove all network routes as they completeAPI
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 proceedAPI
await page.route('**/*', (route) => {
route.continue();
});
Modify request before allowing to proceedAPI
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 responseAPI
await page.route('**/*', (route) => {
const response = await route.fetch();
route.fulfill({
response,
json: {
...await response.json(),
test: true,
},
})
});
Fulfill routed request with custom responseAPI
await page.route('**/*', (route) => {
route.fulfill({
status: 404,
json: {message: 'not found'},
});
});
Fulfill routed request with local fileAPI
await page.route('**/*.png', (route) => {
route.fulfill({
path: './1-pixel.png',
});
});
Fallback to earlier-defined matching route handlerAPI
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 handlerAPI
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 requestAPI
await page.route('**/*', (route) => {
route.abort();
});
Abort routed request with custom errorAPI
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 requestsAPI
await page.routeFromHAR('./recorded.har');
Respond using HAR, allowing unknown requestsAPI
await page.routeFromHAR('./recorded.har', {
notFound: 'fallback',
});
Respond using HAR, for requests matching patternAPI
await page.routeFromHAR('./recorded.har', {
url: /\.png$/i,
});
Record HAR file using network trafficAPI
await page.routeFromHAR('./recorded.har', {
update: true,
});
Websockets
Listen for new websocket requestsAPI
page.on('websocket', (ws) => {});
Get websocket URLAPI
page.on('websocket', (ws) => {
const url = ws.url();
});
Check if websocket is closedAPI
page.on('websocket', (ws) => {
const isClosed = ws.isClosed();
});
Wait for Websocket Events
Wait for websocket message sent eventAPI
page.on('websocket', (ws) => {
const payload = await ws.waitForEvent('framesent');
});
Wait for websocket message received eventAPI
page.on('websocket', (ws) => {
const payload = await ws.waitForEvent('framereceived');
});
Wait for websocket close eventAPI
page.on('websocket', (ws) => {
const ws = await ws.waitForEvent('close');
});
Wait for websocket error eventAPI
page.on('websocket', (ws) => {
const error = await ws.waitForEvent('socketerror');
});
Websocket Events
Listen for websocket close eventAPI
page.on('websocket', (ws) => {
ws.on('close', (ws) => {});
});
Listen for websocket message sent eventAPI
page.on('websocket', (ws) => {
ws.on('framesent', (payload) => {});
});
Listen for websocket message received eventAPI
page.on('websocket', (ws) => {
ws.on('framereceived', (payload) => {});
});
Listen for websocket error eventAPI
page.on('websocket', (ws) => {
ws.on('socketerror', (error) => {});
});
Keyboard Control
Type text with keyboardAPI
await page.locator('input').focus();
await page.keyboard.type('Hello');
Type text with delay between pressesAPI
await page.keyboard.type('Hello', {
delay: 100,
});
Press key combinationAPI
await page.keyboard.press('F12');
await page.keyboard.press('Control+c');
Press key combination with delay between pressesAPI
await page.keyboard.press('Control+v', {
delay: 100,
});
Press key combination in specific elementAPI
await page.locator('textarea').press('Control+Z');
Low-level Keyboard Events
Dispatch keydown
eventAPI
keydown
eventawait page.keyboard.down('Shift');
await page.keyboard.down('a');
Dispatch keyup
eventAPI
keyup
eventawait page.keyboard.up('a');
await page.keyboard.up('Shift');
Dispatch keyboard events on specific elementAPI
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 coordinatesAPI
await page.mouse.click(100, 100);
Move mouse and click with delay between eventsAPI
await page.mouse.click(100, 100, {
delay: 100,
});
Click element with modifier keysAPI
await page.locator('button').click({
modifiers: ['Shift'],
});
Click element with custom X/Y coordinatesAPI
await page.locator('button').click({
position: {x: 10, y: 10},
});
Move mouse and double-click at specific coordinatesAPI
await page.mouse.dblclick(100, 100);
Move mouse and double-click with delay between eventsAPI
await page.mouse.dblclick(100, 100, {
delay: 100,
});
Double-click element with modifier keysAPI
await page.locator('button').dblclick({
modifiers: ['Shift'],
});
Double-click element with custom X/Y coordinatesAPI
await page.locator('button').dblclick({
position: {x: 10, y: 10},
});
Scroll mouse wheel horizontallyAPI
await page.mouse.wheel(0, 100);
Scroll mouse wheel verticallyAPI
await page.mouse.wheel(100, 0);
Hover elementAPI
await page.locator('button').hover();
Hover element with custom optionsAPI
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
eventAPI
mousedown
eventawait page.mouse.down();
Dispatch mouseup
eventAPI
mouseup
eventawait page.mouse.up();
Dispatch mousemove
eventAPI
mousemove
eventawait page.mouse.move(100, 100);
Dispatch mousemove
event across smooth stepsAPI
mousemove
event across smooth stepsawait page.mouse.move(100, 100, {
steps: 5, // default: 1
});
Dispatch mouse events to specific elementAPI
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 coordinatesAPI
await page.touchscreen.tap(100, 100);
Low-level Touchscreen Events
Dispatch touchscreen events to specific elementAPI
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 elementAPI
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 typeAPI
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 eventsAPI
// emitted when browser connection lost
broser.on('disconnected', (browser) => {});
Listen for browser server eventsAPI
// emitted when browser server is closed
server.on('close', (browserServer) => {});
Listen for context eventsAPI
// 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 eventsAPI
// 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 eventsAPI
// 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 eventsAPI
// 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 eventsAPI
// 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 eventsAPI
// 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 eventsAPI
// emitted when worker is closed
worker.on('close', (worker) => {});
Debugging
Do slowMo
on .connect()
and .launch()
slowMo
on .connect()
and .launch()
Do logger
on .connect()
and .launch()
logger
on .connect()
and .launch()
Do devtools
on .connect()
and .launch()
devtools
on .connect()
and .launch()
Do headless
on .connect()
and .launch()
headless
on .connect()
and .launch()
Do tracesDir
on .connect()
and .launch()
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 traceAPI
await context.tracing.start();
Record context trace files with custom prefixAPI
await context.tracing.start({
name: 'checkout-process',
});
Record screenshots for traceAPI
await context.tracing.start({
screenshots: true,
});
Record snapshots of all actions for traceAPI
await context.tracing.start({
snapshots: true,
});
Include source files in traceAPI
await context.tracing.start({
sources: true,
});
Stop recording context traceAPI
const trace = await context.tracing.stop();
Save context trace to specific fileAPI
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 chunkAPI
await context.tracing.start(); // required
await context.tracing.startChunk();
Record trace chunk with custom prefixAPI
await context.tracing.start(); // required
await context.tracing.startChunk({
name: 'shopping-cart',
});
Stop recording trace chunkAPI
await context.tracing.stopChunk();
Save trace chunk to specific fileAPI
await context.tracing.stopChunk({
path: './trace.json',
// default: browser.launch({tracesDir})
});
End chunk tracing sessionAPI
await context.tracing.stop();
Chromium Browser Trace
These methods will trace the entire browser process. Only supported by Chromium-based browsers.
Record browser traceAPI
await browser.startTracing();
Record browser trace for specific pageAPI
await browser.startTracing(page);
Record browser trace to fileAPI
await browser.startTracing(null, {
path: './trace.json',
// default: browser.launch({tracesDir})
});
Record screenshots for traceAPI
await browser.startTracing(null, {
screenshots: true,
});
Stop recording browser traceAPI
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…