Playwright Cheatsheet for JavaScript & TypeScript
A comprehensive reference for Playwright scripting, testing, and browser gestures, with copy-ready code snippets.
Automation Quick Start
Install Playwright and local browsers
npm i playwright
npx playwright install --with-deps
npm i -D typescript tsx # optionalCreate a script file
touch src/example.js # with JS
touch src/example.ts # with TSWrite a basic script
import * as pw from 'playwright';
const browser = await pw.chromium.launch();
const page = await browser.newPage();
await page.goto('https://www.browsersolver.com');
await browser.close();Generate code from user behavior
npx playwright codegenRun your script
node src/example.js # with JS
npx tsx src/example.ts # with TSLaunch Browsers Locally
Launch Chromium
const chromium = await pw.chromium.launch();Launch Firefox
const firefox = await pw.firefox.launch();Launch Webkit
const webkit = await pw.webkit.launch();Persistent User Data
Browser contexts are normally flushed. But you can optionally store context between sessions.
Launch browser with persist user data
const userDataDir = './userData';
const context = await pw.chromium
.launchPersistentContext(userDataDir);Configure persistent context options
// accepts all options from Browser.newContext()
const context = await pw.chromium
.launchPersistentContext(userDataDir, {
acceptDownloads: true,
ignoreHTTPSErrors: true,
});Chrome Browser Variants
Install Chrome
npx playwright install --with-deps chromeInstall Chrome Beta
npx playwright install --with-deps chrome-betaLaunch Chrome
const chrome = await pw.chromium.launch({
channel: 'chrome',
});Launch Chrome Beta
const chrome = await pw.chromium.launch({
channel: 'chrome-beta',
});Launch Chrome Dev
const chrome = await pw.chromium.launch({
channel: 'chrome-dev',
executablePath: '/path/to/chrome-dev',
});Launch Chrome Canary
const chrome = await pw.chromium.launch({
channel: 'chrome-canary',
executablePath: '/path/to/chrome-canary',
});Edge Browser Variants
Playwright supports all four Edge variants, but you must download Edge Canary manually.
Install Edge
npx playwright install --with-deps edgeInstall Edge Beta
npx playwright install --with-deps edge-betaInstall Edge Dev
npx playwright install --with-deps msedge-devLaunch Edge
const browser = await pw.chromium.launch({
channel: 'msedge',
});Launch Edge Beta
const browser = await pw.chromium.launch({
channel: 'msedge-beta',
});Launch Edge Dev
const browser = await pw.chromium.launch({
channel: 'msedge-dev',
});Launch Edge Canary
const browser = await pw.chromium.launch({
channel: 'msedge-canary',
});Firefox Browser Variants
Playwright supports all four Firefox variants, but you must download Firefox Dev manually.
Install Firefox
npx playwright install --with-deps firefoxInstall Firefox Beta
npx playwright install --with-deps firefox-betaInstall Firefox Nightly
npx playwright install --with-deps firefox-asanLaunch Firefox
const firefox = await pw.firefox.launch();Launch Firefox Beta
const chrome = await pw.firefox.launch({
channel: 'firefox-beta',
});Launch Firefox Dev
const chrome = await pw.firefox.launch({
channel: 'firefox-beta', // yes, this is correct
executablePath: '/path/to/firefox-dev',
});Launch Firefox Nightly
const chrome = await pw.firefox.launch({
channel: 'firefox-asan',
});Contexts (aka User Sessions)
Create new context
const context = await browser.newContext();Create new context with custom options
const context = await browser.newContext({
bypassCSP: true,
colorScheme: 'dark',
deviceScaleFactor: 1,
permissions: ['geolocation'],
// etc.
});List all browser's contexts
const contexts = browser.contexts();Get the current page's context
const context = page.context();Close context
await context.close();Close context with a reason
await context.close({reason: 'success'});Pages
Create new page in context
const page = await context.newPage();Create new page in new context
const page = await browser.newPage();Create new page in new context with custom options
const page = await browser.newPage({
bypassCSP: true,
colorScheme: 'dark',
deviceScaleFactor: 1,
permissions: ['geolocation'],
// etc.
});Page Navigation
Navigate to specific URL
await page.goto('https://www.browsersolver.com');Navigate via page actions
await page.locator('a[href]').first().click();
await page.waitForEvent('load');Reload the page
await page.reload();Navigate to previous page
await page.goBack();Navigate to next page
await page.goForward();Close the page
await page.close();Check if the page is closed
const isClosed = page.isClosed();Working with Navigation
Wait for the page to navigate to a new URL
await page.waitForURL('https://www.browsersolver.com');Navigate to URL and wait for content to load
await page.goto('https://www.browsersolver.com', {
waitUntil: 'domcontentloaded', // default: 'load'
});Reload page and wait for content to load
await page.reload({
waitUntil: 'domcontentloaded', // default: 'load'
});Catch page that opens in new tab
page.locator('a[target="_blank"]').first().click();
const newTabPage = await page.waitForEvent('popup');Catch page that opens in pop-up window
page.evaluate(() => {
window.open('https://www.browsersolver.com', null, {
popup: true,
});
});
const popupPage = await page.waitForEvent('popup');Page Frames
Web pages are made up of frames. The main frame is the viewport, which can contain numerous nested `iframe` elements.
Get parent frame for current page
const frame = page.mainFrame();Get frame by `name` attribute
const frame = page.frame({
name: /^footer-ad$/, // or exact string match
});Get frame by `url` attribute
const frame = page.frame({
url: //footer-ad.html$/, // or exact string match
});Get all frames for current page
const frames = page.frames();Frame Locators
Use frame locators to find elements within a specific frame. Normal locators stop at the frame boundary.
Create frame locator from CSS selector
const $frame = page.frameLocator('#soundcloud-embed');Create frame locator from locator
const $frame = page.locator('#soundcloud-embed');
const $frameLoc = $frame.frameLocator(':scope');Locate Elements
Contract-based Locators
These helpers target elements using properties unlikely to change. Use whenever possible for readability and durability.
Select element by role
const $alert = await page.getByRole('alert');Select element by label
const $input = await page.getByLabel('Username');Select element by placeholder
const $input = await page.getByPlaceholder('Search');Select element by title
const $el = await page.getByTitle('Welcome');Select element by alt text
const $image = await page.getByAltText('Logo');Select element by text
const $button = await page.getByText('Submit');Select element by test id
const $button = await page.getByTestId('submit-button');Role-based Selectors
These locators support ARIA roles, states, and properties. Very useful for shorthand selection of most DOM elements.
Select element by role
const $alert = await page.getByRole('alert');
const $heading = await page.getByRole('heading');
const $button = await page.getByRole('button');
const $link = await page.getByRole('link');
// etc.Select element by accessible name
const $button = await page.getByRole('button', {
name: /(submit|save)/i, // or string
exact: true, // default: false
});Select elements by `checked` state
const $checkbox = await page.getByRole('checkbox', {
checked: true, // or false
});Select elements by `selected` state
const $option = await page.getByRole('option', {
selected: true, // or false
});Select elements by `expanded` state
const $menu = await page.getByRole('menu', {
expanded: true, // or false
});Select elements by `disabled` state
const $input = await page.getByRole('textbox', {
disabled: true, // or false
});Select elements by depth level
const $heading = await page.getByRole('heading', {
level: 2, // etc.
});Match hidden elements with ARIA locators
const $alert = await page.getByRole('alert', {
includeHidden: true, // default: false
});CSS Selectors
Playwright supports all CSS selectors using `document.querySelector()`.
Select elements by CSS selector
const $icon = await page.locator('button > svg[width]');Select elements by tag name
const $header = await page.locator('header');Select elements by tag attribute
const $absLinks = await page.locator('[href^="https://"]');Select elements by CSS class
const $buttons = await page.locator('.btn');Select elements by CSS id
const $captcha = await page.locator('#captcha');XPath Selectors
Playwright supports all XPath selectors using `document.evaluate()`. However, XPath selectors are brittle and highly discouraged.
Select element by XPath selector
const $button = await page.locator('//button[text()="Submit"]');Browser Actions
Get browser type
const browserType = browser.browserType();Get browser version
const version = await browser.version();Check if browser is connected
const isConnected = browser.isConnected();Close browser (force)
await browser.close();Close browser (gentle)
await Promise.all(
browser.contexts()
.map((context) => context.close()),
);
await browser.close();Close browser with a reason
await browser.close({reason: 'success'});Listen for browser disconnection event
browser.on('disconnected', (browser) => {});Network Traffic
Intercept network traffic on the `page` or `context` objects using any method listed below. Prefer `page` for narrow targeting.
Send `fetch` request
const res = await page.request.fetch(
'https://www.browsersolver.com',
{method: 'GET'},
);Flush network traffic cache
await page.request.dispose();Set default HTTP headers on all requests
await page.setExtraHTTPHeaders({
'X-Agent': 'production-test-bot',
});Wait for Network Traffic
Wait for request matching test
const req = await page.waitForEvent('request', (req) => {
return req.method() === 'PUT' &&
req.headers()['content-type'] === 'application/json';
});Wait for response matching test
const res = await page.waitForEvent('response', (res) => {
return res.status() === 201 &&
Array.isArray(await res.json());
});Wait for page request by URL
const req = await page.waitForRequest(/browsersolver.com/);Wait for page response by URL
const res = await page.waitForResponse(/browsersolver.com/);Network Events
Listen for new network requests
page.on('request', (req) => {});Listen for successful network requests
page.on('requestfinished', (req) => {});Listen for failed network requests
page.on('requestfailed', (req) => {});Listen for network responses
page.on('response', (res) => {});Listen for new websocket requests (`page` only)
page.on('websocket', (ws) => {});Intercept Network Traffic
Note: Playwright can't currently intercept traffic to webworkers.
Route all requests through handler
await page.route('**/*', (route) => {
route.continue();
});Route requests matching glob
await page.route('**/*.png', (route) => {
route.continue();
});Route requests matching regex
await page.route(/.json$/i, (route) => {
route.continue();
});Route request only once
await page.route('**/*.png', (route) => {
route.continue();
}, {times: 1}); // or any numberRemove all handlers from route
await page.unroute('**/*.png');Remove all network routes immediately
await page.unrouteAll();Transforming Network Traffic
All routed requests must be handled using either `.continue()`, `.abort()`, or `.fulfill()`.
Allow routed request to proceed
await page.route('**/*', (route) => {
route.continue();
});Modify request before allowing to proceed
await page.route('**/*', (route, req) => {
route.continue({
method: 'GET',
url: 'http://localhost:8080/test',
headers: {
...req.headers(),
'X-Test': 'true',
},
postData: JSON.stringify({
...req.postDataJSON(),
test: true,
}),
});
});Proceed with request, but intercept response
await page.route('**/*', (route) => {
const response = await route.fetch();
route.fulfill({
response,
json: {
...await response.json(),
test: true,
},
})
});Fulfill routed request with custom response
await page.route('**/*', (route) => {
route.fulfill({
status: 404,
json: {message: 'not found'},
});
});Fulfill routed request with local file
await page.route('**/*.png', (route) => {
route.fulfill({
path: './1-pixel.png',
});
});Abort routed request
await page.route('**/*', (route) => {
route.abort();
});Abort routed request with custom error
await page.route('**/*', (route) => {
route.abort('connectionrefused'); // see docs for values
});HAR Replay Network Traffic
HAR (HTTP Archive format) records network traffic to replay later. Use for exact reproduction of test conditions or in advanced workflows.
Respond using HAR, aborting unknown requests
await page.routeFromHAR('./recorded.har');Respond using HAR, allowing unknown requests
await page.routeFromHAR('./recorded.har', {
notFound: 'fallback',
});Respond using HAR, for requests matching pattern
await page.routeFromHAR('./recorded.har', {
url: /.png$/i,
});Record HAR file using network traffic
await page.routeFromHAR('./recorded.har', {
update: true,
});Image Generation
Take screenshots of entire pages or specific elements. Most configuration options work in both cases.
Screenshot current viewport
await page.screenshot();Screenshot entire page
await page.screenshot({fullPage: true});Screenshot specific element
const $element = page.locator('h1');
await $element.screenshot();Resize viewport before screenshot
await page.setViewportSize({
width: 2000,
height: 1000,
});
await page.screenshot();Screenshot custom HTML/CSS content
await page.setContent(`
<html>
<head>
<style>
body { background-color: red; }
</style>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
`);
await page.screenshot();Apply custom stylesheet during screenshot
await page.screenshot({
style: './path/to/screenshot.css',
});Hide specific elements in screenshot
await page.screenshot({
mask: [
page.locator('input'),
page.getByRole('button'),
page.locator('.sensitive'),
],
});Clip screenshot to specific region
await page.screenshot({
clip: {x: 0, y: 0, width: 100, height: 100},
});Save screenshot to file
await page.screenshot({
path: './screenshot.png',
});Save screenshot as JPEG
await page.screenshot({
type: 'jpeg',
quality: 80, // 0-100
});Tracing
Record context trace
await context.tracing.start();Record context trace with custom prefix
await context.tracing.start({
name: 'checkout-process',
});Record screenshots for trace
await context.tracing.start({
screenshots: true,
});Record snapshots of all actions for trace
await context.tracing.start({
snapshots: true,
});Include source files in trace
await context.tracing.start({
sources: true,
});Stop recording context trace
const trace = await context.tracing.stop();Save context trace to specific file
await context.tracing.stop({
path: './trace.json',
// default: browser.launch({tracesDir})
});Debugging
Launch browser with custom logger
const browser = await pw.chromium.launch({
logger: {
isEnabled: (name, sev) => sev === 'error',
log: (name, sev, msg, args) => console.debug(name, msg),
},
});Launch browser with slow motion
const browser = await pw.chromium.launch({
slowMo: 100, // ms delay between actions
});Launch browser in headed mode
const browser = await pw.chromium.launch({
headless: false,
});Open browser DevTools automatically
const browser = await pw.chromium.launch({
devtools: true,
});Browser Servers
Connect to a remote Playwright browser server
const browser = await pw.chromium.connect(
process.env.BROWSER_WS_ENDPOINT!,
);Connect to remote browser server over CDP
const browser = await pw.chromium.connectOverCDP(
'http://localhost:9222',
);Launch a local browser server
const browserServer = await pw.chromium.launchServer();
const wsEndpoint = browserServer.wsEndpoint();Connect to local browser server
const browser = await pw.chromium.connect(wsEndpoint);Close browser server
await browserServer.close();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 TSCreate a test file
touch tests/example.spec.js # with JS
touch tests/example.spec.ts # with TSWrite a basic test
import {test, expect} from '@playwright/test';
test('has brand in <title>', async ({ page }) => {
await page.goto('https://www.browsersolver.com/');
await expect(page).toHaveTitle(/BrowserSolver/);
});Generate code from user behavior
npx playwright codegenRun your tests
npx playwright testRun your tests in UI mode
npx playwright test --uiShow test results in browser
npx playwright show-reportWriting Tests
Write a basic test
import {test, expect} from '@playwright/test';
test('has brand in <title>', async ({ page }) => {
await page.goto('https://www.browsersolver.com/');
await expect(page).toHaveTitle(/BrowserSolver/);
});Attach screenshot to a test
test('with screenshot', async ({page}) => {
await test.info().attach('screenshot', {
contentType: 'image/png',
body: await page.screenshot(),
});
});Test Groups
Test groups cluster tests for shared configuration and organized reporting.
Create a test group
test.describe('group', async () => {
test('test', async () => {});
});Create nested test groups
test.describe('group', async () => {
test.describe('subgroup', async () => {
test('test', async () => {});
});
});Test Steps
Test steps improve report readability. They can also be reused across multiple tests.
Break a test into steps
test('long test', async () => {
await step('step 1', async () => {});
await step('step 2', async () => {});
await step('step 3', async () => {});
});Create reusable test steps
async function login(user: string, pass: string) {
return test.step('login', async ({page}) => {
await page.fill('input[name="username"]', user);
await page.fill('input[name="password"]', pass);
await page.click('button[type="submit"]');
});
}
test('login, then interact', async () => {
await login('user', 'pass');
});Test Fixtures
Use Playwright's built-in fixtures to save time on setup and teardown.
Access `page` fixture in test
test('test', async ({page}) => {
// `page` is exclusive to this test
await page.goto('https://www.browsersolver.com');
});Access `context` fixture in test
test('test', async ({context}) => {
// `context` is exclusive to this test
const page = await context.newPage();
});Access `browser` fixture in test
test('test', async ({browser}) => {
// `browser` is shared across worker thread
const context = await browser.newContext();
});Access `request` fixture in test
test('test', async ({request}) => {
// `request` is exclusive to this test
await request.get('https://api.browsersolver.com');
});Custom Test Fixtures
Create fixtures to share behavior and improve readability.
Create fixture with setup and teardown
import {test as base, type BrowserContext} from '@playwright/test';
type TestFixtures = {
authContext: BrowserContext;
};
export const test = base.extend<TestFixtures>({
authContext: async ({browser}, use) => {
// setup before each test
const authContext = await browser.newContext({
storageState: 'auth.json',
});
// run test
await use(authContext);
// teardown after each test
await authContext.close();
},
});Override fixture implementation
export const test = base.extend({
page: [async ({page}, use) => {
page.addInitScript('tests/init.js');
await use(page);
}],
});Combine multiple custom fixture assignments
import {test, mergeTests} from '@playwright/test';
const test1 = test.extend({});
const test2 = test.extend({});
export const test = mergeTests(test1, test2);Share fixture across all tests in worker
export const test = base.extend<{}, WorkerFixtures>({
authContext: [async ({browser}, use) => {
const authContext = await browser.newContext({
storageState: 'auth.json',
});
await use(authContext);
await authContext.close();
}, {scope: 'worker'}],
});Test Runner Configuration
Create a config file
touch playwright.config.tsWrite basic config
import {defineConfig} from '@playwright/test';
export default defineConfig({
testMatch: 'tests/**/*.spec.{ts,tsx}',
});Minimize CLI output
export default defineConfig({
quiet: !!process.env.CI,
});Configure Exit Criteria
Retry failed tests before marking as failed
export default defineConfig({
retries: 3, // default 0
});Repeat all tests before marking as passed
export default defineConfig({
repeatEach: 3, // default 1
});Fail individual tests if they exceed timeout
export default defineConfig({
timeout: 1000 * 30,
});Fail test suite if exceeds timeout
export default defineConfig({
globalTimeout: 1000 * 60 * 60,
});Fail test suite early, after N failures
export default defineConfig({
maxFailures: 10, // default 0
});Output Files
Select test results output path
export default defineConfig({
outputDir: './.test/results',
});Only preserve test results on failure
export default defineConfig({
preserveOutput: 'failures-only',
});Test Environment Options
Configure your test environment, browser, and emulated device.
Connect tests to a remote browser server
export default defineConfig({
use: {
connectOptions: {
wsEndpoint: process.env.BROWSER_WS_ENDPOINT!,
},
},
});Configure Behavior
Show browser window during tests
export default defineConfig({
use: {
headless: false,
},
});Screenshot tests automatically
export default defineConfig({
use: {
screenshot: 'on', // or 'only-on-failure' | 'off'
},
});Record video of tests automatically
export default defineConfig({
use: {
video: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'off'
},
});Record trace data for tests automatically
export default defineConfig({
use: {
trace: 'on', // or 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'off'
},
});Set delay between user actions
export default defineConfig({
use: {
actionTimeout: 1000 * 3, // default: 0
},
});Enable or disable JavaScript
export default defineConfig({
use: {
javaScriptEnabled: false, // default: true
},
});Grant custom browser permissions automatically
export default defineConfig({
use: {
permissions: ['geolocation', 'notifications'],
},
});Configure Network Traffic
Enable relative URLs with custom base URL
export default defineConfig({
use: {
// Allows tests to use relative URLs
// e.g. `page.goto('/login');`
baseURL: isDev ?
'https://localhost:8080' :
'https://www.example.com',
},
});Set custom HTTP credentials
export default defineConfig({
use: {
httpCredentials: {
username: 'user',
password: 'pass',
},
},
});Send custom default HTTP headers with requests
export default defineConfig({
use: {
extraHTTPHeaders: {
'X-My-Header': 'value',
},
},
});Route all traffic through a proxy server
export default defineConfig({
use: {
proxy: {
server: 'http://localhost:8080',
username: 'user',
password: 'pass',
bypass: 'browsersolver.com, .example.com',
},
},
});Ignore HTTPS errors
export default defineConfig({
use: {
ignoreHTTPSErrors: process.env.NODE_ENV === 'development',
},
});Configure Browsers
Emulate specific browsers quickly
import {defineConfig, devices} from '@playwright/test';
export default defineConfig({
projects: [{
name: 'Desktop Chrome',
use: {
...devices['Desktop Chrome'],
},
}, {
name: 'Mobile Chrome',
use: {
...devices['Mobile Chrome'],
},
}],
});Select browser to use for tests
export default defineConfig({
use: {
browserName: 'firefox', // or 'webkit' | 'chromium'
},
});Set user preferred color scheme
export default defineConfig({
use: {
colorScheme: 'dark', // or 'light' | 'no-preference'
},
});Set browser viewport size
export default defineConfig({
use: {
viewport: { width: 1920, height: 1080 },
},
});Configure Locale
Emulate custom locale
export default defineConfig({
use: {
locale: 'en-US',
},
});Emulate custom timezone
export default defineConfig({
use: {
timezoneId: 'America/New_York',
},
});Emulate custom geolocation
export default defineConfig({
use: {
geolocation: {
latitude: 40.730610,
longitude: -73.935242,
accuracy: 11.08,
},
},
});Test Parallelization
By default, Playwright executes test files in parallel, but it executes a file's tests in order.
Run test files in parallel, tests sequentially
export default defineConfig({
fullyParallel: false, // default
});Run all tests in all files in parallel
export default defineConfig({
fullyParallel: true, // default: false
});Set number of parallel workers
export default defineConfig({
workers: 3, // default: 50% logical CPUs
});Disable all parallelism (forcefully)
export default defineConfig({
workers: 1,
});Test File & Group Parallelism
Note that parallelizing tests within a file prohibits tests from sharing state. Use wisely.
Run a single file's tests in parallel
test.describe.configure({
mode: 'parallel', // default 'serial'
});
test('test', async () => {});Run a test group's tests in parallel
test.describe('group', async () => {
test.describe.configure({
mode: 'parallel', // default 'serial'
});
test('test', async () => {});
});Page Assertions
Assert page has specific title
await expect(page).toHaveTitle('My Page Title');
await expect(page).toHaveTitle(/My Page/);Assert page has specific URL
await expect(page).toHaveURL('https://example.com/login');
await expect(page).toHaveURL(//login$/);Element Property Assertions
Assert element is visible
await expect(locator).toBeVisible();Assert element is hidden
await expect(locator).toBeHidden();Assert element is enabled
await expect(locator).toBeEnabled();Assert element is disabled
await expect(locator).toBeDisabled();Assert element is checked
await expect(locator).toBeChecked();Assert element is focused
await expect(locator).toBeFocused();Assert element is in the viewport
await expect(locator).toBeInViewport();Assert element has text content
await expect(locator).toHaveText('Submit');
await expect(locator).toHaveText(/submit/i);Assert element has attribute
await expect(locator).toHaveAttribute('href', '/home');Assert element has class
await expect(locator).toHaveClass('active');Assert element has value
await expect(locator).toHaveValue('Hello');Assert list has N elements
await expect(locator).toHaveCount(5);Assert element has CSS property
await expect(locator).toHaveCSS('color', 'rgb(255, 0, 0)');Visual Assertions
Page Screenshot Assertions
Assert page matches screenshot
await expect(page).toHaveScreenshot();Assert image matches named screenshot
// compare to a single image shared across tests
await expect(page).toHaveScreenshot('home-page.png');Assert page roughly matches screenshot
await expect(page).toHaveScreenshot({
// pixel color can vary by 10% in YIQ space
threshold: 0.1,
// 10% of pixels can exceed threshold
maxDiffPixelRatio: 0.1,
});Element Screenshot Assertions
Assert element matches screenshot
await expect(locator).toHaveScreenshot();Assert element matches named screenshot
await expect(locator).toHaveScreenshot('home-hero.png');Test Annotations
.only() Tests
Run only this test
test.only('test', async () => {});Run only tests in this group
test.describe.only('group', async () => {
test('test', async () => {});
});.skip() Tests
Skip this test
test.skip('test', async () => {});Skip test conditionally
test('test', async ({page}) => {
test.skip(process.env.CI === 'true', 'Not running in CI');
});.fixme() Tests
Mark test as fixme (skipped)
test.fixme('test', async () => {});.slow() Tests
Mark test as slow (3x timeout)
test('test', async () => {
test.slow();
});.fail() Tests
Mark test as expected to fail
test.fail('test', async () => {});API Testing
Send GET request
test('get users', async ({request}) => {
const res = await request.get('https://api.example.com/users');
expect(res.status()).toBe(200);
const data = await res.json();
expect(data).toHaveLength(10);
});Send POST request
test('create user', async ({request}) => {
const res = await request.post('https://api.example.com/users', {
data: {name: 'Jane Doe', email: 'jane@example.com'},
});
expect(res.status()).toBe(201);
});Send PUT request
test('update user', async ({request}) => {
const res = await request.put('https://api.example.com/users/1', {
data: {name: 'Updated Name'},
headers: {'Authorization': 'Bearer token'},
});
expect(res.status()).toBe(200);
});Send DELETE request
test('delete user', async ({request}) => {
const res = await request.delete('https://api.example.com/users/1');
expect(res.status()).toBe(204);
});Parameterize Tests
Parameterize tests across multiple projects
// playwright.config.ts
export type TestOptions = {
cat: string;
};
export default defineConfig<TestOptions>({
projects: [{
name: 'cartoons',
use: {cat: 'Garfield'},
}, {
name: 'sitcoms',
use: {cat: 'Smelly Cat'},
}]
});
// extended-test.ts
import {test} from '@playwright/test';
import type {TestOptions} from 'playwright.config.ts';
export default test.extend<TestOptions>({
cat: ['Hello Kitty', {option: true}],
});
// profiles.spec.ts
import test from './extended-test.ts';
test('test', async ({page, cat}) => {
await expect(page).toHaveTitle(cat);
});Local Web Server
Launch web server during tests
export default defineConfig({
webServer: {
command: 'npm run start',
url: 'http://localhost:3000',
reuseExistingServer: true,
},
use: {
baseURL: 'http://localhost:3000',
},
});Launch multiple web servers during tests
export default defineConfig({
webServer: [{
command: 'npm run start:app',
url: 'http://localhost:3000',
}, {
command: 'npm run start:api',
url: 'http://localhost:3030',
}],
});Test Reporters
Configure test reporters
export default defineConfig({
reporter: [
['list'],
['json', {outputFile: 'test-results.json'}],
['junit', {outputFile: 'test-results.xml'}],
],
});CLI Reporters
Use list reporter (default)
export default defineConfig({
reporter: 'list',
});Use dot reporter
export default defineConfig({
reporter: 'dot',
});Use line reporter
export default defineConfig({
reporter: 'line',
});File Reporters
Use JSON reporter
export default defineConfig({
reporter: [['json', {outputFile: 'test-results.json'}]],
});Use JUnit reporter
export default defineConfig({
reporter: [['junit', {outputFile: 'results.xml'}]],
});Use HTML reporter
export default defineConfig({
reporter: [['html', {outputFolder: 'playwright-report'}]],
});Run Tests via CLI
Run all tests
npx playwright testRun tests in a specific file
npx playwright test tests/example.spec.tsRun tests matching pattern
npx playwright test --grep "login"Run tests in a specific project
npx playwright test --project "Desktop Chrome"Run tests in headed mode
npx playwright test --headedRun tests in debug mode
npx playwright test --debugRun tests with specific number of workers
npx playwright test --workers 4Run tests and show results in browser
npx playwright test --reporter html
npx playwright show-reportMouse Control
Move mouse and click at specific coordinates
await page.mouse.click(100, 100);Move mouse and click with delay between events
await page.mouse.click(100, 100, {
delay: 100,
});Click element with modifier keys
await page.locator('button').click({
modifiers: ['Shift'],
});Click element with custom X/Y coordinates
await page.locator('button').click({
position: {x: 10, y: 10},
});Move mouse and double-click at specific coordinates
await page.mouse.dblclick(100, 100);Double-click element
await page.locator('button').dblclick();Scroll mouse wheel vertically
await page.mouse.wheel(0, 100);Scroll mouse wheel horizontally
await page.mouse.wheel(100, 0);Hover element
await page.locator('button').hover();Hover element with custom options
await page.locator('button').hover({
modifiers: ['Meta'],
position: {x: 10, y: 10},
});Drag and Drop
Playwright supports manual and automatic drag-and-drop behavior. Use the automatic method, if possible.
Drag and drop element (easy method)
const $source = page.locator('#source');
const $target = page.locator('#target');
await $source.dragTo($target);Drag and drop with custom X/Y coordinates
const $source = page.locator('#source');
const $target = page.locator('#target');
await $source.dragTo($target, {
// relative to top-left corner
sourcePosition: {x: 10, y: 10},
targetPosition: {x: 10, y: 10},
});Drag and drop element (manual method)
const $source = page.locator('#source');
const $target = page.locator('#target');
await $source.scrollIntoViewIfNeeded();
await $source.hover();
const sourceBox = await $source.boundingBox();
await page.mouse.move(
sourceBox.x + sourceBox.width / 2,
sourceBox.y + sourceBox.height / 2,
);
await page.mouse.down();
await $target.scrollIntoViewIfNeeded();
await $target.hover();
await $target.hover(); // needed for some browsers
const targetBox = await $target.boundingBox();
await page.mouse.move(
targetBox.x + targetBox.width / 2,
targetBox.y + targetBox.height / 2,
);
await page.mouse.up();Low-level Mouse Events
Dispatch `mousedown` event
await page.mouse.down();Dispatch `mouseup` event
await page.mouse.up();Dispatch `mousemove` event
await page.mouse.move(100, 100);Dispatch `mousemove` event across smooth steps
await page.mouse.move(100, 100, {
steps: 5, // default: 1
});Dispatch mouse events to specific element
const $button = page.locator('button');
await $button.dispatchEvent('click');
await $button.dispatchEvent('dblclick');
await $button.dispatchEvent('mousedown');
await $button.dispatchEvent('mouseup');
await $button.dispatchEvent('mousemove');Keyboard Control
Type text with keyboard
await page.locator('input').focus();
await page.keyboard.type('Hello');Type text with delay between presses
await page.keyboard.type('Hello', {
delay: 100,
});Press key combination
await page.keyboard.press('F12');
await page.keyboard.press('Control+c');Press key combination with delay
await page.keyboard.press('Control+v', {
delay: 100,
});Press key combination in specific element
await page.locator('textarea').press('Control+Z');Low-level Keyboard Events
Dispatch `keydown` event
await page.keyboard.down('Shift');
await page.keyboard.down('a');Dispatch `keyup` event
await page.keyboard.up('a');
await page.keyboard.up('Shift');Dispatch keyboard events on specific element
const $input = page.locator('input');
await $input.dispatchEvent('keydown', {key: 'a'});
await $input.dispatchEvent('keyup', {key: 'b'});
await $input.dispatchEvent('keypress', {key: 'c'});Touchscreen Control
Tap screen at specific coordinates
await page.touchscreen.tap(100, 100);Low-level Touchscreen Events
Dispatch touchscreen events to specific element
const $link = page.locator('a');
await $link.dispatchEvent('touchstart');
await $link.dispatchEvent('touchmove');
await $link.dispatchEvent('touchend');
await $link.dispatchEvent('touchcancel');Events and Listeners
Listen for page load event
page.on('load', () => {});Listen for page DOM content loaded event
page.on('domcontentloaded', () => {});Listen for page close event
page.on('close', () => {});Listen for page crash event
page.on('crash', () => {});Listen for page dialog event
page.on('dialog', async (dialog) => {
console.log(dialog.message());
await dialog.accept();
});Listen for console messages
page.on('console', (msg) => {
console.log(msg.type(), msg.text());
});Remove event listener
const handler = () => {};
page.on('load', handler);
page.off('load', handler);Listen for event only once
page.once('load', () => {});Wait for an event to be emitted
const response = await page.waitForEvent('response');