Playwright Commands
A working reference for the Playwright commands you'll reach for daily. All examples are TypeScript-first and assume an active page (and request for API testing) inside a Playwright test.
Navigation
page.goto()
Navigate to a URL.
await page.goto('https://example.com');
await page.goto('/dashboard'); // resolves against baseURL
await page.goto('/login', { waitUntil: 'networkidle' });
await page.goto('/secure', {
timeout: 10000,
referer: 'https://example.com/',
});waitUntil options: 'load' (default), 'domcontentloaded', 'networkidle', 'commit'.
page.reload()
Reload the current page.
await page.reload();
await page.reload({ waitUntil: 'networkidle' });page.goBack() / page.goForward()
Move through browser history.
await page.goBack();
await page.goForward();
await page.goBack({ waitUntil: 'domcontentloaded' });Locators
Locators are the way to find elements. They auto-retry until the element is actionable, so you almost never need explicit waits.
page.locator()
Lowest-level locator — accepts a selector engine string.
await page.locator('button.submit').click();
await page.locator('text=Sign in').click();
await page.locator('[data-testid="email"]').fill('user@example.com');
await page.locator('css=button >> text="Save"').click();
await page.locator('xpath=//button[@id="submit"]').click();page.getByRole()
Find by ARIA role and accessible name — preferred for accessibility-aligned tests.
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByRole('link', { name: 'Sign up' }).click();
await page.getByRole('textbox', { name: 'Email' }).fill('jane@example.com');
await page.getByRole('checkbox', { name: 'Remember me' }).check();
await page.getByRole('heading', { level: 1 }).waitFor();page.getByText()
Find by visible text content.
await page.getByText('Welcome back').waitFor();
await page.getByText('Submit').click();
await page.getByText(/order #\d+/i).click(); // regex match
await page.getByText('Submit', { exact: true }).click();page.getByTestId()
Find by data-testid attribute (default; configurable via testIdAttribute).
await page.getByTestId('login-submit').click();
await page.getByTestId('email-field').fill('jane@example.com');page.getByLabel()
Find a form control by its associated <label>.
await page.getByLabel('Email').fill('jane@example.com');
await page.getByLabel('Password').fill('Secret!23');
await page.getByLabel('Remember me').check();page.getByPlaceholder()
Find an input by its placeholder text.
await page.getByPlaceholder('Search…').fill('cypress');
await page.getByPlaceholder('Email address').fill('jane@example.com');Combining and refining locators
// Chain: find an item inside a list
const item = page.getByRole('listitem').filter({ hasText: 'Cypress' });
await item.getByRole('button', { name: 'Edit' }).click();
// First / nth / last
await page.getByRole('listitem').first().click();
await page.getByRole('listitem').nth(2).click();
await page.getByRole('listitem').last().click();
// Scoped locator
const form = page.locator('form#signup');
await form.getByLabel('Email').fill('jane@example.com');
await form.getByRole('button', { name: 'Sign up' }).click();Actions
click()
await page.getByRole('button', { name: 'Save' }).click();
await page.locator('.menu').click({ force: true }); // skip actionability checks
await page.locator('li').click({ button: 'right' }); // right-click
await page.locator('.image').dblclick();
await page.locator('button').click({ modifiers: ['Shift'] });
await page.locator('button').click({ position: { x: 10, y: 5 } });fill()
Set the value of an input, textarea, or contenteditable. Replaces existing content.
await page.getByLabel('Email').fill('jane@example.com');
await page.getByLabel('Notes').fill(''); // clears the fieldtype()
Type character by character — fires keypress events for each character. Use when the app listens to keystrokes.
await page.getByLabel('Search').type('cypress', { delay: 100 });
await page.getByLabel('Search').type('hello{Enter}'); // not magic — use press() for keysFor most inputs, prefer fill() — faster and equivalent.
check() / uncheck()
await page.getByLabel('I accept the terms').check();
await page.getByLabel('Subscribe to newsletter').uncheck();
await page.getByRole('radio', { name: 'Standard shipping' }).check();selectOption()
Select an option in a <select>.
await page.getByLabel('Country').selectOption('US'); // by value
await page.getByLabel('Country').selectOption({ label: 'United States' });
await page.getByLabel('Country').selectOption({ index: 2 });
await page.getByLabel('Tags').selectOption(['a', 'b']); // multiplepress()
Send keyboard input — physical key names, not characters.
await page.getByLabel('Search').press('Enter');
await page.getByLabel('Search').press('Control+A');
await page.keyboard.press('Escape');
await page.keyboard.press('ArrowDown');hover()
await page.getByRole('button', { name: 'Account' }).hover();
await page.getByRole('menuitem', { name: 'Settings' }).click();dragTo()
const source = page.getByTestId('card-1');
const target = page.getByTestId('column-done');
await source.dragTo(target);
// With explicit positions
await source.dragTo(target, {
sourcePosition: { x: 10, y: 10 },
targetPosition: { x: 20, y: 20 },
});For more granular control, drive the mouse directly:
await source.hover();
await page.mouse.down();
await target.hover();
await page.mouse.up();Assertions
Playwright's expect retries until the assertion passes or the timeout is hit — so you don't need separate waits.
toBeVisible() / toBeHidden()
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByText('Loading…')).toBeHidden();
await expect(page.locator('.spinner')).not.toBeVisible();toHaveText() / toContainText()
await expect(page.getByRole('heading')).toHaveText('Welcome back');
await expect(page.getByRole('alert')).toContainText('Invalid');
await expect(page.getByRole('list')).toHaveText(['Apple', 'Banana']); // array for multi-element
await expect(page.getByRole('heading')).toHaveText(/order #\d+/i);toHaveValue()
await expect(page.getByLabel('Email')).toHaveValue('jane@example.com');
await expect(page.getByLabel('Quantity')).toHaveValue('5');toHaveCount()
await expect(page.getByRole('listitem')).toHaveCount(3);
await expect(page.locator('.error')).toHaveCount(0);toBeEnabled() / toBeDisabled()
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
await expect(page.getByRole('button', { name: 'Submit' })).toBeDisabled();toHaveAttribute()
await expect(page.getByRole('link', { name: 'Docs' })).toHaveAttribute('href', '/docs');
await expect(page.locator('input[type="email"]')).toHaveAttribute('required', '');
await expect(page.locator('img.avatar')).toHaveAttribute('src', /avatars\/.*\.png/);Other handy assertions
await expect(page.getByLabel('Email')).toBeFocused();
await expect(page.getByLabel('Subscribe')).toBeChecked();
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle(/qa\.codes/);
await expect(page.locator('.card')).toHaveClass(/active/);
await expect(page.locator('input')).toBeEditable();API Testing
Playwright's request fixture is a fully-featured HTTP client — useful for fixture setup, teardown, and pure API tests.
request.get(), post(), put(), delete()
import { test, expect } from '@playwright/test';
test('GET /api/users returns the seeded list', async ({ request }) => {
const response = await request.get('/api/users');
expect(response.status()).toBe(200);
const users = await response.json();
expect(users).toHaveLength(3);
});
test('POST /api/orders creates an order', async ({ request }) => {
const response = await request.post('/api/orders', {
data: { sku: 'WIDGET-42', quantity: 1 },
headers: { Authorization: 'Bearer ' + token },
});
expect(response.status()).toBe(201);
});
test('PUT /api/users/:id updates a user', async ({ request }) => {
const response = await request.put('/api/users/42', {
data: { name: 'Jane Updated' },
});
expect(response.ok()).toBeTruthy();
});
test('DELETE /api/users/:id removes a user', async ({ request }) => {
const response = await request.delete('/api/users/42');
expect(response.status()).toBe(204);
});expect(response).toBeOK()
Asserts a 2xx status without checking the exact code.
const response = await request.get('/api/health');
await expect(response).toBeOK();Mixing API and UI
Seed via API, verify via UI — much faster than driving the UI for setup.
test.beforeEach(async ({ request, page }) => {
await request.post('/api/test/seed', {
data: { user: 'qa.user@example.com' },
});
await page.goto('/dashboard');
});Waits & auto-waiting
Playwright auto-waits for actionable state on every action. You rarely need explicit waits — but when you do:
page.waitForSelector()
await page.waitForSelector('.dashboard');
await page.waitForSelector('.spinner', { state: 'detached' });In modern code, prefer locator.waitFor():
await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
await page.locator('.spinner').waitFor({ state: 'hidden' });page.waitForResponse()
const responsePromise = page.waitForResponse('**/api/users');
await page.getByRole('button', { name: 'Load users' }).click();
const response = await responsePromise;
expect(response.status()).toBe(200);
// Match by predicate
await page.waitForResponse(
(resp) => resp.url().includes('/api/orders') && resp.status() === 201,
);page.waitForURL()
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('/dashboard');
await page.waitForURL(/\/orders\/\d+/);page.waitForLoadState()
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');Avoid page.waitForTimeout()
await page.waitForTimeout(2000); // ❌ flaky and slowIf you find yourself reaching for a fixed timeout, look for the actual signal you're waiting on — a network response, a DOM change, a URL change — and wait on that instead.
Screenshots & video
page.screenshot()
await page.screenshot({ path: 'screenshots/home.png' });
await page.screenshot({ path: 'screenshots/full.png', fullPage: true });
await page.locator('.modal').screenshot({ path: 'modal.png' });
// Buffer for in-memory use (e.g., visual diff libraries)
const buf = await page.screenshot();
// Mask sensitive areas
await page.screenshot({
path: 'home.png',
mask: [page.locator('.user-avatar'), page.locator('.timestamp')],
});Video recording
Configured in playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
video: 'retain-on-failure', // 'on' | 'off' | 'retain-on-failure' | 'on-first-retry'
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
},
});For the trace viewer (the most useful debugging tool Playwright ships):
npx playwright show-trace trace.zip