Skip to content
SDETRole
SDETRole

Learn / Cheat sheet

Playwright Cheat Sheet

The Playwright Test APIs you reach for every day, on one sheet. Locators are listed in the order Playwright recommends: role, label and text before test ids, and CSS or XPath last. Every assertion here is web-first: it retries until it passes or times out, so you don’t need manual waits.

Checked against Playwright Test 1.5x, TypeScript

Run and debug

TaskCode
New project npm init playwright@latest
Run all tests npx playwright test
One file, one line npx playwright test tests/login.spec.ts:12
By title or tag npx playwright test --grep @smoke
One browser project npx playwright test --project=chromium
See the browser npx playwright test --headed
Step through with the inspector npx playwright test --debug
UI mode (watch, time travel) npx playwright test --ui
Only last run’s failures npx playwright test --last-failed
Hunt a flaky test npx playwright test my.spec.ts --repeat-each=20
Shard across CI machines npx playwright test --shard=1/4
Open the HTML report npx playwright show-report
Open a trace npx playwright show-trace trace.zip
Record a script npx playwright codegen https://example.com

Locators (preferred first)

TaskCode
By role and accessible name page.getByRole('button', { name: 'Sign in' })
Form field by its label page.getByLabel('Email')
By placeholder page.getByPlaceholder('Search')
By text (exact) page.getByText('Welcome back', { exact: true })
By test id (data-testid) page.getByTestId('checkout-submit')
CSS page.locator('#orders tbody tr')
XPath page.locator('xpath=//table[@id="orders"]//tr')
Filter by text page.getByRole('row').filter({ hasText: 'Mei Lin' })
Filter by child page.getByRole('listitem').filter({ has: page.getByRole('button', { name: 'Buy' }) })
Chain into a match row.getByRole('button', { name: 'Cancel' })
Pick one of many .first() .last() .nth(2)
Inside an iframe page.frameLocator('#payment').getByLabel('Card number')

Locators are lazy: nothing is looked up until you act or assert, so define them once and reuse them.

Actions

TaskCode
Navigate (relative to baseURL) await page.goto('/login')
Click await locator.click()
Replace the value of an input await locator.fill('mei@example.com')
Type key by key await locator.pressSequentially('abc', { delay: 50 })
Press a key await locator.press('Enter')
Checkbox or radio await locator.check() / await locator.uncheck()
Select an option await page.getByLabel('Country').selectOption('UK')
Upload a file await locator.setInputFiles('fixtures/invoice.pdf')
Hover await locator.hover()
Drag and drop await source.dragTo(target)
Accept the next dialog page.once('dialog', (d) => d.accept())

Web-first assertions

TaskCode
Visible / hidden await expect(locator).toBeVisible() / .toBeHidden()
Exact text await expect(locator).toHaveText('Order placed')
Text contains await expect(locator).toContainText('placed')
Input value await expect(input).toHaveValue('42')
Number of matches await expect(page.getByRole('row')).toHaveCount(5)
Enabled / disabled / checked toBeEnabled() toBeDisabled() toBeChecked()
Attribute await expect(menu).toHaveAttribute('aria-expanded', 'true')
URL and title await expect(page).toHaveURL(/dashboard/) / .toHaveTitle('Orders')
Negate await expect(spinner).not.toBeVisible()
Keep going after a failure await expect.soft(total).toHaveText('£40.00')
Retry any async check await expect.poll(async () => (await api.get(url)).status()).toBe(200)
Visual comparison await expect(page).toHaveScreenshot('home.png')

Network and API

TaskCode
Mock a response await page.route('**/api/orders', (r) => r.fulfill({ json: [] }))
Fail a request await page.route('**/api/orders', (r) => r.abort())
Change a real response await page.route('**/api/me', async (r) => { const res = await r.fetch(); const json = await res.json(); await r.fulfill({ response: res, json: { ...json, plan: 'pro' } }); })
Wait for a response
const res = page.waitForResponse('**/api/orders');
await page.getByRole('button', { name: 'Save' }).click();
expect((await res).ok()).toBeTruthy();
API test
test('list users', async ({ request }) => {
  const res = await request.get('/api/users');
  await expect(res).toBeOK();
  expect(await res.json()).toHaveLength(3);
});

Structure, fixtures and hooks

TaskCode
Group tests test.describe('checkout', () => { /* tests */ })
Run before each test test.beforeEach(async ({ page }) => { await page.goto('/'); })
Tag a test test('pays by card', { tag: '@smoke' }, async ({ page }) => { /* ... */ })
Skip conditionally test.skip(browserName === 'webkit', 'Not supported on WebKit')
Parallel within a file test.describe.configure({ mode: 'parallel' })
Custom fixture
export const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
});
Reuse a signed-in state test.use({ storageState: 'playwright/.auth/user.json' })
Name a step in the report await test.step('add to cart', async () => { /* ... */ })

playwright.config.ts

SettingExample
Base URL for page.goto use: { baseURL: 'http://localhost:3000' }
Evidence on failure use: { trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' }
Retries only in CI retries: process.env.CI ? 2 : 0
Parallelism fullyParallel: true, workers: process.env.CI ? 2 : undefined
Fail if test.only is committed forbidOnly: !!process.env.CI
Start the app first webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI }
Browsers projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }]
Default timeouts timeout: 30_000, expect: { timeout: 5_000 }

Practise and go deeper

More cheat sheets

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →