Typorithm · medium

7. Auto-waiting, assertions and readiness

qatestingplaywrightindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

7. Auto-waiting, assertions and readiness

0%

Concept and contract

Wait for the required observable state instead of guessing a delay.

Browser actions and assertions happen while the page changes. Playwright waits for actionability conditions for supported actions and retries web-first assertions until their condition or timeout. This reduces timing guesses, but it does not know every application-specific readiness rule automatically.

Choose the state that matters to the user: the expected rows, an enabled action or a visible error. Waiting for an arbitrary duration does not establish that state and can be both too short on a slow machine and wasteful on a fast one.

Mechanism and worked example

The example searches for an impossible title and waits for No matching roles.

The example searches for an impossible title and waits for No matching roles. It also verifies the matching list is empty. These assertions express the desired outcome after the debounced request rather than sleeping for a guessed number of milliseconds.

Do not turn a retrying locator assertion into a one-time value read accidentally. expect(locator).toHaveText can wait; comparing an immediately read string may capture an intermediate value. Understand which operation supplies the waiting behavior.

Complete worked example

typescript

import { test, expect } from "../support/test.js";
test("empty query result is observable", async ({page}) => {
  await page.goto("/");
  await page.getByLabel("Search roles").fill("no-such-role");
  await expect(page.getByText("No matching roles", {exact:true})).toBeVisible();
  await expect(page.getByRole("list", {name:"Matching roles"}).getByRole("listitem")).toHaveCount(0);
});
Reasoning
The test waits for the meaningful empty outcome, not a guessed duration.

Counterexamples and common mistakes

The weak test sleeps briefly and checks an immediate count.

The weak test sleeps briefly and checks an immediate count. Its success depends on the machine and network finishing within that guess. Increasing the sleep repeatedly can hide the problem while making the suite slower and still unreliable.

Avoid networkidle as a universal readiness definition. Polling, analytics or other background activity may never become idle, while the useful UI is already ready. A specific visible state is usually a clearer contract.

Counterexample: what this check misses

typescript

import { test, expect } from "../support/test.js";
test("weak guessed delay", async ({page}) => {
  await page.goto("/");
  await page.waitForTimeout(50);
  expect(await page.locator("#roles li").count()).toBe(4);
});
Reasoning
A one-time count after a sleep may observe a transient state.

Independent practice: small task

Write a test for an empty UA query that eventually displays two matching roles.

Write a test for an empty UA query that eventually displays two matching roles. Start by selecting the country and asserting the expected count. Add a title assertion only after the count check is understandable.

Use no fixed sleep. Optional extension: delay the local API response with a route fixture and show that the same observable assertion still works. Keep the timeout bounded rather than extending it until every failure becomes invisible.

Step 1: Select UA without a sleep. Step 2: Wait for the two expected titles in the matching list.

Practice starter: solve before reading the solution

typescript

import { test, expect } from "../support/test.js";
test("UA readiness", async ({page}) => {
  await page.goto("/");
  // TODO: select UA and wait for its two visible records.
});
Reasoning
Assert the two-record UA state without introducing a fixed delay.
  • Hint 1: Know which matcher retries.
  • Hint 2: Timeouts bound waiting; they do not repair wrong results.

Solution, tests and trade-offs

The solution uses toHaveCount(2) and verifies both expected UA titles.

The solution uses toHaveCount(2) and verifies both expected UA titles. Auto-waiting retries the observation while the page updates. It does not turn a wrong final result into success; the assertion still fails when the contract is not reached.

A deliberate delayed-response test can validate the waiting strategy. A controlled wrong result should still fail. These two cases separate tolerance of legitimate timing from tolerance of incorrect behavior.

Practice solution and acceptance checks

typescript

import { test, expect } from "../support/test.js";
test("UA readiness", async ({page}) => {
  await page.goto("/");
  await page.getByLabel("Country", {exact:true}).selectOption("UA");
  const rows = page.getByRole("list", {name:"Matching roles"}).getByRole("listitem");
  await expect(rows).toHaveCount(2);
  await expect(rows).toContainText(["QA Engineer", "Backend Engineer"]);
});
Reasoning
The assertions retry legitimate updates but retain a bounded failure for incorrect results.

Interview reasoning and next step

In interviews, explain actionability versus application readiness and identify which assertion retries.

In interviews, explain actionability versus application readiness and identify which assertion retries. Give a counterexample to arbitrary sleeps or universal network idle. Discuss timeouts as failure bounds rather than proof that an operation was canceled.

Next, apply those visible-state checks to forms and keyboard interaction, where typed event handlers do not establish valid user input.

Optional stretch — after the core task works: Delay the local response and retain the same visible oracle.

Computer practice: download the companion lab at https://applydjinn.com/typorithm/qa-lab-20261105.zip. Read its README, install the pinned tools and run the accepted examples before attempting a starter. The mobile reader preserves your reading and self-check progress; the local lab runs browser tests separately.

Wait for the required observable state instead of guessing a delay.