On this page7 sections
CommandsIntermediate6-8 min reference

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.

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 field

type()

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 keys

For 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']);        // multiple

press()

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 slow

If 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