Playwright Cheat sheet

Everything you use daily in Playwright Test on one page — in TypeScript, using the modern locator-based API Playwright recommends. Bookmark it for coding and for the night before an interview. New to Playwright? Start with The Complete Playwright Tutorial.

Advertisement

Prefer user-facing locators — they survive refactors better than CSS or XPath.

page.getByRole('button', { name: 'Submit' })   // ⭐ best — how users and screen readers see it
page.getByLabel('Email')                        // form fields with a label
page.getByPlaceholder('Enter email')
page.getByText('Welcome back')                  // non-interactive text
page.getByAltText('Company logo')               // images
page.getByTitle('Close')
page.getByTestId('login-btn')                   // needs data-testid
page.locator('#username')                       // CSS — fallback
page.locator('//input[@id="user"]')             // XPath — last resort

Filtering and chaining:

page.getByRole('listitem').filter({ hasText: 'Product 2' })

page.getByRole('row').filter({ has: page.getByText('Ravi') })
    .getByRole('button', { name: 'Edit' })      // "Edit" in Ravi's row

page.locator('.card').first()                   // .last() / .nth(2)

page.locator('#form').getByRole('button')       // scoped inside a container

⚠️ Strictness: if a locator matches several elements, actions throw instead of guessing. Make it unambiguous with filter(), scoping, or first() when the order genuinely doesn't matter.

Actions

await page.goto('/login');                                         // uses baseURL
await page.getByRole('button', { name: 'Log in' }).click();

await page.getByLabel('Email').fill('qa@example.com');            // clears, then types
await page.getByLabel('Email').clear();

await page.getByLabel('Search').pressSequentially('playwright', { delay: 50 }); // key by key
await page.getByLabel('Search').press('Enter');

await page.getByLabel('I agree').check();                          // checkbox / radio
await page.getByLabel('Country').selectOption('India');           // dropdown

await page.getByLabel('Upload').setInputFiles('files/resume.pdf');

await page.getByText('Products').hover();
await page.getByText('Item').dblclick();
await page.getByText('Item').click({ button: 'right' });           // right-click

await page.locator('#source').dragTo(page.locator('#target'));

Older tutorials use page-level shortcuts like page.click('#btn') and page.type(). They still exist, but Playwright's docs recommend the locator versions above (page.type() is deprecated in favor of pressSequentially()).

Reading values

await page.getByTestId('message').textContent();
await page.getByTestId('message').innerText();       // rendered text only
await page.getByLabel('Email').inputValue();
await page.getByRole('link', { name: 'Docs' }).getAttribute('href');

await page.getByRole('listitem').count();
await page.getByRole('listitem').allTextContents();  // string[]

await page.title();
page.url();

For checks, prefer assertions (below) over reading values and comparing — assertions retry automatically.

Auto-Wait and Waiting ⭐

Before every action Playwright waits for the element to be attached, visible, stable, enabled and able to receive events. You rarely need explicit waits — when you do, wait for a condition, never a fixed time:

// ❌ Avoid — hard-coded pause
await page.waitForTimeout(3000);

// ✅ Prefer — condition-based
await expect(page.getByTestId('result')).toBeVisible();
await page.waitForURL('**/dashboard');

// Wait for a specific API call triggered by a click
const responsePromise = page.waitForResponse(
  r => r.url().includes('/api/users') && r.status() === 200
);

await page.getByRole('button', { name: 'Load users' }).click();
await responsePromise;

Playwright's docs discourage waitForLoadState('networkidle') and waitForSelector() for tests — web-first assertions do the same job more reliably. More in Playwright Auto-Wait & Synchronization.

Assertions (Web-First)

These auto-retry until they pass or time out (5 seconds by default):

const msg = page.getByTestId('message');

await expect(msg).toBeVisible();
await expect(msg).toBeHidden();
await expect(msg).toHaveText('Saved');
await expect(msg).toContainText('Sav');

await expect(msg).not.toBeVisible();                 // negate any assertion

await expect(
  page.getByRole('button', { name: 'Save' })
).toBeEnabled();                                     // or toBeDisabled()

await expect(page.getByLabel('I agree')).toBeChecked();
await expect(page.getByLabel('Email')).toHaveValue('qa@example.com');

await expect(
  page.getByRole('link', { name: 'Docs' })
).toHaveAttribute('href', /docs/);

await expect(page.getByRole('listitem')).toHaveCount(5);

await expect(page).toHaveTitle(/Dashboard/);
await expect(page).toHaveURL(/dashboard/);

await expect.soft(msg).toHaveText('Saved');          // soft: records failure, test continues

await expect(page).toHaveScreenshot('dashboard.png');   // visual comparison

Details: Playwright Assertions & Screenshots.

Frames, Dialogs, New Tabs and Downloads

