Quick Reference: Master Playwright end-to-end testing with this comprehensive cheatsheet. Includes installation, selectors, actions, assertions, and advanced patterns!
Getting Started
Playwright is a powerful end-to-end testing framework that enables reliable testing across Chromium, Firefox, and WebKit with a single API.
Installation
bash1# Install Playwright with npm 2npm init playwright@latest 3 4# Or add to existing project 5npm install -D @playwright/test 6 7# Install browsers 8npx playwright install 9 10# Install specific browser 11npx playwright install chromium
Quick Start
typescript1// tests/example.spec.ts 2import { test, expect } from '@playwright/test'; 3 4test('basic test', async ({ page }) => { 5 await page.goto('https://playwright.dev/'); 6 await expect(page).toHaveTitle(/Playwright/); 7});
Prerequisites
- Node.js 18+ (required)
- Modern browser (Chrome, Firefox, or Safari)
- Basic TypeScript/JavaScript knowledge
Essential Commands
Running Tests
| Command | Description | Example |
|---|---|---|
npx playwright test | Run all tests | npx playwright test |
npx playwright test --headed | Run with browser UI | npx playwright test --headed |
npx playwright test --debug | Debug mode | npx playwright test --debug |
npx playwright test --ui | Run in UI mode | npx playwright test --ui |
npx playwright test file.spec.ts | Run specific file | npx playwright test login.spec.ts |
npx playwright test --grep @smoke | Run tests with tag | npx playwright test --grep @smoke |
Code Generation
bash1# Record new test 2npx playwright codegen https://example.com 3 4# Record with specific browser 5npx playwright codegen --browser=firefox https://example.com 6 7# Record with device emulation 8npx playwright codegen --device="iPhone 13" https://example.com
Selectors
Basic Selectors
| Selector Type | Syntax | Example |
|---|---|---|
| Text | text= | page.locator('text=Sign in') |
| CSS | CSS selector | page.locator('.submit-button') |
| ID | #id | page.locator('#username') |
| Class | .class | page.locator('.form-input') |
| Attribute | [attr=value] | page.locator('[data-testid="submit"]') |
| XPath | xpath= | page.locator('xpath=//button') |
Recommended Selectors
typescript1// 1. data-testid (Best) 2await page.locator('[data-testid="login-button"]').click(); 3 4// 2. Role-based (Accessible) 5await page.getByRole('button', { name: 'Sign in' }).click(); 6 7// 3. Label text 8await page.getByLabel('Email address').fill('user@example.com'); 9 10// 4. Placeholder 11await page.getByPlaceholder('Enter your email').fill('test@test.com'); 12 13// 5. Text content 14await page.getByText('Welcome back').click();
Advanced Selectors
typescript1// Has text 2await page.locator('button:has-text("Submit")').click(); 3 4// Nth element 5await page.locator('li >> nth=2').click(); 6 7// Within parent 8await page.locator('.parent >> .child').click(); 9 10// Filter by text 11await page.locator('button').filter({ hasText: 'Submit' }).click(); 12 13// First/Last 14await page.locator('li').first().click(); 15await page.locator('li').last().click();
Actions
Navigation
typescript1// Navigate to URL 2await page.goto('https://example.com'); 3 4// Navigate with options 5await page.goto('https://example.com', { 6 waitUntil: 'networkidle', 7 timeout: 30000 8}); 9 10// Go back/forward 11await page.goBack(); 12await page.goForward(); 13 14// Reload 15await page.reload();
Clicks and Interactions
typescript1// Basic click 2await page.locator('button').click(); 3 4// Double click 5await page.locator('button').dblclick(); 6 7// Right click 8await page.locator('button').click({ button: 'right' }); 9 10// Click with modifiers 11await page.locator('a').click({ modifiers: ['Control'] }); 12 13// Hover 14await page.locator('.menu-item').hover(); 15 16// Focus 17await page.locator('input').focus();
Form Input
typescript1// Type text 2await page.locator('#username').fill('john@example.com'); 3 4// Type slowly (for autocomplete) 5await page.locator('input').pressSequentially('playwright', { delay: 100 }); 6 7// Clear input 8await page.locator('input').clear(); 9 10// Press key 11await page.locator('input').press('Enter'); 12 13// Keyboard shortcuts 14await page.keyboard.press('Control+A'); 15await page.keyboard.press('Meta+C'); // Cmd on Mac 16 17// Check/uncheck 18await page.locator('#agree').check(); 19await page.locator('#agree').uncheck(); 20 21// Select option 22await page.locator('select').selectOption('value'); 23await page.locator('select').selectOption({ label: 'Blue' });
File Upload
typescript1// Upload single file 2await page.locator('input[type="file"]').setInputFiles('path/to/file.pdf'); 3 4// Upload multiple files 5await page.locator('input[type="file"]').setInputFiles([ 6 'file1.pdf', 7 'file2.pdf' 8]); 9 10// Remove files 11await page.locator('input[type="file"]').setInputFiles([]);
Assertions
Page Assertions
typescript1// Title 2await expect(page).toHaveTitle('My Page'); 3await expect(page).toHaveTitle(/Page/); 4 5// URL 6await expect(page).toHaveURL('https://example.com/dashboard'); 7await expect(page).toHaveURL(/dashboard/); 8 9// Screenshot comparison 10await expect(page).toHaveScreenshot();
Element Assertions
typescript1// Visibility 2await expect(page.locator('.message')).toBeVisible(); 3await expect(page.locator('.loader')).toBeHidden(); 4 5// Text content 6await expect(page.locator('h1')).toHaveText('Welcome'); 7await expect(page.locator('h1')).toContainText('Welcome'); 8 9// Count 10await expect(page.locator('li')).toHaveCount(3); 11 12// Attribute 13await expect(page.locator('a')).toHaveAttribute('href', '/home'); 14 15// Class 16await expect(page.locator('button')).toHaveClass(/active/); 17 18// Value (for inputs) 19await expect(page.locator('input')).toHaveValue('test@example.com'); 20 21// Enabled/Disabled 22await expect(page.locator('button')).toBeEnabled(); 23await expect(page.locator('button')).toBeDisabled(); 24 25// Checked 26await expect(page.locator('#checkbox')).toBeChecked(); 27await expect(page.locator('#checkbox')).not.toBeChecked();
Soft Assertions
typescript1// Continue test even if assertion fails 2await expect.soft(page.locator('h1')).toHaveText('Expected Title'); 3await expect.soft(page.locator('p')).toBeVisible(); 4 5// Test continues and reports all failures at the end
Waiting Strategies
Auto-Waiting
typescript1// Playwright auto-waits for elements to be: 2// - Attached to DOM 3// - Visible 4// - Stable (not animating) 5// - Enabled 6// - Not covered by other elements 7 8await page.locator('button').click(); // Auto-waits before clicking
Explicit Waits
typescript1// Wait for selector 2await page.waitForSelector('.content'); 3 4// Wait for URL 5await page.waitForURL('**/dashboard'); 6 7// Wait for load state 8await page.waitForLoadState('networkidle'); 9await page.waitForLoadState('domcontentloaded'); 10 11// Wait for timeout 12await page.waitForTimeout(1000); // Avoid if possible! 13 14// Wait for function 15await page.waitForFunction(() => { 16 return document.querySelector('.loaded') !== null; 17}); 18 19// Wait for response 20await page.waitForResponse('**/api/users'); 21await page.waitForResponse(resp => 22 resp.url().includes('/api/') && resp.status() === 200 23);
Screenshots and Videos
Screenshots
typescript1// Full page screenshot 2await page.screenshot({ path: 'screenshot.png' }); 3 4// Full page (including scrollable area) 5await page.screenshot({ path: 'screenshot.png', fullPage: true }); 6 7// Element screenshot 8await page.locator('.card').screenshot({ path: 'card.png' }); 9 10// Screenshot as buffer 11const buffer = await page.screenshot();
Videos
typescript1// Configure in playwright.config.ts 2export default { 3 use: { 4 video: 'on', // 'on', 'off', 'retain-on-failure', 'on-first-retry' 5 videoSize: { width: 1280, height: 720 } 6 } 7}; 8 9// Access video in test 10test('with video', async ({ page }) => { 11 // Test code... 12 const path = await page.video()?.path(); 13 console.log('Video saved at:', path); 14});
Configuration
playwright.config.ts
typescript1import { defineConfig, devices } from '@playwright/test'; 2 3export default defineConfig({ 4 // Test directory 5 testDir: './tests', 6 7 // Global timeout 8 timeout: 30000, 9 10 // Expect timeout 11 expect: { 12 timeout: 5000 13 }, 14 15 // Retries 16 retries: process.env.CI ? 2 : 0, 17 18 // Workers (parallel execution) 19 workers: process.env.CI ? 1 : undefined, 20 21 // Reporter 22 reporter: [ 23 ['html'], 24 ['json', { outputFile: 'test-results.json' }], 25 ['junit', { outputFile: 'junit-results.xml' }] 26 ], 27 28 // Shared settings 29 use: { 30 baseURL: 'http://localhost:3000', 31 trace: 'on-first-retry', 32 screenshot: 'only-on-failure', 33 video: 'retain-on-failure', 34 35 // Browser context options 36 viewport: { width: 1280, height: 720 }, 37 ignoreHTTPSErrors: true, 38 39 // Locale & timezone 40 locale: 'en-US', 41 timezoneId: 'America/New_York', 42 }, 43 44 // Projects (browsers) 45 projects: [ 46 { 47 name: 'chromium', 48 use: { ...devices['Desktop Chrome'] }, 49 }, 50 { 51 name: 'firefox', 52 use: { ...devices['Desktop Firefox'] }, 53 }, 54 { 55 name: 'webkit', 56 use: { ...devices['Desktop Safari'] }, 57 }, 58 { 59 name: 'Mobile Chrome', 60 use: { ...devices['Pixel 5'] }, 61 }, 62 { 63 name: 'Mobile Safari', 64 use: { ...devices['iPhone 13'] }, 65 }, 66 ], 67 68 // Web server 69 webServer: { 70 command: 'npm run start', 71 url: 'http://localhost:3000', 72 reuseExistingServer: !process.env.CI, 73 timeout: 120000, 74 }, 75});
Fixtures and Hooks
Test Hooks
typescript1import { test } from '@playwright/test'; 2 3// Before each test 4test.beforeEach(async ({ page }) => { 5 await page.goto('https://example.com'); 6 await page.locator('#login').click(); 7}); 8 9// After each test 10test.afterEach(async ({ page }) => { 11 await page.close(); 12}); 13 14// Before all tests in file 15test.beforeAll(async () => { 16 // Setup database, etc. 17}); 18 19// After all tests in file 20test.afterAll(async () => { 21 // Cleanup 22});
Custom Fixtures
typescript1// fixtures.ts 2import { test as base } from '@playwright/test'; 3 4type MyFixtures = { 5 authenticatedPage: Page; 6}; 7 8export const test = base.extend<MyFixtures>({ 9 authenticatedPage: async ({ page }, use) => { 10 // Setup 11 await page.goto('/login'); 12 await page.fill('#username', 'user'); 13 await page.fill('#password', 'pass'); 14 await page.click('#submit'); 15 await page.waitForURL('**/dashboard'); 16 17 // Use fixture 18 await use(page); 19 20 // Teardown 21 await page.close(); 22 }, 23}); 24 25// Use in test 26test('use authenticated page', async ({ authenticatedPage }) => { 27 await authenticatedPage.goto('/profile'); 28 // Already logged in! 29});
Common Patterns
Page Object Model
typescript1// 📁 pages/LoginPage.ts 2import { Page, Locator } from '@playwright/test'; 3 4export class LoginPage { 5 readonly page: Page; 6 readonly usernameInput: Locator; 7 readonly passwordInput: Locator; 8 readonly submitButton: Locator; 9 10 constructor(page: Page) { 11 this.page = page; 12 this.usernameInput = page.locator('#username'); 13 this.passwordInput = page.locator('#password'); 14 this.submitButton = page.locator('button[type="submit"]'); 15 } 16 17 async goto() { 18 await this.page.goto('/login'); 19 } 20 21 async login(username: string, password: string) { 22 await this.usernameInput.fill(username); 23 await this.passwordInput.fill(password); 24 await this.submitButton.click(); 25 await this.page.waitForURL('**/dashboard'); 26 } 27} 28 29// 📁 tests/login.spec.ts 30import { test, expect } from '@playwright/test'; 31import { LoginPage } from './pages/LoginPage'; 32 33test('successful login', async ({ page }) => { 34 const loginPage = new LoginPage(page); 35 await loginPage.goto(); 36 await loginPage.login('user@example.com', 'password123'); 37 38 await expect(page).toHaveURL(/dashboard/); 39});
API Testing
typescript1import { test, expect } from '@playwright/test'; 2 3test('API testing', async ({ request }) => { 4 // GET request 5 const response = await request.get('https://api.example.com/users'); 6 expect(response.ok()).toBeTruthy(); 7 const users = await response.json(); 8 expect(users).toHaveLength(5); 9 10 // POST request 11 const newUser = await request.post('https://api.example.com/users', { 12 data: { 13 name: 'John Doe', 14 email: 'john@example.com' 15 } 16 }); 17 expect(newUser.ok()).toBeTruthy(); 18 19 // With headers 20 const authResponse = await request.get('https://api.example.com/profile', { 21 headers: { 22 'Authorization': 'Bearer token123' 23 } 24 }); 25});
Handling Dialogs
typescript1// Alert, confirm, prompt 2test('handle dialogs', async ({ page }) => { 3 // Accept dialog 4 page.on('dialog', dialog => dialog.accept()); 5 await page.locator('button').click(); 6 7 // Dismiss dialog 8 page.on('dialog', dialog => dialog.dismiss()); 9 10 // Get dialog message 11 page.on('dialog', async dialog => { 12 console.log(dialog.message()); 13 await dialog.accept(); 14 }); 15 16 // Prompt with input 17 page.on('dialog', dialog => dialog.accept('My input')); 18});
Multiple Tabs/Windows
typescript1test('handle new tab', async ({ context, page }) => { 2 // Wait for new page 3 const [newPage] = await Promise.all([ 4 context.waitForEvent('page'), 5 page.locator('a[target="_blank"]').click() 6 ]); 7 8 await newPage.waitForLoadState(); 9 console.log(await newPage.title()); 10 await newPage.close(); 11});
Network Interception
typescript1test('mock API response', async ({ page }) => { 2 // Mock API endpoint 3 await page.route('**/api/users', route => { 4 route.fulfill({ 5 status: 200, 6 contentType: 'application/json', 7 body: JSON.stringify([ 8 { id: 1, name: 'John' }, 9 { id: 2, name: 'Jane' } 10 ]) 11 }); 12 }); 13 14 await page.goto('/users'); 15}); 16 17test('block images', async ({ page }) => { 18 // Block image requests 19 await page.route('**/*.{png,jpg,jpeg}', route => route.abort()); 20 await page.goto('/'); 21});
Best Practices
Pro Tip: Use data-testid attributes for reliable selectors that won't break when UI changes
Selector Best Practices
-
Prefer user-facing attributes
typescript1// ✅ Good 2await page.getByRole('button', { name: 'Submit' }); 3await page.getByLabel('Email'); 4await page.getByText('Welcome'); 5 6// ❌ Avoid 7await page.locator('.btn-primary-submit-form'); 8await page.locator('div > div > button:nth-child(2)'); -
Use data-testid for dynamic content
typescript1// ✅ Good 2await page.locator('[data-testid="user-profile"]'); 3 4// ❌ Avoid 5await page.locator('#user-123456'); -
Keep selectors simple
typescript1// ✅ Good 2await page.locator('[data-testid="submit"]'); 3 4// ❌ Avoid 5await page.locator('div.container > form > div.row > button.submit');
Test Organization
typescript1// Group related tests 2test.describe('Login functionality', () => { 3 test('successful login', async ({ page }) => { 4 // Test code 5 }); 6 7 test('failed login', async ({ page }) => { 8 // Test code 9 }); 10 11 test('password reset', async ({ page }) => { 12 // Test code 13 }); 14}); 15 16// Add tags 17test('smoke test @smoke', async ({ page }) => { 18 // Critical path test 19}); 20 21test('integration test @integration', async ({ page }) => { 22 // Full flow test 23});
Performance Tips
-
Reuse authentication state
typescript1// Save auth state once 2test('save auth state', async ({ page }) => { 3 await page.goto('/login'); 4 await page.fill('#username', 'user'); 5 await page.fill('#password', 'pass'); 6 await page.click('#submit'); 7 8 await page.context().storageState({ path: 'auth.json' }); 9}); 10 11// Reuse in other tests 12test.use({ storageState: 'auth.json' }); -
Run tests in parallel
typescript1// playwright.config.ts 2export default { 3 workers: 4, // Run 4 tests in parallel 4}; -
Use test.describe.parallel
typescripttest.describe.parallel('parallel tests', () => { // All tests in this block run in parallel });
Debugging
Debug Mode
bash1# Run in debug mode 2npx playwright test --debug 3 4# Debug specific test 5npx playwright test login.spec.ts --debug 6 7# Debug from specific line 8PWDEBUG=console npx playwright test
Playwright Inspector
typescript1// Add breakpoint in code 2await page.pause(); 3 4// Inspect element 5await page.locator('button').click();
Trace Viewer
bash1# Enable trace in config 2use: { 3 trace: 'on' 4} 5 6# View trace 7npx playwright show-trace trace.zip
VS Code Extension
- Install "Playwright Test for VSCode"
- Run/Debug tests from editor
- Set breakpoints
- View test results inline
Troubleshooting
Common Issues
Issue: Element not visible
typescript// Solution: Wait for element or check visibility await page.locator('button').waitFor({ state: 'visible' }); await expect(page.locator('button')).toBeVisible();
Issue: Timeout waiting for element
typescript1// Solution: Increase timeout or fix selector 2await page.locator('button').click({ timeout: 10000 }); 3 4// Or update globally 5test.setTimeout(60000);
Issue: Flaky tests
typescript1// Solution: Use proper waits instead of timeouts 2// ❌ Bad 3await page.waitForTimeout(1000); 4 5// ✅ Good 6await page.waitForLoadState('networkidle'); 7await expect(page.locator('.content')).toBeVisible();
Issue: Tests fail in CI
typescript1// Solution: Disable parallelization or increase retries 2export default { 3 workers: process.env.CI ? 1 : 4, 4 retries: process.env.CI ? 2 : 0, 5};
Quick Command Reference
bash1# Running Tests 2npx playwright test # Run all tests 3npx playwright test --headed # Show browser 4npx playwright test --debug # Debug mode 5npx playwright test --ui # UI mode 6npx playwright test file.spec.ts # Run specific file 7npx playwright test --grep @smoke # Run tagged tests 8npx playwright test --project=webkit # Run specific browser 9 10# Code Generation 11npx playwright codegen [url] # Record tests 12npx playwright codegen --target=typescript # Generate TypeScript 13 14# Reports 15npx playwright show-report # View HTML report 16npx playwright show-trace trace.zip # View trace 17 18# Installation 19npm init playwright@latest # Create new project 20npx playwright install # Install browsers 21npx playwright install chromium # Install specific browser 22 23# Configuration 24npx playwright test --config=custom.config.ts # Custom config
Additional Resources
- 📚 Official Documentation
- 🎓 Playwright University
- 💬 Discord Community
- 🐙 GitHub Repository
- 📖 API Reference
- 🎥 Video Tutorials
Bookmark This Page! Keep this Playwright cheatsheet handy for quick reference while writing your tests.
Last updated: February 2024 | Playwright v1.41