Playwright Cheatsheet
Writing Tests
Use this Playwright reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Basic test structure
import { test, expect } from '@playwright/test'; test('page title is correct', async ({ page }) => { await page.goto('https://example.com'); await expect(page).toHaveTitle('Example Domain'); });
test() overloads
test('name', async ({ page }) => { /* ... */ }); test('name', { tag: '@smoke' }, async ({ page }) => { /* ... */ }); test.only('name', async ({ page }) => { /* ... */ }); // focus — run only this test.skip('name', async ({ page }) => { /* ... */ }); // skip unconditionally test.fixme('name', async ({ page }) => { /* ... */ });// known broken, do not fail CI test.fail('name', async ({ page }) => { /* ... */ }); // expected to fail
Conditional skip / fixme at runtime
test('mobile only', async ({ page, isMobile }) => { test.skip(!isMobile, 'Desktop not relevant'); // ... }); test('buggy feature', async ({ page }) => { test.fixme(process.env.CI === 'true', 'Flaky on CI, see #123'); });
Slow tests
test('heavy upload', async ({ page }) => { test.slow(); // triples the timeout for this test only // ... });
Grouping with describe
test.describe('auth flow', () => { test('login', async ({ page }) => { /* ... */ }); test('logout', async ({ page }) => { /* ... */ }); }); test.describe.serial('ordered suite', () => { // tests run in sequence, next test skipped if previous fails }); test.describe.only('focused group', () => { /* ... */ }); test.describe.skip('skip group', () => { /* ... */ });
Hooks
test.beforeAll(async ({ browser }) => { // runs once per describe block — note fixture is browser, not page }); test.afterAll(async () => { /* cleanup */ }); test.beforeEach(async ({ page }) => { await page.goto('/'); }); test.afterEach(async ({ page }, testInfo) => { if (testInfo.status !== testInfo.expectedStatus) { await page.screenshot({ path: `failure-${testInfo.title}.png` }); } });
beforeAll/afterAllindescribe.serialshare the same worker; elsewhere each runs in its own worker.
TestInfo object
Available as the second argument to test and hooks:
test('example', async ({ page }, testInfo) => { testInfo.title; // 'example' testInfo.titlePath; // ['describe', 'example'] testInfo.file; // absolute path to spec file testInfo.outputDir; // per-test output directory testInfo.snapshotDir; // snapshot directory testInfo.retry; // 0 = first attempt testInfo.annotations; // [{type, description}] testInfo.attachments; // add via testInfo.attach() await testInfo.attach('screenshot', { body: await page.screenshot(), contentType: 'image/png', }); });
Parameterized tests
const locales = ['en', 'de', 'fr']; for (const locale of locales) { test(`homepage renders in ${locale}`, async ({ page }) => { await page.goto(`/${locale}`); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); }); } // Or with a data table: const CREDENTIALS = [ { user: 'admin', pass: 'secret' }, { user: 'guest', pass: 'guest' }, ]; for (const { user, pass } of CREDENTIALS) { test(`login as ${user}`, async ({ page }) => { await page.getByLabel('Username').fill(user); await page.getByLabel('Password').fill(pass); }); }
Cross-browser coverage is NOT a loop — the
pagefixture's browser is fixed per project. Defineprojectsinplaywright.config.tsinstead (see Configuration).
Timeouts
| Scope | Config key | Default |
|---|---|---|
| Per-test | timeout | 30 000 ms |
| Per-expect assertion | expect.timeout | 5 000 ms |
| Global (all tests) | globalTimeout | none |
test('slow test', async ({ page }) => { test.setTimeout(60_000); // override for this test only // ... });
Retries
// playwright.config.ts export default defineConfig({ retries: 2 });
There is no per-test retries option — configure per file or per describe block:
test.describe(() => { test.describe.configure({ retries: 3 }); test('flaky', async ({ page }) => { /* ... */ }); });
npx playwright test --retries=3 # override from the CLI
Expect soft assertions
// Soft: continue on failure, report all at end await expect.soft(page.locator('h1')).toHaveText('Home'); await expect.soft(page.locator('h2')).toHaveText('Welcome'); // all soft failures collected and surfaced together
expect.poll / expect.toPass
// Poll a custom predicate until it passes await expect.poll(async () => { const res = await page.evaluate(() => window.appReady); return res; }, { timeout: 10_000 }).toBe(true); // Retry an entire block await expect(async () => { const count = await page.locator('.item').count(); expect(count).toBeGreaterThan(3); }).toPass({ timeout: 15_000 });