Skip to main content

Playwright cheatsheet

A one-page reference for Playwright. For the full walkthrough β€” architecture, POM, CI setup, interview Q&A β€” see the complete guide.

πŸ“– Full guide: Playwright β†’ Intermediate
Learn this in 5 minutes5 min
  1. Begin with the Install & config card
  2. Then the Anatomy of a test and Locators β€” priority order cards
  3. Treat the other 9 cards as lookups β€” scan by card title when you need one
  4. Open the full guide when a card isn't enough

Install & config​

npm init playwright@latest
npx playwright install
npx playwright test
// playwright.config.ts
export default defineConfig({
testDir: './tests',
retries: process.env.CI ? 2 : 0,
use: { trace: 'on-first-retry' },
projects: [{ name: 'chromium', use: devices['Desktop Chrome'] }],
});

Anatomy of a test​

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

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

test('valid creds redirect', async ({ page }) => {
await page.getByLabel('Username').fill('abhishek');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/dashboard/);
});
});

Locators β€” priority order​

page.getByRole('button', { name: 'Submit' });
page.getByLabel('Username');
page.getByPlaceholder('Search…');
page.getByText('Welcome');
page.getByTestId('user-menu'); // last resort: CSS/XPath
// chaining / filtering
page.getByRole('row', { name: 'Invoice #1042' })
.getByRole('button', { name: 'Delete' });
page.getByRole('listitem').filter({ hasText: 'A' });
page.getByRole('listitem').nth(2);

Waiting​

// auto-waits before every action β€” no sleeps needed
await page.waitForURL('**/dashboard');
await locator.waitFor({ state: 'visible' });
await page.waitForResponse(r => r.url().includes('/api/orders'));

Anti-pattern: page.waitForTimeout(3000) β€” never commit a hard sleep.

See: Auto-Waiting & Actionability

Assertions (web-first, auto-retry)​

await expect(locator).toBeVisible();
await expect(locator).toHaveText('Order confirmed');
await expect(locator).toHaveCount(3);
await expect(page).toHaveURL(/\/orders\/\d+/);
await expect(page).toHaveScreenshot('dash.png');

expect.soft(locator).toHaveText('X'); // collects, doesn't stop
await expect.poll(async () => n()).toBeLessThan(5);

Fixtures (DI)​

export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
authedPage: async ({ browser }, use) => {
const ctx = await browser.newContext({ storageState: 'auth/user.json' });
await use(await ctx.newPage());
await ctx.close();
},
});

Built-ins: page, context, browser, request.

Network mocking​

await page.route('**/api/orders', route =>
route.fulfill({ status: 200, body: JSON.stringify([{ id: 1 }]) })
);
await page.route('**/*.{png,jpg}', r => r.abort()); // block assets
await page.route('**/api/payment', r => r.abort('failed')); // sim. failure

Auth: reuse a session​

// once, in setup
await page.context().storageState({ path: 'auth/user.json' });
// playwright.config.ts
use: { storageState: 'auth/user.json' }

Avoids re-running UI login for every test.

Real-world UI​

page.frameLocator('#payment-iframe').getByLabel('Card');
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.getByRole('link', { name: 'Open' }).click(),
]);
await page.getByLabel('Upload').setInputFiles('f.pdf');
page.on('dialog', d => d.accept());

Debugging toolkit​

ToolCommand
UI Mode--ui
Trace Viewershow-trace trace.zip
Codegencodegen example.com
Debug mode--debug
Inline pausepage.pause()

CI​

npx playwright install --with-deps
npx playwright test --shard=1/4
npx playwright show-trace trace.zip

forbidOnly: !!process.env.CI blocks a stray .only from shipping.

Rules of thumb​

  • Prefer getByRole/getByLabel over CSS/XPath.
  • No hard sleeps, ever.
  • One assertion concept per test.
  • Tests independent β€” no shared UI-chain state.
  • Tag tests (@smoke) and wire to CI via --grep.