Typorithm · medium

9. Network failures, clocks and deterministic recovery

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

Step 1 of 4 · intro card

9. Network failures, clocks and deterministic recovery

0%

Concept and contract

Control one asynchronous condition and assert the resulting user-visible state.

Asynchronous tests need controlled conditions, not arbitrary delays. A browser clock can advance a debounce timer, while a network route can supply a defined response. These tools control different boundaries; a fake page clock does not automatically change server time or complete a real network request.

Install time control before the relevant application code runs. For network interception, define the route before navigation or the triggering action. Keep a real API integration test elsewhere because a mocked response cannot prove the actual server produces that response.

Mechanism and worked example

The worked test advances the search debounce and expects the two QA titles.

The worked test advances the search debounce and expects the two QA titles. The clock change removes an unnecessary real wait, while the web-first assertion still observes the requested UI result. It does not assume that advancing time alone proves the fetch finished correctly.

For recovery, intercept the local catalogue response with 503, assert the visible error, remove the interception and retry through the real local server. This separates failure presentation from the API’s normal success behavior.

Complete worked example

typescript

import { test, expect } from "../support/test.js";
test("debounced search with controlled time", async ({page}) => {
  await page.clock.install();
  await page.goto("/");
  const rows = page.getByRole("list", {name:"Matching roles"}).getByRole("listitem");
  await expect(rows).toHaveCount(4);
  await page.getByLabel("Search roles").fill("qa");
  await page.clock.runFor(350);
  await expect(rows).toHaveCount(2);
  await expect(rows).toContainText(["QA Engineer", "QA Lead"]);
});
Reasoning
Advancing the debounce is paired with an actual result assertion.

Counterexamples and common mistakes

The weak test sleeps and assumes the error or result must now be present.

The weak test sleeps and assumes the error or result must now be present. The guess can race the debounce, fetch and rendering independently. Making the sleep longer conceals timing assumptions rather than giving the test a meaningful readiness condition.

Do not intercept every dependency and call the result end-to-end evidence. Label which boundary is controlled. Likewise, aborting a request does not necessarily undo a server-side effect; write operations need their own idempotency and retry contracts.

Counterexample: what this check misses

typescript

import { test, expect } from "../support/test.js";
test("weak guessed readiness", async ({page}) => {
  await page.goto("/");
  await page.getByLabel("Search roles").fill("qa");
  await page.waitForTimeout(100);
  expect(await page.locator("#roles li").count()).toBe(2);
});
Reasoning
A guessed delay does not establish the requested state.

Independent practice: small task

Write the failure-and-recovery browser test.

Write the failure-and-recovery browser test. Fulfill only the local roles route with a 503 response, observe Could not load roles, then remove the route and click Reload roles. Expect four records after recovery.

Begin with the error observation before adding recovery. Optional extension: deliver two search responses out of order and verify that an older result cannot overwrite the current query. The basic task stays a small deterministic failure path.

Step 1: Observe the intercepted error first. Step 2: Remove interception and retry through the local server.

Practice starter: solve before reading the solution

typescript

import { test, expect } from "../support/test.js";
test("recovers from a controlled API error", async ({page}) => {
  // TODO: intercept before navigation, then remove the route before retry.
});
Reasoning
First make the error deterministic, then restore the real local route.
  • Hint 1: Install the clock before the application timer.
  • Hint 2: Keep a real API check separate from mocked UI behavior.

Solution, tests and trade-offs

The solution places interception before navigation and removes it explicitly before retry.

The solution places interception before navigation and removes it explicitly before retry. The final rows come from the real local API. That makes the recovery observation stronger than replacing both failure and success with unrelated canned UI text.

The verifier runs the clock and recovery examples in actual headless Chrome. Their scope remains the local fixture; they make no assertion about production provider availability or server cancellation semantics.

Practice solution and acceptance checks

typescript

import { test, expect } from "../support/test.js";
test("recovers from a controlled API error", async ({page}) => {
  await page.route("**/api/roles?**", route => route.fulfill({status:503,json:{error:"Unavailable"}}));
  await page.goto("/");
  await expect(page.getByText("Could not load roles", {exact:true})).toBeVisible();
  await page.unroute("**/api/roles?**");
  await page.getByRole("button", {name:"Reload roles",exact:true}).click();
  await expect(page.getByRole("list", {name:"Matching roles"}).getByRole("listitem")).toHaveCount(4);
});
Reasoning
The recovery path returns to the actual local server after the controlled failure.

Interview reasoning and next step

In interviews, distinguish fake time, mocked network responses and observable readiness.

In interviews, distinguish fake time, mocked network responses and observable readiness. Explain what remains real in each test. Show how a bounded assertion can tolerate legitimate timing while still rejecting an incorrect final state.

Next, connect authentication setup with the correct browser cookie jar and verify isolation across separate contexts.

Optional stretch — after the core task works: Test out-of-order responses with an explicit current-request 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.

Control one asynchronous condition and assert the resulting user-visible state.