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 →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.
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
| Tool | Command |
|---|---|
| UI Mode | --ui |
| Trace Viewer | show-trace trace.zip |
| Codegen | codegen example.com |
| Debug mode | --debug |
| Inline pause | page.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/getByLabelover 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.