/* Best Practice Hierarchy */
getByRole('button', { name: 'Submit' })
getByLabel('Email')
getByText('Confirm Order')
/* Fallback */
locator('[data-testid="submit-btn"]')
/* Avoid when possible */
locator('.btn.primary')
locator('//div[3]/button[2]')
/* Add 3 days */
const future = new Date();
future.setDate(future.getDate() + 3);
/* Round to nearest 15 minutes */
let minutes = future.getMinutes();
let rounded = Math.round(minutes / 15) * 15;
if (rounded === 60) {
rounded = 0;
future.setHours(future.getHours() + 1);
}
/* Handle midnight rollover */
if (future.getHours() === 0 && rounded === 0) {
future.setDate(future.getDate() + 1);
}
/* Format (MM/DD/YYYY) */
const date = future.toLocaleDateString('en-US');
/* Format time */
const time = `${future.getHours()}:${String(rounded).padStart(2,'0')}`;
/* Wait for element */
await expect(locator).toBeVisible();
/* Wait for loading spinner to disappear */
await expect(loadBox).not.toBeVisible();
/* Wait for navigation */
await page.waitForURL('**/orders');
/* Wait for API response */
await page.waitForResponse(resp =>
resp.url().includes('/api/orders') && resp.status() === 200
);
/* Avoid this */
await page.waitForTimeout(2000); // ❌ flaky
npm init playwright@latest — what it creates and whytest(), page.goto(), expect()npx playwright test, --ui flag, --headednpx playwright show-report--ui and --headed?getByRole(), getByText(), getByLabel(),
getByPlaceholder(), getByTestId()
page.locator('.card').getByRole('button')getByTestId over getByRole?click(), fill(), type(), press(),
selectOption()
check() / uncheck() for checkboxes and radioshover(), focus(), blur()dragAndDrop() and mouse eventspage.keyboard and page.mouse for low-level controlsetInputFiles()fill() and type()?toBeVisible(), toHaveText(), toHaveValue(),
toBeEnabled(), toBeChecked()
toHaveURL(), toHaveTitle()toHaveCount()toHaveAttribute(), toHaveClass()expect.soft().notexpect.soft() instead of a normal assertion?toHaveText() and toContainText()?test.describe() for grouping related teststest.beforeEach(), test.afterEach(), test.beforeAll(),
test.afterAll()
page, context, browser, request
test.extend()test.use() for per-suite configuration overridesbeforeAll vs beforeEach?loggedInPage fixture from memory.Page instancepage.route() to intercept and fulfill requestsroute.fulfill()route.continue()route.abort()page.waitForResponse()request fixture (no browser needed)route.fulfill() and route.continue()?page.context().storageState() for saving sessionstorageState in playwright.config.tswaitForTimeout(). Learn to diagnose and fix flaky tests systematically.
page.waitForURL(), page.waitForLoadState()locator.waitFor() for explicit element waitingpage.waitForFunction() for custom conditionswaitForTimeout() causes flakes — and alternativestest.retries — when appropriate--trace on to debug flakesawait page.waitForTimeout(2000)?PWDEBUG=1 — step through tests livescreenshot: 'only-on-failure'page.pause() for breakpoint-style debugging--ui): live reload and trace viewer built inexpect(page).toHaveScreenshot() — full page snapshotsexpect(locator).toHaveScreenshot() — component-level--update-snapshotsmask optionrequest fixture for API-only testsrequest.get/post/put/delete()playwright.request.newContext() for standalone API contextsfullyParallel, workers config options--shard=1/4 to split across machines--grep, test.tagframeLocator() — the modern approachpage.waitForEvent('popup')page.on('dialog', ...)page.waitForEvent('download')locator.shadowRoot() vs CSS piercingnpx playwright install --with-deps.env locally vs CI secrets--with-deps in CI but not locally?test.extend<{}>()Partial<>, Pick<> for test data buildersUserData interface with 5 fields for a test data builder.process.env.X directly?@axe-core/playwrightcheckA11y() — configuring rules, disabling false positivespage.evaluate(() => performance.timing)checkA11y actually check — and what does it NOT check?test.info().attach() — adding custom artifacts to reports@playwright/experimental-ct-reactmount() fixture