// iframe
await page
  .frameLocator('#payment-frame')
  .getByLabel('Card number')
  .fill('4111111111111111');

// JavaScript alert / confirm — register the handler BEFORE the click
page.once('dialog', dialog => dialog.accept());
await page.getByRole('button', { name: 'Delete' }).click();

// New tab
const pagePromise = context.waitForEvent('page');
await page.getByRole('link', { name: 'Open in new tab' }).click();

const newTab = await pagePromise;
await newTab.waitForLoadState();
await expect(newTab).toHaveTitle(/Help/);

// Download
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Export CSV' }).click();

const download = await downloadPromise;
await download.saveAs('downloads/' + download.suggestedFilename());

More in Playwright UI Handling: Alerts, Tabs & Keyboard.

Screenshots

await page.screenshot({ path: 'page.png', fullPage: true });

await page
  .getByTestId('chart')
  .screenshot({ path: 'chart.png' });   // one element

Test Runner

import { test, expect } from '@playwright/test';

test.describe('Login', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('valid login @smoke', async ({ page }) => {
    await page.getByLabel('Username').fill('admin');
    await page.getByRole('button', { name: 'Log in' }).click();
    await expect(page).toHaveURL(/dashboard/);
  });

  test('heavy report', async ({ page }) => {
    test.slow();                 // triples the timeout for this test
    await page.goto('/reports');
  });
});
Annotation Effect
test.only() Run only this test (never commit it)
test.skip() Skip it
test.fixme() Mark as known-broken and skip
test.slow() Triple the timeout
test.describe.serial() Run in order, stop on first failure
@smoke in the title A tag — run with --grep @smoke

Hooks, workers and retries: Playwright Test Runner.

Custom fixture (e.g. a page object)

import { test as base, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await use(loginPage);           // the test runs here
  },
});

test('uses a fixture', async ({ loginPage }) => {
  await expect(loginPage.page).toHaveURL(/login/);
});

Storage State (Log In Once) ⭐

The biggest speed win in a Playwright suite — every test skips the login flow:

// tests/auth.setup.ts — after logging in once:
await page.context().storageState({
  path: 'playwright/.auth/user.json'
});

// playwright.config.ts — reuse it (see the config below)
use: {
  storageState: 'playwright/.auth/user.json'
}

Full walkthrough: Playwright Storage State.

API Testing and Network Mocking

// API request (the request fixture)
const res = await request.post('/api/login', {
  data: { user: 'admin', pass: 'secret' }
});

expect(res.status()).toBe(200);

const body = await res.json();

// Mock a response
await page.route('**/api/users', route =>
  route.fulfill({
    status: 200,
    json: { users: [] }
  })
);

// Simulate a server error
await page.route('**/api/orders', route =>
  route.fulfill({ status: 500 })
);

// Block images to speed up runs
await page.route('**/*.{png,jpg,jpeg}', route => route.abort());

More: Playwright API Testing & Network Mocking.

Config (playwright.config.ts)

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  workers: process.env.CI ? 2 : undefined,
  retries: process.env.CI ? 2 : 0,
  timeout: 30_000,                   // per test
  expect: { timeout: 5_000 },        // per assertion
  reporter: [['html'], ['list']],

  use: {
    baseURL: 'https://example.com',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'on-first-retry',         // ⭐ enables the trace viewer
  },

  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },

    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json'
      },
      dependencies: ['setup'],
    },

    {
      name: 'firefox',
      use: {
        ...devices['Desktop Firefox'],
        storageState: 'playwright/.auth/user.json'
      },
      dependencies: ['setup'],
    },
  ],
});

CLI Commands

npx playwright test                        # run all tests
npx playwright test tests/login.spec.ts    # one file
npx playwright test -g "valid login"       # by title
npx playwright test --grep @smoke          # by tag
npx playwright test --project=firefox      # one browser
npx playwright test --headed               # see the browser
npx playwright test --ui                   # ⭐ UI mode — time-travel debugging
npx playwright test --debug                # step through with the Inspector
npx playwright test --workers=1            # run serially
npx playwright test --last-failed          # re-run only failures
npx playwright show-report                 # HTML report
npx playwright show-trace trace.zip        # ⭐ trace viewer
npx playwright codegen https://example.com  # record a test
npx playwright install --with-deps         # browsers + OS dependencies (CI)

Pause a running test at any line with await page.pause();. See Playwright Reports & Debugging.

Playwright vs Selenium (Quick)

Aspect Playwright Selenium
Waits Automatic Explicit waits you write
Test runner Built in TestNG / JUnit
API testing & mocking Built in Separate tools
Debugging Trace viewer, UI mode Screenshots and logs
Ecosystem Growing fast Largest

→ Full comparison · Convert code with the Selenium ⇄ Playwright Translator

Go Deeper

📚 Official documentation: Selenium docs: Locator strategies · Selenium documentation