Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
core/assertions-waiting.md
1# Assertions & Waiting23## Table of Contents451. [Web-First Assertions](#web-first-assertions)62. [Generic Assertions](#generic-assertions)73. [Soft Assertions](#soft-assertions)84. [Waiting Strategies](#waiting-strategies)95. [Polling & Retrying](#polling--retrying)106. [Custom Matchers](#custom-matchers)1112## Web-First Assertions1314Auto-retry until condition is met or timeout. Always prefer these over generic assertions.1516### Locator Assertions1718```typescript19import { expect } from "@playwright/test";2021// Visibility22await expect(page.getByRole("button")).toBeVisible();23await expect(page.getByRole("button")).toBeHidden();24await expect(page.getByRole("button")).not.toBeVisible();2526// Enabled/Disabled27await expect(page.getByRole("button")).toBeEnabled();28await expect(page.getByRole("button")).toBeDisabled();2930// Text content31await expect(page.getByRole("heading")).toHaveText("Welcome");32await expect(page.getByRole("heading")).toHaveText(/welcome/i);33await expect(page.getByRole("heading")).toContainText("Welcome");3435// Count36await expect(page.getByRole("listitem")).toHaveCount(5);3738// Attributes39await expect(page.getByRole("link")).toHaveAttribute("href", "/home");40await expect(page.getByRole("img")).toHaveAttribute("alt", /logo/i);4142// CSS43await expect(page.getByRole("button")).toHaveClass(/primary/);44await expect(page.getByRole("button")).toHaveCSS("color", "rgb(0, 0, 255)");4546// Input values47await expect(page.getByLabel("Email")).toHaveValue("[email protected]");48await expect(page.getByLabel("Email")).toBeEmpty();4950// Focus51await expect(page.getByLabel("Email")).toBeFocused();5253// Checked state54await expect(page.getByRole("checkbox")).toBeChecked();55await expect(page.getByRole("checkbox")).not.toBeChecked();5657// Editable state58await expect(page.getByLabel("Name")).toBeEditable();59```6061### Page Assertions6263```typescript64// URL65await expect(page).toHaveURL("/dashboard");66await expect(page).toHaveURL(/\/dashboard/);6768// Title69await expect(page).toHaveTitle("Dashboard - MyApp");70await expect(page).toHaveTitle(/dashboard/i);71```7273### Response Assertions7475```typescript76const response = await page.request.get("/api/users");77await expect(response).toBeOK();78await expect(response).not.toBeOK();79```8081## Generic Assertions8283Use for non-UI values. Do NOT retry - execute immediately.8485```typescript86// Equality87expect(value).toBe(5);88expect(object).toEqual({ name: "Test" });89expect(array).toContain("item");9091// Truthiness92expect(value).toBeTruthy();93expect(value).toBeFalsy();94expect(value).toBeNull();95expect(value).toBeUndefined();96expect(value).toBeDefined();9798// Numbers99expect(value).toBeGreaterThan(5);100expect(value).toBeLessThanOrEqual(10);101expect(value).toBeCloseTo(5.5, 1);102103// Strings104expect(string).toMatch(/pattern/);105expect(string).toContain("substring");106107// Arrays/Objects108expect(array).toHaveLength(3);109expect(object).toHaveProperty("key", "value");110111// Exceptions112expect(() => fn()).toThrow();113expect(() => fn()).toThrow("error message");114await expect(asyncFn()).rejects.toThrow();115```116117## Soft Assertions118119Continue test execution after failure, report all failures at end.120121```typescript122test("check multiple elements", async ({ page }) => {123await page.goto("/dashboard");124125// Won't stop on first failure126await expect.soft(page.getByRole("heading")).toHaveText("Dashboard");127await expect.soft(page.getByRole("button", { name: "Save" })).toBeEnabled();128await expect.soft(page.getByText("Welcome")).toBeVisible();129130// Test continues; all failures reported at end131});132```133134### Soft Assertions with Early Exit135136```typescript137test("check form", async ({ page }) => {138await expect.soft(page.getByRole("form")).toBeVisible();139140// Exit early if form not visible (pointless to check fields)141if (expect.soft.hasFailures()) {142return;143}144145await expect.soft(page.getByLabel("Name")).toBeVisible();146await expect.soft(page.getByLabel("Email")).toBeVisible();147});148```149150## Waiting Strategies151152### Auto-Waiting (Default)153154Actions automatically wait for:155156- Element to be attached to DOM157- Element to be visible158- Element to be stable (no animations)159- Element to be enabled160- Element to receive events161162```typescript163// These auto-wait164await page.click("button");165await page.fill("input", "text");166await page.getByRole("button").click();167```168169### Wait for Navigation170171```typescript172// Wait for URL change173await page.waitForURL("/dashboard");174await page.waitForURL(/\/dashboard/);175176// Wait for navigation after action177await Promise.all([178page.waitForURL("**/dashboard"),179page.click('a[href="/dashboard"]'),180]);181182// Or without Promise.all183const urlPromise = page.waitForURL("**/dashboard");184await page.click("a");185await urlPromise;186```187188### Wait for Network189190```typescript191// Wait for specific response192const responsePromise = page.waitForResponse("**/api/users");193await page.click("button");194const response = await responsePromise;195expect(response.status()).toBe(200);196197// Wait for request198const requestPromise = page.waitForRequest("**/api/submit");199await page.click("button");200const request = await requestPromise;201202// Wait for no network activity203await page.waitForLoadState("networkidle");204```205206### Wait for Element State207208```typescript209// Wait for element to appear210await page.getByRole("dialog").waitFor({ state: "visible" });211212// Wait for element to disappear213await page.getByText("Loading...").waitFor({ state: "hidden" });214215// Wait for element to be attached216await page.getByTestId("result").waitFor({ state: "attached" });217218// Wait for element to be detached219await page.getByTestId("modal").waitFor({ state: "detached" });220```221222### Wait for Function223224```typescript225// Wait for arbitrary condition226await page.waitForFunction(() => {227return document.querySelector(".loaded") !== null;228});229230// With arguments231await page.waitForFunction(232(selector) => document.querySelector(selector)?.textContent === "Ready",233".status",234);235```236237## Polling & Retrying238239### toPass() for Polling240241Retry until block passes or times out:242243```typescript244await expect(async () => {245const response = await page.request.get("/api/status");246expect(response.status()).toBe(200);247248const data = await response.json();249expect(data.ready).toBe(true);250}).toPass({251intervals: [1000, 2000, 5000], // Retry intervals252timeout: 30000,253});254```255256### expect.poll()257258Poll a function until assertion passes:259260```typescript261// Poll API until condition met262await expect263.poll(264async () => {265const response = await page.request.get("/api/job/123");266return (await response.json()).status;267},268{269intervals: [1000, 2000, 5000],270timeout: 30000,271},272)273.toBe("completed");274275// Poll DOM value276await expect.poll(() => page.getByTestId("counter").textContent()).toBe("10");277```278279## Custom Matchers280281```typescript282// playwright.config.ts or fixtures283import { expect } from "@playwright/test";284285expect.extend({286async toHaveDataLoaded(page: Page) {287const locator = page.getByTestId("data-container");288let pass = false;289let message = "";290291try {292await expect(locator).toBeVisible();293await expect(locator).not.toContainText("Loading");294pass = true;295} catch (e) {296message = `Expected data to be loaded but found loading state`;297}298299return { pass, message: () => message };300},301});302303// Extend TypeScript types304declare global {305namespace PlaywrightTest {306interface Matchers<R> {307toHaveDataLoaded(): Promise<R>;308}309}310}311312// Usage313await expect(page).toHaveDataLoaded();314```315316## Timeouts317318### Configure Timeouts319320```typescript321// playwright.config.ts322export default defineConfig({323timeout: 30000, // Test timeout324expect: {325timeout: 5000, // Assertion timeout326},327});328329// Per-test timeout330test("long test", async ({ page }) => {331test.setTimeout(60000);332// ...333});334335// Per-assertion timeout336await expect(page.getByRole("button")).toBeVisible({ timeout: 10000 });337```338339## Best Practices340341| Do | Don't |342| ------------------------------ | ------------------------------ |343| Use web-first assertions | Use generic assertions for DOM |344| Let auto-waiting work | Add unnecessary explicit waits |345| Use `toPass()` for polling | Write manual retry loops |346| Configure appropriate timeouts | Use `waitForTimeout()` |347| Check specific conditions | Wait for arbitrary time |348349## Anti-Patterns to Avoid350351| Anti-Pattern | Problem | Solution |352| --------------------------------------------------------- | ----------------------------- | -------------------------------------------- |353| `await page.waitForTimeout(5000)` | Slow, flaky, arbitrary timing | Use auto-waiting or `waitForResponse` |354| `await new Promise(resolve => setTimeout(resolve, 1000))` | Same as above | Use `waitForResponse` or element state waits |355| Generic assertions on DOM elements | No auto-retry, flaky | Use web-first assertions with `expect()` |356357## Related References358359- **Debugging timeout issues**: See [debugging.md](../debugging/debugging.md) for troubleshooting360- **Fixing flaky tests**: See [debugging.md](../debugging/debugging.md) for race condition solutions361- **Network interception**: See [test-suite-structure.md](test-suite-structure.md) for API mocking362