Introduction
This comprehensive reference guide covers all essential Playwright APIs, methods, and concepts. Use this as your complete dictionary for Playwright test automation with TypeScript.
- Core APIs: Browser, BrowserContext, Page, Locator
- Locator strategies and selection methods
- Actions: Click, fill, select, hover, drag & drop
- Assertions: Web-first assertions with auto-retry
- Network: Request/response interception and API testing
- Advanced: Fixtures, trace viewer, codegen, visual testing
- Use the alphabetical index to quickly find specific keywords
- Each entry includes syntax, examples, and best practices
- Code examples use TypeScript (Playwright's recommended language)
- Cross-references help you discover related concepts
Quick Navigation Index
Core APIs
Browser
Core APIPurpose: Represents a browser instance (Chromium, Firefox, or WebKit). Manages browser lifecycle and creates isolated browser contexts.
import { chromium, firefox, webkit } from '@playwright/test';
// Launch browser
const browser = await chromium.launch({
headless: true,
slowMo: 100, // Slow down by 100ms (debugging)
timeout: 30000
});
// Launch with options
const browser = await chromium.launch({
headless: false,
args: ['--start-maximized'],
downloadsPath: './downloads',
channel: 'chrome' // Use Google Chrome instead of Chromium
});
// Create browser context
const context = await browser.newContext();
// Get browser contexts
const contexts = browser.contexts();
console.log(`Active contexts: ${contexts.length}`);
// Check if browser is connected
console.log(`Connected: ${browser.isConnected()}`);
// Browser version
console.log(`Version: ${browser.version()}`);
// Close browser
await browser.close();
// Different browsers
const chromiumBrowser = await chromium.launch();
const firefoxBrowser = await firefox.launch();
const webkitBrowser = await webkit.launch();| Method | Description |
|---|---|
launch() | Launch new browser instance |
newContext() | Create isolated browser context |
contexts() | Get all active contexts |
close() | Close browser and all contexts |
isConnected() | Check if browser is running |
version() | Get browser version |
Related: BrowserContext,Page
BrowserContext
Core APIPurpose: Isolated incognito-like session with own cookies, cache, and storage. Multiple contexts can exist in one browser - key to fast, parallel test execution.
import { chromium } from '@playwright/test';
const browser = await chromium.launch();
// Create context with options
const context = await browser.newContext({
// Viewport
viewport: { width: 1920, height: 1080 },
// User agent
userAgent: 'Custom User Agent',
// Locale and timezone
locale: 'en-US',
timezoneId: 'America/New_York',
// Geolocation
geolocation: { latitude: 40.7128, longitude: -74.0060 },
permissions: ['geolocation'],
// Authentication
httpCredentials: {
username: 'admin',
password: 'password123'
},
// Storage state (authentication)
storageState: 'auth.json',
// Ignore HTTPS errors
ignoreHTTPSErrors: true,
// Offline mode
offline: true,
// Extra headers
extraHTTPHeaders: {
'X-Custom-Header': 'value'
}
});
// Create new page in context
const page = await context.newPage();
// Get all pages in context
const pages = context.pages();
console.log(`Pages: ${pages.length}`);
// Add cookies
await context.addCookies([{
name: 'token',
value: 'abc123',
domain: 'example.com',
path: '/'
}]);
// Get cookies
const cookies = await context.cookies();
// Clear cookies
await context.clearCookies();
// Save storage state (for reuse)
await context.storageState({ path: 'auth.json' });
// Set offline
await context.setOffline(true);
// Grant permissions
await context.grantPermissions(['clipboard-read']);
// Close context
await context.close();- Isolation: Each context is completely independent
- Speed: Faster than launching new browsers
- Parallel: Run multiple contexts simultaneously
- State: Save and reuse authentication state
Page
Core APIPurpose: Represents a single tab or window in browser context. Primary interface for interacting with web pages.
import { test } from '@playwright/test';
test('page methods', async ({ page }) => {
// Navigation
await page.goto('https://example.com');
await page.goBack();
await page.goForward();
await page.reload();
// Get URL and title
console.log(page.url());
console.log(await page.title());
// Wait for navigation
await page.waitForURL('**/dashboard');
await page.waitForLoadState('networkidle');
// Locators
const button = page.locator('button');
const heading = page.getByRole('heading');
// Screenshot
await page.screenshot({ path: 'screenshot.png' });
// PDF generation
await page.pdf({ path: 'page.pdf' });
// Viewport
await page.setViewportSize({ width: 1280, height: 720 });
// Emulate media
await page.emulateMedia({ colorScheme: 'dark' });
// Evaluate JavaScript
const result = await page.evaluate(() => {
return document.title;
});
// Add script tag
await page.addScriptTag({
url: 'https://cdn.example.com/script.js'
});
// Add style tag
await page.addStyleTag({
content: 'body { background: red; }'
});
// Wait for timeout (use sparingly!)
await page.waitForTimeout(1000);
// Set extra HTTP headers
await page.setExtraHTTPHeaders({
'X-Custom-Header': 'value'
});
// Get content
const html = await page.content();
// Close page
await page.close();
});| Category | Methods |
|---|---|
| Navigation | goto(), goBack(), goForward(), reload() |
| Locators | locator(), getByRole(), getByText(), getByTestId() |
| Actions | click(), fill(), check(), selectOption() |
| Waits | waitForSelector(), waitForURL(), waitForLoadState() |
| Screenshots | screenshot(), pdf() |
| JavaScript | evaluate(), evaluateHandle() |
Locator
Core APIPurpose: Represents element query with auto-waiting andauto-retry. Locators are evaluated when action is performed, not when created.
import { test } from '@playwright/test';
test('locators', async ({ page }) => {
await page.goto('https://example.com');
// Create locator (doesn't find element yet)
const button = page.locator('button#submit');
// Actions (triggers element finding)
await button.click();
await button.fill('text');
await button.check();
// Get properties
const text = await button.textContent();
const value = await button.inputValue();
const attr = await button.getAttribute('class');
// Check state
await button.isVisible();
await button.isEnabled();
await button.isChecked();
await button.isEditable();
// Count matching elements
const count = await button.count();
// Get multiple elements
const all = await button.all();
for (const el of all) {
console.log(await el.textContent());
}
// Get first/last/nth element
await button.first().click();
await button.last().click();
await button.nth(2).click();
// Filter locators
await page.locator('button')
.filter({ hasText: 'Submit' })
.click();
await page.locator('li')
.filter({ has: page.locator('span.icon') })
.click();
// Chain locators
await page.locator('.form')
.locator('input[name="email"]')
.fill('user@example.com');
// And/Or combinations
await page.locator('button')
.and(page.locator('.primary'))
.click();
await page.locator('button')
.or(page.locator('a.button'))
.first()
.click();
});- Lazy: Element not found until action is performed
- Auto-wait: Waits for element to be actionable
- Auto-retry: Retries until success or timeout
- Strict: Fails if multiple elements match (unless using first/last/nth)
Related: getByRole(),click()
Locator Selection Methods
getByRole()
Locator StrategyPurpose: Locate elements by ARIA role. Most recommended strategy - accessibility-friendly and resilient.
import { test } from '@playwright/test';
test('role-based locators', async ({ page }) => {
await page.goto('https://example.com');
// Buttons
await page.getByRole('button').click();
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('button', { name: /submit/i }).click(); // Regex
// Links
await page.getByRole('link', { name: 'Home' }).click();
// Textboxes (inputs)
await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await page.getByRole('textbox').first().fill('text');
// Checkboxes
await page.getByRole('checkbox', { name: 'Accept terms' }).check();
// Radio buttons
await page.getByRole('radio', { name: 'Option 1' }).check();
// Combobox (select dropdown)
await page.getByRole('combobox').selectOption('value');
// Headings
await page.getByRole('heading', { name: 'Dashboard' }).isVisible();
await page.getByRole('heading', { level: 1 }).textContent();
// Lists and list items
await page.getByRole('list').count();
await page.getByRole('listitem').first().click();
// Tables
await page.getByRole('table').isVisible();
await page.getByRole('row').count();
await page.getByRole('cell').first().textContent();
// Dialogs (modals)
await page.getByRole('dialog').isVisible();
// Navigation
await page.getByRole('navigation').locator('a').first().click();
// Additional options
await page.getByRole('button', {
name: 'Submit',
exact: true, // Exact match
disabled: false, // Only enabled buttons
pressed: true, // Toggle buttons
checked: true, // Checkboxes/radios
expanded: true // Accordion/dropdown state
}).click();
});| Role | HTML Elements |
|---|---|
button | <button>, <input type="button"> |
link | <a href> |
textbox | <input type="text">, <input type="email"> |
checkbox | <input type="checkbox"> |
radio | <input type="radio"> |
combobox | <select> |
heading | <h1> - <h6> |
img | <img> |
Related: getByText(),getByLabel()
getByText()
Locator StrategyPurpose: Locate elements by visible text content. User-centric approach - finds what users see.
import { test } from '@playwright/test';
test('text-based locators', async ({ page }) => {
await page.goto('https://example.com');
// Exact text match
await page.getByText('Sign In').click();
await page.getByText('Welcome Back').isVisible();
// Exact match with quotes
await page.getByText('Sign In', { exact: true }).click();
// Partial match (contains)
await page.getByText('Sign').click(); // Matches "Sign In", "Sign Up"
// Regex
await page.getByText(/sign (in|up)/i).click();
// Case insensitive
await page.getByText('submit', { exact: false }).click();
});Related: getByRole(),getByLabel()
getByLabel()
Locator StrategyPurpose: Locate form inputs by their associated label text.
import { test } from '@playwright/test';
test('label-based locators', async ({ page }) => {
await page.goto('https://example.com/form');
// Find input by label text
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByLabel('Remember me').check();
// Exact match
await page.getByLabel('Email Address', { exact: true }).fill('test@example.com');
// Works with different label associations:
// <label for="email">Email</label><input id="email">
// <label>Email <input></label>
// <input aria-labelledby="email-label">
});Related: getByPlaceholder()
getByPlaceholder()
Locator Strategyawait page.getByPlaceholder('Enter email').fill('user@example.com');
await page.getByPlaceholder('Search...').fill('playwright');getByTestId()
Locator StrategyPurpose: Locate elements by data-testid attribute. Explicit test hooks - stable and recommended for testing.
<!-- HTML -->
<button data-testid="submit-button">Submit</button>
<div data-testid="user-profile">Profile Content</div>
// Test
await page.getByTestId('submit-button').click();
await page.getByTestId('user-profile').isVisible();export default defineConfig({
use: {
testIdAttribute: 'data-test-id' // Use data-test-id instead
}
});Related: getByRole()
getByAltText() / getByTitle()
Locator Strategy// By alt text (images)
await page.getByAltText('Company Logo').isVisible();
await page.getByAltText('Profile Picture').click();
// By title attribute
await page.getByTitle('Close').click();
await page.getByTitle('Settings').hover();User Actions & Interactions
click()
ActionPurpose: Click an element. Automatically waits for element to be visible, enabled, stable, and ready to receive events.
import { test } from '@playwright/test';
test('click actions', async ({ page }) => {
await page.goto('https://example.com');
// Simple click
await page.click('button#submit');
await page.locator('button').click();
// Click with options
await page.click('button', {
button: 'right', // 'left' | 'right' | 'middle'
clickCount: 2, // Double click
delay: 100, // Delay between mousedown and mouseup
position: { x: 10, y: 10 }, // Click at specific position
force: true, // Skip actionability checks
timeout: 5000, // Custom timeout
trial: true // Dry run (check if clickable without clicking)
});
// Double click
await page.dblclick('button#edit');
// Right click
await page.click('button', { button: 'right' });
// Click with modifiers
await page.click('button', { modifiers: ['Control'] });
await page.click('button', { modifiers: ['Shift', 'Meta'] });
// Click at specific coordinates
await page.click('canvas', { position: { x: 100, y: 200 } });
});
// Locator.click() - same options
test('locator click', async ({ page }) => {
await page.goto('https://example.com');
const button = page.locator('button#submit');
await button.click();
await button.click({ force: true }); // Skip checks
});- Element is attached to DOM
- Element is visible (not display:none, visibility:hidden)
- Element is stable (not animating)
- Element receives events (not covered by other elements)
- Element is enabled (not disabled)
Related: dblclick(),hover()
fill()
ActionPurpose: Fill input field. Automatically clears field first, then types text. Waits for element to be editable.
import { test } from '@playwright/test';
test('fill input fields', async ({ page }) => {
await page.goto('https://example.com');
// Simple fill (clears first)
await page.fill('input#username', 'john.doe');
// Locator.fill()
await page.locator('input[name="email"]').fill('user@example.com');
// Fill with options
await page.fill('input#search', 'playwright', {
timeout: 5000,
force: true // Skip actionability checks
});
// Empty string to clear
await page.fill('input#username', '');
});
// Difference: fill() vs type()
test('fill vs type', async ({ page }) => {
await page.goto('https://example.com');
// fill() - Clears first, then types (fast)
await page.fill('input', 'new text'); // Old text replaced
// type() - Just types (char by char)
await page.locator('input').type('additional text'); // Appends
// pressSequentially() - Types with delay (human-like)
await page.locator('input').pressSequentially('slow typing', {
delay: 100 // 100ms between chars
});
});fill()only works on<input>,<textarea>,[contenteditable]- Automatically clears existing value before typing
- For special keys, use
press()instead - Waits for element to be editable (not readonly/disabled)
check() / uncheck()
ActionPurpose: Check or uncheck checkboxes and radio buttons.
import { test } from '@playwright/test';
test('check and uncheck', async ({ page }) => {
await page.goto('https://example.com');
// Check checkbox
await page.check('input[type="checkbox"]');
await page.locator('#terms').check();
// Uncheck checkbox
await page.uncheck('input[type="checkbox"]');
// Check if already checked
const isChecked = await page.locator('#terms').isChecked();
if (!isChecked) {
await page.check('#terms');
}
// Radio buttons
await page.check('input[value="option1"]');
// With options
await page.check('#terms', {
force: true,
timeout: 5000
});
});
// Verify checkbox state
test('verify checkbox', async ({ page }) => {
await page.goto('https://example.com');
await page.check('#terms');
await expect(page.locator('#terms')).toBeChecked();
await page.uncheck('#terms');
await expect(page.locator('#terms')).not.toBeChecked();
});Related: click()
selectOption()
ActionPurpose: Select option(s) from <select> dropdown.
import { test } from '@playwright/test';
test('select options', async ({ page }) => {
await page.goto('https://example.com');
// By value attribute
await page.selectOption('select#country', 'usa');
// By visible text (label)
await page.selectOption('select#country', { label: 'United States' });
// By index (0-based)
await page.selectOption('select#country', { index: 2 });
// Multiple options (multi-select)
await page.selectOption('select#skills', ['java', 'python', 'javascript']);
// Multiple by label
await page.selectOption('select#skills', [
{ label: 'Java' },
{ label: 'Python' }
]);
// Using locator
await page.locator('select#country').selectOption('usa');
// Get selected value
const value = await page.locator('select#country').inputValue();
console.log(`Selected: ${value}`);
});<select id="country" name="country"> <option value="">Select Country</option> <option value="usa">United States</option> <option value="uk">United Kingdom</option> <option value="in">India</option> </select> <!-- Multi-select --> <select id="skills" multiple> <option value="java">Java</option> <option value="python">Python</option> <option value="javascript">JavaScript</option> </select>
Related: fill()
hover()
Actionimport { test } from '@playwright/test';
test('hover actions', async ({ page }) => {
await page.goto('https://example.com');
// Simple hover
await page.hover('button#menu');
// Hover and click submenu
await page.hover('.menu-item');
await page.click('.submenu-item');
// Hover at specific position
await page.hover('canvas', { position: { x: 100, y: 200 } });
// With modifiers
await page.hover('element', { modifiers: ['Control'] });
});dragAndDrop()
Actionimport { test } from '@playwright/test';
test('drag and drop', async ({ page }) => {
await page.goto('https://example.com');
// Simple drag and drop
await page.dragAndDrop('#source', '#target');
// With options
await page.dragAndDrop('#source', '#target', {
sourcePosition: { x: 10, y: 10 },
targetPosition: { x: 20, y: 20 },
force: true
});
// Using locator
const source = page.locator('#draggable');
const target = page.locator('#droppable');
await source.dragTo(target);
});press() / keyboard
ActionPurpose: Press keyboard keys and key combinations.
import { test } from '@playwright/test';
test('keyboard actions', async ({ page }) => {
await page.goto('https://example.com');
// Press single key
await page.press('input#search', 'Enter');
await page.press('input', 'Tab');
await page.press('input', 'Escape');
// Keyboard shortcuts
await page.press('input', 'Control+A'); // Select all
await page.press('input', 'Control+C'); // Copy
await page.press('input', 'Control+V'); // Paste
await page.press('input', 'Meta+A'); // Mac: Cmd+A
// Using keyboard object
await page.keyboard.press('Enter');
await page.keyboard.press('ArrowDown');
// Type text (char by char)
await page.keyboard.type('Hello World');
// Key down/up
await page.keyboard.down('Shift');
await page.keyboard.press('KeyA'); // Types 'A'
await page.keyboard.up('Shift');
// Insert text (fast, no events)
await page.keyboard.insertText('Quick insert');
// Available keys:
// F1-F12, Digit0-9, KeyA-Z
// Enter, Escape, Tab, Backspace, Delete
// ArrowUp, ArrowDown, ArrowLeft, ArrowRight
// Home, End, PageUp, PageDown
// Control, Alt, Shift, Meta
});Related: fill()
setInputFiles()
ActionPurpose: Upload file(s) to <input type="file"> element.
import { test } from '@playwright/test';
import path from 'path';
test('file upload', async ({ page }) => {
await page.goto('https://example.com/upload');
// Upload single file
await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
// Upload multiple files
await page.setInputFiles('input[type="file"]', [
'file1.pdf',
'file2.png',
'file3.docx'
]);
// Using path.join for cross-platform
await page.setInputFiles(
'input[type="file"]',
path.join(__dirname, '../testdata/document.pdf')
);
// Upload from buffer
await page.setInputFiles('input[type="file"]', {
name: 'file.txt',
mimeType: 'text/plain',
buffer: Buffer.from('File content here')
});
// Clear file input
await page.setInputFiles('input[type="file"]', []);
// Using locator
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles('document.pdf');
});
// Handle file chooser dialog
test('file chooser', async ({ page }) => {
await page.goto('https://example.com');
// Start waiting for file chooser before clicking
const fileChooserPromise = page.waitForEvent('filechooser');
await page.click('button#upload');
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles('path/to/file.pdf');
});Assertions & Expectations
expect() - Web-First Assertions
AssertionPurpose: Playwright's auto-retrying assertions for UI elements. Automatically waits until condition is met or timeout.
import { test, expect } from '@playwright/test';
test('element assertions', async ({ page }) => {
await page.goto('https://example.com');
const locator = page.locator('h1');
// Visibility
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
await expect(locator).not.toBeVisible();
// State
await expect(locator).toBeEnabled();
await expect(locator).toBeDisabled();
await expect(locator).toBeEditable();
await expect(locator).toBeChecked();
await expect(locator).toBeFocused();
// Text
await expect(locator).toHaveText('Welcome');
await expect(locator).toContainText('Wel');
await expect(locator).toHaveText(/welcome/i); // Regex
// Value (for inputs)
await expect(page.locator('input')).toHaveValue('john@example.com');
await expect(page.locator('input')).toHaveValue(/.*@example.com/);
// Attribute
await expect(locator).toHaveAttribute('href', '/home');
await expect(locator).toHaveAttribute('class', /btn-primary/);
// CSS
await expect(locator).toHaveClass('active');
await expect(locator).toHaveClass(/btn-/);
await expect(locator).toHaveCSS('color', 'rgb(0, 0, 0)');
// Count
await expect(page.locator('.item')).toHaveCount(5);
// Empty
await expect(page.locator('input')).toBeEmpty();
// Attached to DOM
await expect(locator).toBeAttached();
// In viewport
await expect(locator).toBeInViewport();
});test('page assertions', async ({ page }) => {
await page.goto('https://example.com');
// URL
await expect(page).toHaveURL('https://example.com/dashboard');
await expect(page).toHaveURL(/.*dashboard.*/);
// Title
await expect(page).toHaveTitle('Dashboard - MyApp');
await expect(page).toHaveTitle(/Dashboard/);
// Screenshot comparison
await expect(page).toHaveScreenshot('homepage.png');
await expect(page.locator('.header')).toHaveScreenshot();
});test('custom assertion timeout', async ({ page }) => {
await page.goto('https://example.com');
// Wait up to 60 seconds
await expect(page.locator('.slow-element')).toBeVisible({
timeout: 60000
});
// Soft assertions (doesn't stop test on failure)
await expect.soft(page.locator('h1')).toHaveText('Wrong Text');
await expect.soft(page.locator('h2')).toBeVisible();
// Test continues, reports all failures
});Web-first assertions automatically retry every 100ms until the condition passes or timeout (default 5s). This eliminates flaky tests caused by timing issues.
Related: Locator
Generic Assertions
AssertionPurpose: Standard Jest-style assertions for non-UI values(numbers, strings, arrays, objects). No auto-retry.
import { test, expect } from '@playwright/test';
test('generic assertions', async ({ page }) => {
await page.goto('https://example.com');
// Get value first, then assert (no auto-retry)
const count = await page.locator('.item').count();
expect(count).toBe(5);
expect(count).toBeGreaterThan(3);
expect(count).toBeLessThanOrEqual(10);
const text = await page.locator('h1').textContent();
expect(text).toBe('Welcome');
expect(text).toContain('Wel');
expect(text).toMatch(/welcome/i);
// Arrays
const items = ['a', 'b', 'c'];
expect(items).toHaveLength(3);
expect(items).toContain('b');
expect(items).toEqual(['a', 'b', 'c']);
// Objects
const user = { name: 'John', age: 30 };
expect(user).toHaveProperty('name');
expect(user).toMatchObject({ name: 'John' });
expect(user).toEqual({ name: 'John', age: 30 });
// Numbers
expect(5).toBe(5);
expect(5).not.toBe(10);
expect(5).toBeGreaterThan(3);
expect(5).toBeLessThan(10);
// Booleans
expect(true).toBeTruthy();
expect(false).toBeFalsy();
// Null/Undefined
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('value').toBeDefined();
});Web-First (Auto-Retry)
expect(locator).toBeVisible()expect(locator).toHaveText('text')- Use for UI elements
- Retries automatically
Generic (Immediate)
expect(value).toBe(5)expect(array).toHaveLength(3)- Use for variables/data
- Checks once immediately
Related: expect() Web-First
Network Interception & API Testing
route()
NetworkPurpose: Intercept and modify network requests/responses. Essential for mocking APIs and controlling network behavior.
import { test } from '@playwright/test';
test('network interception', async ({ page }) => {
// Mock API response
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
])
});
});
// Modify request
await page.route('**/api/users', route => {
const headers = {
...route.request().headers(),
'Authorization': 'Bearer fake-token'
};
route.continue({ headers });
});
// Abort request (block resources)
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());
// Modify response
await page.route('**/api/config', async route => {
const response = await route.fetch();
const json = await response.json();
json.feature_flag = true; // Modify
route.fulfill({ response, json });
});
await page.goto('https://example.com');
});
// Monitor requests/responses
test('monitor network', async ({ page }) => {
// Log all requests
page.on('request', request => {
console.log(`>> ${request.method()} ${request.url()}`);
});
// Log all responses
page.on('response', response => {
console.log(`<< ${response.status()} ${response.url()}`);
});
// Filter specific APIs
page.on('response', async response => {
if (response.url().includes('/api/users')) {
console.log(await response.json());
}
});
await page.goto('https://example.com');
});
// Wait for specific network activity
test('wait for network', async ({ page }) => {
await page.goto('https://example.com');
// Wait for API call
const response = await page.waitForResponse('**/api/users');
const data = await response.json();
console.log(data);
// Wait for request
const request = await page.waitForRequest('**/api/login');
console.log(request.postDataJSON());
});Related: request
request (APIRequestContext)
API TestingPurpose: Built-in API testing without browser. Send HTTP requests directly.
import { test, expect } from '@playwright/test';
test('API testing', async ({ request }) => {
// GET request
const response = await request.get('https://api.example.com/users');
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const users = await response.json();
expect(users).toHaveLength(10);
// POST request
const createResponse = await request.post('https://api.example.com/users', {
data: {
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
}
});
expect(createResponse.status()).toBe(201);
const newUser = await createResponse.json();
expect(newUser.name).toBe('John Doe');
// PUT request
await request.put(`https://api.example.com/users/${newUser.id}`, {
data: {
name: 'John Updated',
role: 'user'
}
});
// PATCH request
await request.patch(`https://api.example.com/users/${newUser.id}`, {
data: {
email: 'newemail@example.com'
}
});
// DELETE request
const deleteResponse = await request.delete(
`https://api.example.com/users/${newUser.id}`
);
expect(deleteResponse.status()).toBe(204);
// With headers
await request.get('https://api.example.com/protected', {
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json'
}
});
// With query parameters
await request.get('https://api.example.com/users', {
params: {
page: 1,
limit: 10,
sort: 'name'
}
});
});
// Create request context with base config
test('request context', async ({ playwright }) => {
const context = await playwright.request.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
'Authorization': 'Bearer token123',
'Accept': 'application/json'
}
});
// Now all requests use base URL and headers
const response = await context.get('/users');
const users = await response.json();
await context.dispose();
});
// Hybrid: UI + API testing
test('hybrid test', async ({ page, request }) => {
// Setup: Create data via API (fast)
const user = await request.post('/api/users', {
data: { name: 'Test User', email: 'test@example.com' }
});
const userId = (await user.json()).id;
// Test: Verify in UI
await page.goto(`/users/${userId}`);
await expect(page.getByRole('heading')).toHaveText('Test User');
// Cleanup: Delete via API (fast)
await request.delete(`/api/users/${userId}`);
});- Speed: No browser overhead
- Setup/Teardown: Create test data via API
- Verification: Check backend state directly
- Hybrid Tests: Combine UI and API validation
Related: route()
Advanced Features
test() - Fixtures
Test FrameworkPurpose: Playwright's test function with built-in fixtures for dependency injection and automatic setup/teardown.
import { test, expect } from '@playwright/test';
// Built-in fixtures
test('using fixtures', async ({
page, // Page instance (fresh per test)
context, // BrowserContext (isolated session)
browser, // Browser instance
browserName, // 'chromium' | 'firefox' | 'webkit'
request // APIRequestContext for API testing
}) => {
await page.goto('https://example.com');
console.log(`Running on: ${browserName}`);
// Each test gets fresh fixtures
// Automatic cleanup after test
});
// Test describe blocks
test.describe('Login Feature', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test.afterEach(async ({ page }) => {
// Cleanup after each test
});
test('successful login', async ({ page }) => {
// Test code
});
test('failed login', async ({ page }) => {
// Test code
});
});
// Test hooks
test.beforeAll(async () => {
// Runs once before all tests in file
});
test.afterAll(async () => {
// Runs once after all tests in file
});// fixtures/test.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
type MyFixtures = {
loginPage: LoginPage;
authenticatedPage: Page;
};
export const test = base.extend<MyFixtures>({
// Page Object fixture
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
// Automatic cleanup
},
// Pre-authenticated page
authenticatedPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'user@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard');
await use(page);
// Optional cleanup
await page.goto('/logout');
}
});
export { expect } from '@playwright/test';import { test, expect } from './fixtures/test';
test('test with custom fixtures', async ({ loginPage, authenticatedPage }) => {
// loginPage and authenticatedPage are ready to use
await loginPage.goto();
await loginPage.login('user@example.com', 'password');
// Or use pre-authenticated page
await authenticatedPage.goto('/settings');
});- Dependency Injection: Automatically provided to tests
- Isolation: Fresh instance per test
- Reusability: Share setup across tests
- Auto-cleanup: Automatic teardown
Related: Page,BrowserContext
Trace Viewer
DebuggingPurpose: Record and debug test execution with screenshots, DOM snapshots, network logs, and source code.
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry',
// Options: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry'
}
});
// Programmatic tracing
test('with tracing', async ({ page, context }) => {
// Start tracing
await context.tracing.start({
screenshots: true,
snapshots: true,
sources: true
});
await page.goto('https://example.com');
await page.click('button#submit');
// Stop and save trace
await context.tracing.stop({
path: 'trace.zip'
});
});# View trace npx playwright show-trace trace.zip # Trace viewer features: # - Timeline of all actions # - Screenshots at each step # - DOM snapshots (inspect HTML at any point) # - Network requests/responses # - Console logs # - Source code that executed # - Time-travel debugging
- Post-mortem debugging: Inspect failed test without re-running
- Time travel: Click any action to see exact state
- Complete context: See everything that happened
- Share with team: Send trace.zip file
screenshot() / video()
Captureimport { test } from '@playwright/test';
test('screenshots', async ({ page }) => {
await page.goto('https://example.com');
// Full page screenshot
await page.screenshot({ path: 'screenshot.png' });
// Full page (including below fold)
await page.screenshot({
path: 'fullpage.png',
fullPage: true
});
// Element screenshot
const element = page.locator('.header');
await element.screenshot({ path: 'header.png' });
// Screenshot to buffer
const buffer = await page.screenshot();
// With options
await page.screenshot({
path: 'custom.png',
type: 'jpeg', // 'png' | 'jpeg'
quality: 80, // JPEG quality 0-100
clip: {
x: 0,
y: 0,
width: 800,
height: 600
},
omitBackground: true // Transparent background
});
});
// Auto-screenshots on failure
// playwright.config.ts
export default defineConfig({
use: {
screenshot: 'only-on-failure',
// Options: 'off' | 'on' | 'only-on-failure'
}
});// playwright.config.ts
export default defineConfig({
use: {
video: 'retain-on-failure',
// Options: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry'
videoSize: { width: 1280, height: 720 }
}
});
// Access video path
test('get video', async ({ page }, testInfo) => {
await page.goto('https://example.com');
// Video automatically recorded
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'passed') {
const videoPath = await page.video()?.path();
console.log(`Video: ${videoPath}`);
}
});import { test, expect } from '@playwright/test';
test('visual regression', async ({ page }) => {
await page.goto('https://example.com');
// Compare full page
await expect(page).toHaveScreenshot('homepage.png');
// Compare element
await expect(page.locator('.header')).toHaveScreenshot('header.png');
// With threshold
await expect(page).toHaveScreenshot('page.png', {
maxDiffPixels: 100,
threshold: 0.2
});
// Mask dynamic regions
await expect(page).toHaveScreenshot('page.png', {
mask: [page.locator('.dynamic-ad')],
});
});
// Update baselines
// npx playwright test --update-snapshotsCodegen (Test Generator)
ToolPurpose: Record browser interactions and generate test code automatically.
# Start codegen npx playwright codegen https://example.com # With specific browser npx playwright codegen --browser=firefox https://example.com # With device emulation npx playwright codegen --device="iPhone 13" https://example.com # Save to file npx playwright codegen --target=typescript -o tests/test.spec.ts https://example.com # With authentication state npx playwright codegen --load-storage=auth.json https://example.com # With viewport npx playwright codegen --viewport-size=1280,720 https://example.com # Record in specific locale/timezone npx playwright codegen --lang=de-DE --timezone="Europe/Berlin" https://example.com
- Record: Click, type, navigate - all recorded
- Locators: Auto-generates resilient locators
- Inspector: Shows locator for any element
- Copy: Copy locator or assertion from UI
- Multiple languages: TypeScript, JavaScript, Python, C#, Java
Use Codegen to learn locators and get started quickly, but always refactor generated code into maintainable Page Objects and proper test structure.
Frame & iFrame Handling
frameLocator()
FramePurpose: Locate and interact with elements inside iframes. No need to switch contexts like Selenium.
import { test } from '@playwright/test';
test('iframe handling', async ({ page }) => {
await page.goto('https://example.com');
// Using frameLocator (recommended)
const frame = page.frameLocator('iframe#payment-frame');
await frame.locator('input#card-number').fill('4111111111111111');
await frame.locator('button#pay').click();
// Multiple ways to locate frame
const frameByName = page.frameLocator('[name="iframe-name"]');
const frameByUrl = page.frameLocator('iframe[src*="payment"]');
// Nested iframes
const parentFrame = page.frameLocator('iframe#parent');
const childFrame = parentFrame.frameLocator('iframe#child');
await childFrame.locator('button').click();
// No need to switch back (unlike Selenium)
await page.locator('button#main-button').click();
});
// Using frame() - less common
test('using frame method', async ({ page }) => {
await page.goto('https://example.com');
// By name
const frame = page.frame({ name: 'iframe-name' });
await frame?.click('button');
// By URL pattern
const frame2 = page.frame({ url: /.*payment.*/ });
await frame2?.fill('input', 'text');
// Get all frames
const frames = page.frames();
console.log(`Total frames: ${frames.length}`);
});- No switching: Access frame elements directly
- Auto-retry: Waits for frame to load
- Chainable: Natural syntax for nested frames
- No switch back: Main page always accessible
Related: Locator
Dialogs & Popup Handling
Dialog (Alert, Confirm, Prompt)
DialogPurpose: Handle JavaScript dialogs (alert, confirm, prompt).
import { test } from '@playwright/test';
test('handle dialogs', async ({ page }) => {
await page.goto('https://example.com');
// Handle alert
page.on('dialog', async dialog => {
console.log(`Dialog type: ${dialog.type()}`);
console.log(`Dialog message: ${dialog.message()}`);
await dialog.accept();
});
await page.click('button#show-alert');
// Handle confirm (accept or dismiss)
page.on('dialog', async dialog => {
if (dialog.type() === 'confirm') {
await dialog.accept(); // Click OK
// OR
await dialog.dismiss(); // Click Cancel
}
});
// Handle prompt (enter text)
page.on('dialog', async dialog => {
if (dialog.type() === 'prompt') {
await dialog.accept('User input text');
}
});
// Default behavior (auto-dismiss if no handler)
page.on('dialog', dialog => dialog.dismiss());
});
// Wait for dialog
test('wait for dialog', async ({ page }) => {
await page.goto('https://example.com');
const dialogPromise = page.waitForEvent('dialog');
await page.click('button#trigger-dialog');
const dialog = await dialogPromise;
console.log(dialog.message());
await dialog.accept();
});| Dialog Type | JavaScript | Actions |
|---|---|---|
alert | alert("Message") | accept() |
confirm | confirm("Question?") | accept() or dismiss() |
prompt | prompt("Enter text:") | accept("text") or dismiss() |
beforeunload | Leave page confirmation | accept() or dismiss() |
Popup Windows (New Tab/Window)
Popupimport { test } from '@playwright/test';
test('handle popup windows', async ({ page, context }) => {
await page.goto('https://example.com');
// Method 1: Wait for popup
const popupPromise = page.waitForEvent('popup');
await page.click('a[target="_blank"]'); // Opens new tab
const popup = await popupPromise;
// Interact with popup
await popup.waitForLoadState();
await popup.click('button#submit');
console.log(await popup.title());
// Close popup
await popup.close();
// Back to original page (still active)
await page.click('button#main');
});
// Method 2: Multiple popups
test('multiple popups', async ({ page, context }) => {
await page.goto('https://example.com');
// Get all pages (tabs)
const pages = context.pages();
console.log(`Open pages: ${pages.length}`);
// Listen for new pages
context.on('page', async newPage => {
await newPage.waitForLoadState();
console.log(`New page opened: ${await newPage.title()}`);
});
await page.click('button#open-tabs');
});
// Download handling
test('handle downloads', async ({ page }) => {
await page.goto('https://example.com');
const downloadPromise = page.waitForEvent('download');
await page.click('a#download-link');
const download = await downloadPromise;
// Wait for download to complete
const path = await download.path();
console.log(`Downloaded to: ${path}`);
// Save to specific location
await download.saveAs('./downloads/file.pdf');
// Get download info
console.log(`Filename: ${download.suggestedFilename()}`);
});Quick Reference Summary
- Core APIs: Browser, BrowserContext, Page, Locator
- Locators: getByRole(), getByText(), getByLabel(), getByTestId()
- Actions: click(), fill(), check(), selectOption(), hover()
- Assertions: expect().toBeVisible(), toHaveText(), toBeEnabled()
- Navigation: goto(), waitForURL(), waitForLoadState()
- Network: route(), request (API testing)
- Advanced: test() fixtures, trace viewer, codegen
| Category | Essential Keywords | When to Use |
|---|---|---|
| Locators | getByRole(), getByTestId() | Finding elements (prefer role-based) |
| Actions | click(), fill(), check() | User interactions |
| Assertions | expect().toBeVisible() | Verifying UI state (auto-retry) |
| Waits | waitForLoadState() | Page loading (avoid waitForTimeout) |
| Network | route(), request | Mocking APIs, API testing |
| Debugging | trace, screenshot() | Troubleshooting failures |
- Locators: Playwright locators are lazy and auto-wait; Selenium elements are eager
- Waits: Playwright auto-waits; Selenium needs explicit waits
- Assertions: Playwright has web-first assertions; Selenium uses external libraries
- API Testing: Playwright has built-in request; Selenium needs RestAssured
- Context: Playwright uses BrowserContext; Selenium uses WebDriver instances
Playwright Pattern
// Auto-wait, auto-retry
await page.getByRole('button')
.click();
await expect(page.locator('h1'))
.toHaveText('Welcome');Selenium Pattern
// Manual wait required
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement button = wait.until(
ExpectedConditions.elementToBeClickable(By.id("btn"))
);
button.click();🎉 You've Mastered Playwright APIs!
This reference guide covered all essential Playwright keywords and concepts. Use it as your go-to resource for test automation with Playwright.
- Explore Playwright Interview Preparation guide
- Build production frameworks with Framework Best Practices
- Practice with real projects using TypeScript
- Master advanced features: fixtures, trace viewer, visual testing