Playwright
Complete reference for Playwright — the cross-browser end-to-end testing framework by Microsoft for reliable web automation
Installation & Setup
Installing Playwright and creating your first project
Initialize a new Playwright test project with all dependencies
# Create new project
npm init playwright@latest
# Install in existing project
npm install -D @playwright/test
# Install browsers
npx playwright installSet up playwright.config.ts with projects, reporters, and options
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});Writing Tests
Test structure, hooks, and organizing test suites
Write tests with test blocks, navigation, and assertions
import { test, expect } from '@playwright/test';
test('has correct title', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/My App/);
});
test('navigates to about page', async ({ page }) => {
await page.goto('/');
await page.getByRole('link', { name: 'About' }).click();
await expect(page).toHaveURL('/about');
});Organize tests with describe blocks and lifecycle hooks
import { test, expect } from '@playwright/test';
test.describe('Dashboard', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/dashboard');
});
test('shows welcome message', async ({ page }) => {
await expect(page.getByText('Welcome')).toBeVisible();
});
test('displays stats', async ({ page }) => {
await expect(page.getByTestId('stats')).toBeVisible();
});
});Locators
Finding elements on the page with built-in locator strategies
Use role-based, text, and semantic locators to find elements
// By role (preferred — accessible and resilient)
page.getByRole('button', { name: 'Submit' })
page.getByRole('heading', { name: 'Welcome' })
page.getByRole('link', { name: 'Home' })
// By label, placeholder, text
page.getByLabel('Email')
page.getByPlaceholder('Search...')
page.getByText('Hello World')
// By test ID
page.getByTestId('submit-btn')Actions & Interactions
Clicking, typing, selecting, and interacting with page elements
Click, type, select, check, and interact with elements
// Click
await page.getByRole('button', { name: 'Save' }).click();
// Type into input
await page.getByLabel('Name').fill('John Doe');
// Select dropdown option
await page.getByLabel('Country').selectOption('us');
// Check/uncheck
await page.getByLabel('Remember me').check();Assertions
Web-first assertions that auto-wait and auto-retry
Assert on page state, element visibility, text content, and attributes
// Page assertions
await expect(page).toHaveTitle(/Dashboard/);
await expect(page).toHaveURL('/dashboard');
// Visibility
await expect(page.getByText('Welcome')).toBeVisible();
await expect(page.getByText('Loading')).toBeHidden();
// Text content
await expect(page.locator('h1')).toHaveText('Hello');
await expect(page.locator('p')).toContainText('world');Emulation & Devices
Emulate mobile devices, viewports, geolocation, locale, and color schemes
Test on mobile devices, custom viewports, and different screen configurations
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'Desktop', use: { viewport: { width: 1280, height: 720 } } },
{ name: 'Mobile', use: { ...devices['iPhone 13'] } },
{ name: 'Tablet', use: { ...devices['iPad Pro 11'] } },
],
});Emulate user locale, timezone, geolocation, permissions, and color scheme
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
colorScheme: 'dark',
geolocation: { latitude: 52.52, longitude: 13.405 },
permissions: ['geolocation'],
},
});Visual & Snapshot Testing
Compare screenshots and snapshots against golden baselines
Capture and compare page or element screenshots against baselines
import { test, expect } from '@playwright/test';
test('homepage visual check', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot();
});
test('component visual check', async ({ page }) => {
await page.goto('/dashboard');
await expect(page.getByTestId('chart')).toHaveScreenshot();
});Parallelism & Sharding
Run tests in parallel across workers and shard across CI machines
Configure parallel workers and split test suites across CI machines
import { defineConfig } from '@playwright/test';
export default defineConfig({
fullyParallel: true, // Parallelize within files too
workers: process.env.CI ? 1 : undefined, // Default: half CPU cores
});
// Shard from CLI
// npx playwright test --shard=1/4
// npx playwright test --shard=2/4Retries & Timeouts
Configure test retries for flaky tests and set timeout boundaries
Automatically retry failed tests to handle flaky behavior
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0, // Retry twice on CI
});Set timeouts for tests, actions, navigation, and assertions
import { defineConfig } from '@playwright/test';
export default defineConfig({
timeout: 30_000, // Per-test timeout (30s)
expect: { timeout: 5_000 }, // Assertion timeout (5s)
use: {
actionTimeout: 10_000, // Click/fill/etc timeout
navigationTimeout: 30_000, // page.goto timeout
},
});Navigation & Waiting
Page navigation, waiting strategies, and auto-wait behavior
Navigate between pages and wait for load states
// Navigate to URL
await page.goto('/products');
// Wait for navigation after click
await page.getByRole('link', { name: 'Products' }).click();
await page.waitForURL('/products');
// Go back/forward
await page.goBack();
await page.goForward();Network Mocking & Interception
Intercept, mock, and modify network requests and responses
Intercept API calls and return custom data without hitting the server
// Mock an API endpoint
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'John' }]),
});
});
await page.goto('/users');
await expect(page.getByText('John')).toBeVisible();API Testing
Test REST APIs directly without browser UI using the request fixture
Send HTTP requests directly to test your API endpoints
import { test, expect } from '@playwright/test';
test('can create and fetch user', async ({ request }) => {
const res = await request.post('/api/users', {
data: { name: 'John', email: 'john@test.com' },
});
expect(res.ok()).toBeTruthy();
const users = await request.get('/api/users');
expect(await users.json()).toContainEqual(
expect.objectContaining({ name: 'John' })
);
});Fixtures & Page Object Model
Custom fixtures for reusable setup and the Page Object pattern
Create reusable test fixtures for shared setup logic
import { test as base, expect } from '@playwright/test';
// Define custom fixture
const test = base.extend<{ todoPage: TodoPage }>({
todoPage: async ({ page }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await use(todoPage);
},
});
test('can add todo', async ({ todoPage }) => {
await todoPage.addTodo('Buy milk');
await todoPage.expectTodoVisible('Buy milk');
});Authentication & Storage State
Reuse authentication state across tests for faster execution
Log in once and share the session across multiple tests
// auth.setup.ts — runs once before all tests
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@test.com');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Log in' }).click();
await page.waitForURL('/dashboard');
await page.context().storageState({ path: authFile });
});Debugging & Trace Viewer
Debug failing tests with UI mode, trace viewer, and screenshots
Use UI mode, trace viewer, and headed mode to debug tests
# Run in UI mode (interactive debugging)
npx playwright test --ui
# Run with browser visible
npx playwright test --headed
# Run with trace recording
npx playwright test --trace on
# View trace file
npx playwright show-trace trace.zipRunning Tests & CI
Run tests locally and in continuous integration pipelines
Common commands for running and filtering tests
# Run all tests
npx playwright test
# Run specific file
npx playwright test tests/login.spec.ts
# Run tests matching name
npx playwright test --grep "submit form"
# Run in specific project/browser
npx playwright test --project=chromiumRun Playwright tests in a GitHub Actions workflow
# .github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/