Summary
Playwright is a modern end-to-end testing framework developed by Microsoft that enables reliable cross-browser automation for web applications. It supports Chromium, Firefox, and WebKit with a single API, providing features like auto-waiting, network interception, and mobile emulation. Unlike traditional testing tools, Playwright runs tests in parallel, handles multiple browser contexts, and offers superior debugging capabilities with trace viewer and codegen tools. Its architecture allows for fast, reliable tests with built-in retry mechanisms and comprehensive reporting. This cheatsheet covers Playwright fundamentals, best practices, and advanced patterns essential for technical interviews.
1. Introduction & Setup
What is Playwright?
- Cross-browser automation library by Microsoft
- Supports Chromium, Firefox, and WebKit
- Auto-wait functionality, network interception, and mobile emulation
Installation
npm init playwright@latest
# or
npm install -D @playwright/test
Basic Test Structure
import { test, expect } from '@playwright/test';
test('basic test', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle('Example Domain');
});
2. Selectors & Locators
Locator Strategies
// Text selector
await page.locator('text=Submit').click();
// CSS selector
await page.locator('.button-class').click();
// XPath
await page.locator('xpath=//button[@id="submit"]').click();
// Role-based (Accessibility)
await page.getByRole('button', { name: 'Submit' }).click();
// Test ID (Recommended)
await page.getByTestId('submit-button').click();
// Chaining locators
await page.locator('article').filter({ hasText: 'Product' }).click();
Advanced Selectors
// nth element
await page.locator('li').nth(2).click();
// Has/Has-Text
await page.locator('article', { has: page.locator('.highlight') }).click();
// Multiple selectors
await page.locator('button:has-text("Submit"), input[type="submit"]').click();
3. Actions & Interactions
Basic Actions
// Click
await page.click('button');
await page.dblclick('button');
await page.click('button', { button: 'right' }); // right-click
// Type
await page.fill('input[name="email"]', 'test@example.com');
await page.type('input[name="email"]', 'test@example.com'); // types char by char
// Select
await page.selectOption('select#country', 'USA');
// Check/Uncheck
await page.check('input[type="checkbox"]');
await page.uncheck('input[type="checkbox"]');
// Hover
await page.hover('button');
// Focus
await page.focus('input');
File Upload & Download
// Upload
await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
// Download
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('a#download-link')
]);
await download.saveAs('/path/to/save/file.pdf');
Keyboard & Mouse
// Keyboard
await page.keyboard.press('Enter');
await page.keyboard.down('Shift');
await page.keyboard.up('Shift');
await page.keyboard.type('Hello World');
// Mouse
await page.mouse.move(100, 100);
await page.mouse.down();
await page.mouse.up();
await page.mouse.click(100, 100);
4. Waits & Assertions
Auto-waiting
Playwright automatically waits for:
- Element to be attached to DOM
- Element to be visible
- Element to be stable (not animating)
- Element to be enabled
- Element to receive events
Explicit Waits
// Wait for selector
await page.waitForSelector('.success-message');
// Wait for load state
await page.waitForLoadState('networkidle');
// Wait for URL
await page.waitForURL('**/success');
// Wait for function
await page.waitForFunction(() => document.querySelector('.counter').innerText === '5');
// Wait with timeout
await page.waitForSelector('.modal', { timeout: 5000 });
Assertions
// Page assertions
await expect(page).toHaveTitle('Home Page');
await expect(page).toHaveURL(/.*success/);
// Element assertions
await expect(page.locator('.message')).toBeVisible();
await expect(page.locator('.message')).toHaveText('Success');
await expect(page.locator('.message')).toContainText('Success');
await expect(page.locator('input')).toHaveValue('john@example.com');
await expect(page.locator('button')).toBeEnabled();
await expect(page.locator('input[type="checkbox"]')).toBeChecked();
// Count assertions
await expect(page.locator('li')).toHaveCount(5);
// Attribute assertions
await expect(page.locator('img')).toHaveAttribute('alt', 'Logo');
5. Page Object Model (POM)
Basic POM Structure
// pages/LoginPage.js
export class LoginPage {
constructor(page) {
this.page = page;
this.emailInput = page.locator('input[name="email"]');
this.passwordInput = page.locator('input[name="password"]');
this.loginButton = page.locator('button[type="submit"]');
}
async login(email, password) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
// test.spec.js
import { LoginPage } from './pages/LoginPage';
test('login test', async ({ page }) => {
const loginPage = new LoginPage(page);
await page.goto('/login');
await loginPage.login('user@example.com', 'password123');
});
6. Advanced Features
Network Interception
// Mock API response
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
body: JSON.stringify([{ id: 1, name: 'John' }])
});
});
// Modify request
await page.route('**/api/data', route => {
const headers = {
...route.request().headers(),
'Authorization': 'Bearer token123'
};
route.continue({ headers });
});
// Abort requests
await page.route('**/*.png', route => route.abort());
Multiple Pages/Contexts
// New page in same context
const newPage = await context.newPage();
// New context (isolated session)
const newContext = await browser.newContext();
const page2 = await newContext.newPage();
// Handle popups
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a[target="_blank"]')
]);
Screenshots & Videos
// Screenshot
await page.screenshot({ path: 'screenshot.png' });
await page.screenshot({ fullPage: true });
// Element screenshot
await page.locator('.header').screenshot({ path: 'header.png' });
// Video recording (in config)
use: {
video: 'retain-on-failure'
}
Mobile Emulation
// Device emulation
const iPhone = playwright.devices['iPhone 12'];
const context = await browser.newContext({
...iPhone,
locale: 'en-US',
geolocation: { longitude: 12.4924, latitude: 41.8902 },
permissions: ['geolocation']
});
7. Configuration
Basic Config (playwright.config.js)
module.exports = {
timeout: 30000,
retries: 2,
workers: 4,
use: {
baseURL: 'http://localhost:3000',
headless: true,
viewport: { width: 1280, height: 720 },
screenshot: 'only-on-failure',
trace: 'on-first-retry'
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } }
]
};
8. API Testing
test('API test', async ({ request }) => {
// GET request
const response = await request.get('/api/users');
expect(response.ok()).toBeTruthy();
const users = await response.json();
expect(users).toHaveLength(3);
// POST request
const newUser = await request.post('/api/users', {
data: {
name: 'John Doe',
email: 'john@example.com'
}
});
expect(newUser.status()).toBe(201);
});
9. Best Practices
1. Use Data Test IDs
<button data-testid="submit-button">Submit</button>
await page.getByTestId('submit-button').click();
2. Avoid Hard Waits
// ❌ Bad
await page.waitForTimeout(5000);
// ✅ Good
await page.waitForSelector('.success-message');
3. Use Locator over ElementHandle
// ❌ Bad
const button = await page.$('button');
await button.click();
// ✅ Good
await page.locator('button').click();
4. Proper Error Handling
try {
await page.click('button', { timeout: 3000 });
} catch (error) {
console.log('Button not found:', error);
}
5. Reusable Functions
async function login(page, username, password) {
await page.fill('#username', username);
await page.fill('#password', password);
await page.click('#login-button');
}
10. Key Interview Concepts
Playwright vs Other Testing Tools
| Feature | Playwright | Cypress | Selenium |
|---|---|---|---|
| Browser Support | Chrome, Firefox, Safari | Chrome-family | All browsers |
| Architecture | Out-of-process | In-browser | WebDriver protocol |
| Auto-waiting | Built-in | Built-in | Manual |
| Network Control | Full control | Limited | Limited |
| Multiple Tabs | Yes | No | Yes |
| Speed | Fast (parallel) | Fast | Slower |
| Mobile Testing | Device emulation | Viewport only | Real devices |
Handling Dynamic Content
// Strategy 1: Wait for specific text
await page.waitForSelector('text=Loading complete');
// Strategy 2: Wait for element state
await page.waitForSelector('.spinner', { state: 'hidden' });
await page.waitForSelector('.content', { state: 'visible' });
// Strategy 3: Wait for network idle
await page.waitForLoadState('networkidle');
// Strategy 4: Custom wait function
await page.waitForFunction(() =>
document.querySelector('.counter').innerText === '100'
);
Frame Handling Strategies
// Method 1: frameLocator (Recommended)
const frame = page.frameLocator('#frame-id');
await frame.locator('button').click();
// Method 2: frame() for direct access
const frameElement = await page.frame({ url: /frame\.html/ });
await frameElement.click('button');
// Method 3: Nested frames
const nestedFrame = page
.frameLocator('#outer-frame')
.frameLocator('#inner-frame');
Dialog & Popup Handling
// Alert/Confirm/Prompt handling
page.on('dialog', async dialog => {
console.log(`Dialog message: ${dialog.message()}`);
if (dialog.type() === 'confirm') {
await dialog.accept();
} else if (dialog.type() === 'prompt') {
await dialog.accept('Input value');
}
});
// Popup window handling
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('a[target="_blank"]')
]);
await popup.waitForLoadState();
Authentication Patterns
// Pattern 1: Session Storage
await context.storageState({ path: 'auth.json' });
const authenticatedContext = await browser.newContext({
storageState: 'auth.json'
});
// Pattern 2: Global Setup
// global-setup.js
module.exports = async config => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://app.com/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
await page.click('#login');
await page.context().storageState({ path: 'auth.json' });
await browser.close();
};
// Pattern 3: API Authentication
const context = await browser.newContext({
extraHTTPHeaders: {
'Authorization': `Bearer ${token}`
}
});
Debugging Techniques
| Method | Use Case | Command/Code |
|---|---|---|
| Pause | Interactive debugging | await page.pause() |
| Slow Motion | Visual debugging | { slowMo: 500 } |
| Headed Mode | See browser | --headed |
| Debug Mode | Step through | --debug |
| Trace Viewer | Post-mortem analysis | trace: 'on' |
| Videos | Visual recordings | video: 'on' |
| Console Logs | Runtime logs | page.on('console') |
11. Performance Testing
// Measure page load time
const startTime = Date.now();
await page.goto('https://example.com');
const loadTime = Date.now() - startTime;
// Performance metrics
const metrics = await page.evaluate(() => ({
domContentLoaded: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,
load: performance.timing.loadEventEnd - performance.timing.navigationStart
}));
12. Accessibility Testing
// Check accessibility
const accessibilitySnapshot = await page.accessibility.snapshot();
// ARIA attributes
await expect(page.getByRole('button', { name: 'Submit' })).toHaveAttribute('aria-disabled', 'false');
// Keyboard navigation
await page.keyboard.press('Tab');
await page.keyboard.press('Enter');
Quick Reference Commands
# Run tests
npx playwright test
# Run specific test
npx playwright test login.spec.js
# Run in headed mode
npx playwright test --headed
# Run with specific browser
npx playwright test --project=chromium
# Debug mode
npx playwright test --debug
# Generate code
npx playwright codegen https://example.com
# Show report
npx playwright show-report
Interview Preparation Checklist
- ✅ Core Concepts: Auto-waiting, locators, selectors
- ✅ Actions: Click, type, select, upload, keyboard/mouse
- ✅ Assertions: Built-in expect API, auto-retry
- ✅ Page Object Model: Structure, implementation
- ✅ Network: Mocking, interception, API testing
- ✅ Multi-page: Contexts, popups, frames
- ✅ Debugging: Trace viewer, screenshots, videos
- ✅ Configuration: Projects, devices, parallelization
- ✅ Best Practices: Test isolation, data-testid, retries
- ✅ Advanced: Authentication, performance, accessibility
Key Takeaway: Playwright excels at reliable cross-browser testing with features like auto-waiting, network control, and parallel execution. Focus on understanding locator strategies, handling dynamic content, and implementing maintainable test architectures using Page Object Model. Its superior debugging tools and built-in features make it ideal for modern web application testing.