Playwright cheat sheet
A scannable Playwright reference: 30 short snippets across 12 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Install and first test | Setting up Playwright, running the generated example test, and the trace viewer that makes failures debuggable | lesson |
| Locators and assertions | A locator is lazy: it describes how to find an element and is resolved again on each use. That is exactly what lets | lesson |
| Runner, fixtures and CI | Projects and workers, typed fixtures for shared setup, and a CI plan that keeps traces when a test fails | lesson |
| Actions and user interactions | Everything on the page object - keyboard, mouse, touchscreen - acts on the page as a whole and bypasses locator checks | lesson |
| Auto-waiting, timeouts and flakiness | Before an action, Playwright waits for a specific set of conditions and retries until they hold or the timeout expires | lesson |
| Network interception and mocking | Fulfil, modify and abort requests, record a HAR for replay, and assert on what the page actually sent | lesson |
| API testing with request contexts | Use the request fixture for direct API tests, and API calls for setup, so a UI test starts from a known server state | lesson |
| Test organisation and page objects | Compose page objects with fixtures, build test data explicitly, and use tags and sharding to keep a growing suite fast | lesson |
| Debugging with UI mode, codegen and inspector | Codegen produces working code, not good code. Its selectors are whatever was stable at the moment you clicked, and they | lesson |
| Visual comparisons, screenshots and video | Set up screenshot baselines that do not fail on every font update, mask the parts that legitimately change, and capture | lesson |
| Multiple browsers, devices and emulation | Run the same tests on Chromium, Firefox and WebKit projects, emulate a phone properly, and control locale, timezone | lesson |
| Component testing and advanced configuration | Mount components in isolation with Playwright CT, write custom reporters, use global setup, and shard a large suite | lesson |
Quick snippets
Install and first test
Install and run
npm init playwright@latest
# asks for TypeScript or JavaScript, installs browsers, writes playwright.config.ts
npx playwright test # run everything headless
npx playwright test --headed # watch the browser
npx playwright test --ui # interactive time-travel interface
npx playwright show-report # open the HTML report
What a generated test looks like
import { test, expect } from '@playwright/test';
test('home page has the right title', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
test('get started link reaches the docs', async ({ page }) => {
await page.goto('https://playwright.dev/');
await page.getByRole('link', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});Full lesson: Install and first test →
Locators and assertions
Locators that survive a redesign
// preferred: semantics a user would recognise
page.getByRole('button', { name: 'Sign in' });
page.getByLabel('Email address');
page.getByPlaceholder('Search');
// scope to a container, then filter
const row = page.getByRole('row').filter({ hasText: 'Ada Lovelace' });
await row.getByRole('button', { name: 'Edit' }).click();
// escape hatch when nothing semantic exists in the markup
page.getByTestId('checkout-total');
Web-first assertions
await expect(page.getByRole('alert')).toHaveText('Saved');
await expect(page.getByRole('listitem')).toHaveCount(3);
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled();
await expect(page).toHaveURL(/\/dashboard$/);
await expect(page.getByTestId('spinner')).toBeHidden();
// negate with .not
await expect(page.getByText('Error')).not.toBeVisible();Full lesson: Locators and assertions →
Runner, fixtures and CI
Running in CI
# the ordered CI steps
npm ci
npx playwright install --with-deps chromium
npx playwright test --shard=1/3
npx playwright merge-reports --reporter html ./blob-reportFull lesson: Runner, fixtures and CI →
Actions and user interactions
Clicks that are not a plain click
const row = page.getByRole("row", { name: "Invoice 1042" });
await row.click(); // left, centre, once
await row.dblclick();
await row.click({ button: "right" }); // context menu
await row.click({ modifiers: ["Shift"] }); // range select
await row.click({ position: { x: 10, y: 10 } }); // a specific spot
await row.hover();
await row.click({ trial: true }); // check actionability, do nothing
Clicks that are not a plain click
// select a range the way a user does
await page.getByRole("row", { name: "Invoice 1042" }).click();
await page.getByRole("row", { name: "Invoice 1047" }).click({ modifiers: ["Shift"] });
// hover to reveal a menu, then click the item
await page.getByRole("button", { name: "Actions" }).hover();
await page.getByRole("menuitem", { name: "Duplicate" }).click();
// drag and drop
await page.getByTestId("card-1").dragTo(page.getByTestId("column-done"));
Files, keyboard and scrolling
// keyboard focus and chords
await page.getByRole("textbox").focus();
await page.keyboard.press("Control+A");
await page.keyboard.press("Backspace");
await page.keyboard.type("replaced");
// global shortcut
await page.keyboard.press("Control+Shift+P");
// programmatic scrolling, when you need a specific element in view
await page.getByText("Terms and conditions").scrollIntoViewIfNeeded();Full lesson: Actions and user interactions →
Auto-waiting, timeouts and flakiness
What auto-waiting actually checks
// wait for the state you actually depend on, not for time to pass
const banner = page.getByRole("alert");
await banner.waitFor({ state: "visible" });
await expect(banner).toHaveText("Saved");
// states: attached, detached, visible, hidden
await page.getByTestId("spinner").waitFor({ state: "hidden" });
// page-level conditions
await page.waitForLoadState("domcontentloaded");
await page.waitForURL("**/dashboard");
await page.waitForResponse((r) => r.url().includes("/api/orders") && r.ok());
Web-first assertions
// retried until it passes or the expect timeout expires
await expect(page.getByRole("heading")).toHaveText("Invoices");
await expect(page.getByRole("listitem")).toHaveCount(3);
await expect(page.getByRole("button", { name: "Pay" })).toBeEnabled();
await expect(page.getByLabel("Email")).toHaveValue("[email protected]");
await expect(page.getByTestId("total")).toContainText("42.00");
await expect(page).toHaveURL(/\/invoices\/\d+/);
await expect(page).toHaveTitle(/Invoices/);
// negation also retries, which is usually what you want
await expect(page.getByText("Loading")).toBeHidden();
Web-first assertions
// polling something that is not a locator
await expect.poll(async () => {
const res = await page.request.get("/api/orders");
return (await res.json()).length;
}, { timeout: 10_000 }).toBe(3);
// retrying a whole block
await expect(async () => {
await page.getByRole("button", { name: "Refresh" }).click();
await expect(page.getByRole("row")).toHaveCount(4);
}).toPass({ timeout: 15_000 });Full lesson: Auto-waiting, timeouts and flakiness →
Network interception and mocking
Record once, replay always
// record: run with the real network, write the archive
await page.routeFromHAR("hars/orders.har", {
url: "**/api/**",
update: true,
});
// replay: no network needed for the recorded endpoints
await page.routeFromHAR("hars/orders.har", {
url: "**/api/**",
notFound: "abort", // or "fallback" to hit the real network
});
Asserting on what was sent
// a fixture that fails the test on any unexpected 5xx
export const test = base.extend({
page: async ({ page }, use) => {
const failures = [];
page.on("response", (r) => {
if (r.status() >= 500) failures.push(r.status() + " " + r.url());
});
await use(page);
expect(failures, "unexpected server errors").toEqual([]);
},
});Full lesson: Network interception and mocking →
API testing with request contexts
The request fixture
// a separate context, with its own base URL and headers
const api = await request.newContext({
baseURL: "https://api.staging.example.com",
extraHTTPHeaders: { Accept: "application/json" },
storageState: "playwright/.auth/api.json",
});
Schema checks and contract drift
// a compact contract suite
for (const path of ["/api/orders/1042", "/api/users/me"]) {
test("contract: " + path, async ({ request }) => {
const res = await request.get(path);
await expect(res).toBeOK();
expect(res.headers()["content-type"]).toContain("application/json");
});
}Full lesson: API testing with request contexts →
Test organisation and page objects
Tags, groups and sharding
test.describe("checkout", { tag: ["@checkout", "@slow"] }, () => {
test("pays with a saved card", { tag: "@smoke" }, async ({ page }) => {
// ...
});
});
test.skip(({ browserName }) => browserName === "firefox", "known Firefox bug");
Tags, groups and sharding
npx playwright test --grep @smoke # the fast subset
npx playwright test --grep-invert @slow # everything else
npx playwright test --shard=1/3 # one third of the suite
npx playwright test --shard=1/3 --reporter=blob # merge reports afterwards
npx playwright merge-reports --reporter=html ./blob-report
Tags, groups and sharding
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/4 --reporter=blob
- uses: actions/upload-artifact@v4
with:
name: blob-${{ matrix.shard }}
path: blob-reportFull lesson: Test organisation and page objects →
Debugging with UI mode, codegen and inspector
UI mode
npx playwright test --ui
# in UI mode:
# - pick a test and watch it run with a timeline
# - hover a step to see the DOM snapshot at that moment
# - open the source, the console and the network for that step
# - toggle "watch" and re-run on every file save
# - click the locator picker to test a selector against the live page
Codegen and the inspector
# scaffold a test by using the app
npx playwright codegen https://staging.example.com
# start from a saved session so you are already logged in
npx playwright codegen --load-storage=playwright/.auth/user.json https://staging.example.com
# emulate a device while recording
npx playwright codegen --device="iPhone 15" https://staging.example.com
# record against a local file
npx playwright codegen ./fixtures/page.html
Codegen and the inspector
# step through an existing test
npx playwright test invoices.spec.ts --debug
# or pause at a specific point from inside the test
# await page.pause();Full lesson: Debugging with UI mode, codegen and inspector →
Visual comparisons, screenshots and video
Baselines and expected diffs
npx playwright test visuals.spec.ts # compare
npx playwright test visuals.spec.ts --update-snapshots # accept the new baseline
# inspect the difference report
npx playwright show-report
Screenshots and video on failure
use: {
screenshot: "only-on-failure", // or "on" / "off"
video: "retain-on-failure", // or "on-first-retry" / "off"
trace: "on-first-retry",
},
Screenshots and video on failure
// an ad hoc capture from inside a test
await page.screenshot({ path: "artifacts/step-1.png", fullPage: true });
await page.locator("#summary").screenshot({ path: "artifacts/summary.png" });
await page.video()?.saveAs("artifacts/run.webm");Full lesson: Visual comparisons, screenshots and video →
Multiple browsers, devices and emulation
One suite, several browsers
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
{ name: "mobile-safari", use: { ...devices["iPhone 15"] } },
{ name: "mobile-chrome", use: { ...devices["Pixel 8"] } },
],
});
One suite, several browsers
npx playwright test --project=chromium
npx playwright test --project=webkit --project=firefox
npx playwright install --with-deps # fetch browser binaries and OS deps
Locale, timezone and colour scheme
// per-test overrides when you only need one variation
test.describe("light theme", () => {
test.use({ colorScheme: "light" });
test("renders a readable contrast ratio", async ({ page }) => { /* ... */ });
});Full lesson: Multiple browsers, devices and emulation →
Component testing and advanced configuration
Component testing
npm init playwright@latest -- --ct
# a separate config and a separate test directory are created
npx playwright test -c playwright-ct.config.ts
Global setup and teardown
// playwright.config.ts
export default defineConfig({
globalSetup: "./global-setup.ts",
globalTeardown: "./global-teardown.ts",
webServer: {
command: "npm run start:test",
url: "http://localhost:3000/health",
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
Reporters and sharding
reporter: [
["list"],
["html", { open: "never", outputFolder: "playwright-report" }],
["junit", { outputFile: "reports/junit.xml" }],
["./my-reporter.ts"],
],
workers: process.env.CI ? 2 : undefined,Full lesson: Component testing and advanced configuration →
FAQ
Is this Playwright cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
AI Basics AI Agents Math for AI Machine Learning scikit-learn TensorFlow
Last refreshed 2026-09-27.