Playwright is one of the strongest choices for modern end-to-end testing because it combines browser automation, reliable waiting behavior, cross-browser support, and a clean developer experience. This Playwright cheat sheet is built for speed: scan the syntax, copy the patterns, and use the examples as a reference while you build or maintain tests.
What this Playwright cheat sheet covers
This page focuses on the Playwright fundamentals that teams use most often:
- installation and setup
- running tests from the CLI
- locators and selectors
- common actions
- assertions
- waiting and synchronization
- fixtures
- mocking and network control
- authentication
- debugging and trace-based investigation
- code generation
- best practices
- common mistakes
Playwright at a glance
Playwright is a browser automation and testing framework used for end-to-end testing, UI testing, and broader web testing workflows. Playwright is positioned around reliable browser automation, with strong support for locators, assertions, multiple browsers, test isolation, and debugging tools.
| Use case | Why Playwright fits |
|---|---|
| End-to-end testing | Covers full user journeys across browsers |
| UI automation | Interacts with modern front-end apps reliably |
| Regression testing | Good for repeatable browser checks |
| Cross-browser validation | Supports Chromium, Firefox, and WebKit |
| Authenticated flows | Handles reusable session state |
| API + UI testing | Works across both browser and network layers |
Core Playwright commands
Quick Command Recap
| Task | Command |
|---|---|
| Install Playwright | npm init playwright@latest |
| Run all tests | npx playwright test |
| Run one file | npx playwright test tests/example.spec.ts |
| Run in headed mode | npx playwright test --headed |
| Run in UI mode | npx playwright test --ui |
| Debug tests | npx playwright test --debug |
| Run last failed tests | npx playwright test --last-failed |
| Show HTML report | npx playwright show-report |
| Generate starter code | npx playwright codegen https://example.com |
First Playwright test
A typical Playwright setup starts with installing the package, initializing the project, and then writing your first test file.
import { test, expect } from '@playwright/test';
test('homepage has title', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
Setup and configuration
Typical config ideas
A Playwright config usually handles:
- browser projects
- base URL
- retries
- trace collection
- test timeout
- parallel execution
- screenshots and videos on failure
Example config snippet
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'https://example.com',
trace: 'on-first-retry',
},
});
Running Tests
The Playwright CLI is built for flexibility. You can run all tests, a single test file, a subset of tests, a specific browser project, or only the tests that failed last time. The docs also document --debug for Inspector-based debugging.
Common commands
npx playwright test
npx playwright test landing-page.spec.ts
npx playwright test tests/todo-page/
npx playwright test landing login
npx playwright test --last-failed
Run on a specific browser project
npx playwright test --project webkit
npx playwright test --project firefox
Debug mode
npx playwright test --debug
Run one test at a specific line
npx playwright test example.spec.ts:10 --debug
Playwright tests run in parallel by default, and projects let you target multiple browsers or device profiles from the same suite.
Locators cheat sheet
Locators are one of the most important concepts in Playwright. Playwright works best when you target the page the way a user would. That means preferring visible roles, labels, and text before falling back to CSS selectors.
| Locator | Example | Best for |
|---|---|---|
getByRole() | page.getByRole('button', { name: 'Sign in' }) | Buttons, links, menus, form controls |
getByLabel() | page.getByLabel('Email') | Form fields |
getByText() | page.getByText('Continue') | Visible text |
getByPlaceholder() | page.getByPlaceholder('Search') | Inputs with placeholders |
getByAltText() | page.getByAltText('Logo') | Images |
getByTitle() | page.getByTitle('Help') | Tooltips and titles |
getByTestId() | page.getByTestId('submit-btn') | Stable custom hooks |
locator() | page.locator('.card') | CSS targeting |
frameLocator() | page.frameLocator('iframe') | Embedded frames |
Locator examples
await page.getByRole('button', { name: 'Save' }).click();
await page.getByLabel('Password').fill('secret');
await page.getByText('Continue').click();
Common actions
Once an element is located, Playwright makes common user interactions straightforward. These actions are most reliable when combined with resilient locators rather than CSS paths or arbitrary timeouts.
| Action | Example |
|---|---|
| Click | await page.getByRole('button', { name: 'Save' }).click(); |
| Fill input | await page.getByLabel('Email').fill('me@example.com'); |
| Type | await page.getByLabel('Search').type('playwright'); |
| Select option | await page.getByLabel('Country').selectOption('IN'); |
| Check checkbox | await page.getByLabel('Subscribe').check(); |
| Uncheck checkbox | await page.getByLabel('Subscribe').uncheck(); |
| Upload file | await page.getByLabel('Upload').setInputFiles('file.pdf'); |
| Hover | await page.getByText('Menu').hover(); |
| Press a key | await page.keyboard.press('Enter'); |
| Double click | await page.getByText('Item').dblclick(); |
Action pattern
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('me@example.com');
await page.getByRole('button', { name: 'Submit' }).click();
Assertions cheat sheet
Assertions are one of the reasons Playwright feels stable. Assertions are written with expect, and emphasize async matchers that wait for the expected condition. This is a key reason Playwright tests are less flaky than tests that rely on manual sleeps.
| Assertion | Example | Use case |
|---|---|---|
toBeVisible() | await expect(page.getByText('Success')).toBeVisible(); | Element should appear |
toBeHidden() | await expect(page.getByText('Loading')).toBeHidden(); | Element should disappear |
toHaveText() | await expect(page.getByTestId('status')).toHaveText('Done'); | Exact text match |
toContainText() | await expect(page.locator('h1')).toContainText('Dashboard'); | Partial text match |
toHaveCount() | await expect(page.locator('li')).toHaveCount(3); | Element count |
toHaveURL() | await expect(page).toHaveURL(/dashboard/); | Navigation check |
toHaveTitle() | await expect(page).toHaveTitle(/Home/); | Page title |
toBeEnabled() | await expect(button).toBeEnabled(); | Clickable state |
toBeDisabled() | await expect(button).toBeDisabled(); | Disabled state |
toHaveValue() | await expect(input).toHaveValue('john'); | Input value |
Assertion example
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page).toHaveURL(/dashboard/);
The docs recommend web-first assertions because they automatically wait for the page to reach the expected state. That makes them a better default than ad hoc polling or manual delays.
Waiting and synchronization
Playwright is designed to wait intelligently. In most cases, you should wait for a visible outcome rather than hard-coding time. Playwright’s auto-waiting covers many common cases, so the default approach should be to rely on locators and assertions rather than fixed pauses.
| Prefer | Avoid |
|---|---|
await expect(locator).toBeVisible() | await page.waitForTimeout(5000) |
await expect(page).toHaveURL(...) | Manual polling |
await locator.click() | Clicking before the element is ready |
await expect(page.getByText('Saved')).toBeVisible() | Sleeping and hoping |
Better pattern
await page.getByRole('button', { name: 'Submit' }).click();
await expect(page.getByText('Submitted')).toBeVisible();
Use explicit waits only when you truly need them, and prefer waiting for a visible application state instead of waiting for a duration. That keeps the suite faster and more deterministic.
Navigation and page handling
Navigation and page handling are at the core of most Playwright tests. Whether you are validating a simple redirect, handling multi-step workflows, or working with tabs and popups, Playwright provides built-in methods to control navigation reliably without introducing flakiness.
Common page actions
await page.goto('https://example.com');
await page.reload();
await page.goBack();
await page.goForward();
Tabs and windows
const newPage = await context.newPage();
await newPage.goto('https://example.com');
Screenshots
await page.screenshot({ path: 'homepage.png', fullPage: true });
Understanding how to move between pages, manage browser contexts, and capture page state ensures your tests remain stable while accurately reflecting real user journeys.
Fixtures cheat sheet
Fixtures make tests cleaner and more isolated. Fixtures are a way to give each test exactly what it needs while keeping test environments isolated. This helps prevent cascading failures and makes suites easier to organize.
Why fixtures matter
- simplify browser/context lifecycle management
- isolate state between tests
- reduce duplicate setup
- group tests by meaning, not just shared code
| Fixture | Purpose |
|---|---|
page | New page for the test |
context | Isolated browser context |
browser | Launch browser instances |
| Custom fixtures | Share reusable setup logic |
Example
import { test, expect } from '@playwright/test';
test('user can open dashboard', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
Playwright’s fixture model is especially useful when you need reusable setup such as authenticated contexts, seeded data, or API state.
Mocking and network control
Playwright can intercept, modify, and mock HTTP and HTTPS traffic, including XHR and fetch requests. Playwright also provides support for mocking with HAR files, which is useful when you need deterministic tests without calling live services.
Typical uses
- simulate a slow backend
- return controlled API responses
- isolate frontend behavior from unstable integrations
- replay network traffic from HAR
| Task | Example |
|---|---|
| Mock API response | page.route() |
| Block request | route.abort() |
| Modify response | route.fulfill() |
| Replay traffic | HAR-based mocking |
Example: mock an API response
await page.route('**/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Asha' }]),
});
});
Mocking is especially valuable in regression suites where you want consistent results independent of backend volatility.
Authentication cheat sheet
Playwright supports authenticated state reuse through browser contexts, which helps avoid logging in for every single test. Browser contexts are isolated, and existing authenticated state can be loaded to speed up tests and reduce repeated setup.
| Pattern | Use when |
|---|---|
| Login once and reuse state | You want faster suites |
| Save storage state | You need persistent auth |
| Separate auth setup | You want cleaner structure |
Example
test.use({ storageState: 'auth.json' });
This is useful for admin dashboards, role-based applications, and any suite where authentication is expensive or rate-limited.
Debugging cheat sheet
Debugging is a major Playwright strength. The docs recommend UI mode, Inspector debugging, and Trace Viewer to understand failures. Trace Viewer lets you step through a recorded test and inspect actions, snapshots, and network activity after the run.
| Tool | Best for |
|---|---|
--debug | Step-by-step debugging |
| UI mode | Interactive test execution |
| Trace Viewer | Reviewing what happened after a failure |
| Screenshots | Visual checkpoints |
| Video | Capturing the full run |
Debug commands
npx playwright test --debug
npx playwright test --ui
npx playwright show-report
Playwright Trace Viewer acts as a GUI that helps you explore recorded traces after the script finishes, which is especially useful for CI failures.
Code generation cheat sheet
Playwright’s test generator, or codegen, can record interactions and generate test code while you browse. It prioritizes role, text, and test id locators, and improves locators when multiple elements match so the resulting test is more resilient.
| Command | Purpose |
|---|---|
npx playwright codegen https://example.com | Record interactions and generate code |
npx playwright codegen --target=typescript https://example.com | Generate TypeScript output |
npx playwright codegen --device="iPhone 13" https://example.com | Record against a device profile |
Why it is useful
- bootstrap a new test quickly
- discover good locator patterns
- reduce manual setup time
- get a starting point for exploratory test creation
Code generation is not the final answer for every test, but it is a strong accelerator when you need to build coverage fast.
Best practices
| Do | Don’t |
|---|---|
| Use role/label/text locators | Start with brittle CSS chains |
| Prefer assertions over sleeps | Rely on waitForTimeout() |
| Keep tests isolated | Share mutable state between tests |
| Mock unstable dependencies | Depend on flaky live services |
| Use traces for failures | Guess at the root cause |
| Reuse auth state | Log in manually in every test |
Common mistakes to avoid
1. Fragile selectors
Bad:
page.locator('div > div > div:nth-child(2) > button')
Better:
page.getByRole('button', { name: 'Save' })
2. Fixed delays
Bad:
await page.waitForTimeout(3000);
Better:
await expect(page.getByText('Saved')).toBeVisible();
3. Overcomplicated tests
Keep one test focused on one behavior. Long tests that do too much are harder to debug and maintain.
4. Testing implementation instead of behavior
Test what the user sees and does. Avoid coupling tests to internal structure unless there is no better option.
A good Playwright test suite is not defined by how many tests you write, but by how reliably those tests reflect real user behavior. The patterns in this cheat sheet are what separate stable, scalable test suites from brittle ones. As your application grows, these fundamentals help reduce flakiness, speed up execution, and make failures easier to debug and trust.
Use this Playwright cheat sheet as a working reference, not just a one-time read. Whether you are writing new tests, debugging failures, or improving an existing suite, the goal is consistency and clarity. Over time, applying these practices will help your team move faster with confidence, ship fewer regressions, and build a testing workflow that scales alongside your product.
FAQ’s
Q: What is Playwright used for?
A: Playwright is used for browser automation, end-to-end testing, UI testing, and cross-browser validation. It is particularly effective for modern web applications that require reliable, repeatable interaction testing across multiple browsers.
Q: Is Playwright better than Selenium?
A: For many modern test suites, Playwright is easier to use due to its built-in waiting mechanisms, powerful debugging tools, and more streamlined developer experience. However, the choice depends on existing infrastructure and team familiarity.
Q: What is the best locator strategy in Playwright?
A: The recommended approach is to use user-facing locators such as getByRole and getByLabel. These are more resilient and maintainable compared to CSS selectors or deeply nested DOM paths.
Q: How do I run tests in headed mode?
A: You can run Playwright tests in headed mode using the following command:
npx playwright test --headed
Q: How do I debug a failing Playwright test?
A: Use Playwright’s debugging tools such as debug mode, UI mode, screenshots, and trace reports. These allow you to step through test execution and identify failures precisely.
Q: Can Playwright mock APIs?
A: Yes. Playwright allows you to intercept network requests, modify responses, abort calls, and even replay traffic using HAR files, making it effective for isolating frontend behavior.
Q: Does Playwright support authenticated testing?
A: Yes. You can persist authentication state using storage snapshots, allowing tests to bypass repeated login flows and improve execution speed.
Q: How do I keep Playwright tests stable?
A: Use resilient locators, rely on built-in assertions that auto-wait, isolate test state with fixtures, and mock unstable external dependencies to reduce flakiness.
Q: Is Playwright good for regression testing?
A: Yes. Playwright is well suited for regression testing due to its stable automation engine, support for parallel execution, strong debugging capabilities, and reusable test architecture.
Q: Should I use waitForTimeout() in Playwright?
A: Only in rare cases. It is better to wait for actual UI states or use assertions that automatically retry, as fixed delays can lead to flaky and slow tests.
Q: How do I generate tests faster in Playwright?
A: Use Playwright’s codegen feature to record interactions and generate a baseline test. Afterward, refine locators and assertions to make the test more stable and maintainable.






