Playwright User Guide
Playwright is a modern browser automation framework developed by Microsoft. It provides a consistent API for testing Chromium, Firefox, and WebKit, and it is widely used for end-to-end testing, UI automation, screenshots, and scripted browser workflows. Its strongest features include user-oriented locators, automatic waiting, web-first assertions, and detailed debugging artifacts.
For a Node.js project, initialize a package and install Playwright:
npm init -y
npm install -D @playwright/test
npx playwright install
The final command downloads the browser binaries required by the test runner. Playwright also offers official bindings for Python, Java, and .NET. A Python project can be set up with:
pip install pytest-playwright
playwright install
Most teams begin with a small project, confirm that the browsers launch correctly, and only then introduce configuration for multiple environments and browsers.
Writing Your First Test
Create a file named tests/example.spec.ts:
import { test, expect } from '@playwright/test';
test('opens the Playwright website', async ({ page }) => {
await page.goto('https://playwright.dev');
await expect(page).toHaveTitle(/Playwright/);
});
Run the suite with:
npx playwright test
The page fixture represents an isolated browser page. Each test receives a clean browser context, preventing cookies or local storage from leaking between test cases. Playwright closes the context automatically when the test finishes.
You can run a single file, filter tests by title, or open a headed browser while developing:
npx playwright test tests/example.spec.ts
npx playwright test -g "opens the Playwright website"
npx playwright test --headed
Finding and Interacting with Elements
Playwright encourages user-facing locators. These selectors describe elements by the role or label that a person would recognize, making tests easier to read and less sensitive to implementation details.
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByText('Sign in').click();
await page.getByLabel('Email address').fill('alice@example.com');
await page.getByPlaceholder('Search documentation').fill('locators');
For elements that do not expose useful semantic attributes, a test ID or CSS selector can serve as a fallback:
await page.getByTestId('search-box').fill('Playwright');
await page.locator('#newsletter-email').fill('alice@example.com');
XPath is also supported, although role-, label-, and test ID-based locators are generally preferable.
Common interaction methods include:
click()clicks an element.fill()replaces the current value of an input.check()anduncheck()control checkboxes.selectOption()selects one or more dropdown options.hover()moves the mouse over an element.press()sends a keyboard key or shortcut.setInputFiles()uploads local files.
await page.getByLabel('Subscribe').check();
await page.getByLabel('Country').selectOption('United States');
await page.getByRole('textbox').press('Control+A');
await page.getByLabel('Avatar').setInputFiles('profile.png');
Automatic Waiting and Assertions
Flaky browser tests often fail because an element has not appeared yet or is not yet ready for interaction. Playwright addresses this with auto-waiting. Before clicking or filling an element, it waits for the element to be visible, enabled, and stable.
There is rarely a need to add arbitrary delays:
// Avoid this
await page.waitForTimeout(3000);
// Playwright waits automatically
await page.getByRole('button', { name: 'Continue' }).click();
Assertions use the same retrying behavior. Instead of checking a value once, Playwright repeatedly evaluates the assertion until it succeeds or the test reaches its timeout.
await expect(page.getByText('Order confirmed')).toBeVisible();
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.getByTestId('item-count')).toHaveText('10');
await expect(page.getByLabel('Email address')).toHaveValue('alice@example.com');
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled();
Frequently used assertions include toBeVisible(), toBeHidden(), toBeEnabled(), toBeDisabled(), toHaveText(), toContainText(), toHaveValue(), toHaveURL(), and toHaveTitle().
Debugging Failed Tests
Playwright can collect several artifacts to make failures easier to investigate.
Screenshots and Video
A screenshot can be captured at any point:
await page.screenshot({
path: 'reports/dashboard.png',
fullPage: true,
});
Screenshots and video can also be enabled in playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},
});
Trace Viewer
A trace records browser actions, network activity, console messages, DOM snapshots, and timing information. It provides a step-by-step view of what happened before a failure.
npx playwright test --trace on
npx playwright show-trace trace.zip
Trace Viewer is especially useful when a test works locally but fails in CI or against a particular browser.
Mocking Network Requests
Playwright can intercept HTTP requests and return controlled responses. This allows teams to test UI states without depending on a live backend.
await page.route('**/api/users', route =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
],
}),
}),
);
Network mocking can simulate slow responses, server errors, redirects, and empty data sets. It is particularly valuable for exercising loading indicators, validation messages, and error-handling interfaces.
Running Tests Across Browsers
A configuration file can define projects for Chromium, Firefox, and WebKit:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
use: {
baseURL: 'https://example.com',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
The same test suite can now run against all three browser engines:
npx playwright test
Parallel execution reduces overall feedback time, while CI mode produces a concise terminal report and stores useful failure artifacts.
Generating Code with Codegen
The Codegen tool records browser interactions and converts them into test code:
npx playwright codegen https://example.com
After clicking buttons and entering data in the generated browser, the corresponding Playwright code appears in the editor. This is a quick way to explore the API, capture stable locators, and bootstrap a new test. Generated code should still be reviewed and reorganized before being added to a production suite.
Building a Maintainable Test Suite
A reliable Playwright project usually follows a few practical guidelines:
- Prefer user-facing locators such as roles, labels, and test IDs.
- Avoid unnecessary XPath and deeply nested CSS selectors.
- Use assertions instead of conditionals whenever the expected outcome can be expressed directly.
- Keep tests independent by avoiding shared mutable state.
- Create small helpers for repeated setup, but keep them transparent and easy to understand.
- Run tests locally across affected browsers before merging changes.
- Enable traces and failure artifacts in CI to speed up diagnosis.
- Use network mocks for unstable external dependencies, while retaining a smaller set of realistic integration tests.
Start with a single page and a handful of critical user journeys. As the suite grows, introduce a configuration file, reusable fixtures, browser projects, and CI execution. This incremental approach makes Playwright easier to adopt while keeping the resulting tests fast, readable, and dependable.