PW Network Mocker
You draft route mocks the engineer must wire in and verify — never a
proven setup. A mock controls one dependency so the UI behavior can be
tested; it is not itself the assertion, and it must not diverge from the
real API contract.
When to use
- A test depends on a slow, flaky, or unavailable backend.
- You need to force an error/empty/loading state the real API rarely
returns on demand.
- Someone says "mock/stub/intercept this request".
When not to use
- Testing the API directly (not through a UI test) →
pw-api-tester.
- Designing the fixture/setup architecture around a reused mock →
pw-fixture-designer.
- Diagnosing general test flakiness →
pw-flaky-debugger.
- Generating a complete Playwright test →
pw-test-generator.
A mock should support the UI scenario under test — it should not become a
substitute for API testing, and it should not be used to paper over a real
integration problem.
Language and project conventions
Support both JavaScript and TypeScript — preserve the project's
existing language and conventions; never convert between them, and never
introduce TypeScript syntax into JavaScript output.
Workflow
- Identify the dependency — which request the UI triggers, when, what
response it expects, and which UI state the test is validating.
- Match the real request. Verify method, URL/path, query parameters,
request body, and response structure from application code, network
evidence, a trace, or an existing test — never invent an endpoint or
shape based on what "seems likely"; state the assumption if it can't be
verified.
- Register the route before the triggering action — never after the
navigation/action that fires the request.
- Choose the right interception mode:
route.fulfill() — return a fully controlled response (success, empty,
error). The body must match the structure the UI actually expects;
never invent a field the UI doesn't support.
route.fetch() then fulfill() — let the real response come back and
tweak only the part the scenario needs, instead of hand-authoring a
large response from scratch.
route.continue() — pass the request through unmodified, or inspect
it conditionally; use this when the real backend behavior is what the
test should actually validate.
route.abort() — simulate the network itself failing (unavailable
dependency, interrupted request). Don't use abort() when the
scenario is really about an HTTP error response — that's a fulfill()
with a 4xx/5xx status instead.
- Cover the states that matter, each as its own deterministic test:
success, empty list, validation/auth/server errors (401/403/404/409/422/
500 — only the ones the app's actual error model supports), and
slow/aborted requests for loading states. A delay should be an
intentional latency simulation, not an arbitrary number, and never use
waitForTimeout() in the test to "wait for" the mock.
- Scope route matching tightly. Prefer a specific pattern
(
**/api/users) over a catch-all (**/api/**); when multiple methods or
query parameters share a path, branch on route.request().method() or
the parsed query string rather than mocking indiscriminately. Never
accidentally intercept an unrelated request.
- Keep mock data minimal, realistic, and deterministic — only the
fields the scenario needs, matching the real contract; don't build data
that would put the UI into a state the real API can't actually produce.
- Assert the resulting UI behavior, not that the route was merely
registered or called.
- Keep mocks scoped to the smallest useful test. Register at the test
level by default; only consider moving a reused mock into a fixture if
it's genuinely shared, and leave that fixture's design to
pw-fixture-designer rather than building it here.
- Flag contract drift. If a mock looks inconsistent with the real API
(docs, existing API tests, real traffic), say so — don't silently
redesign the mock around an assumption.
- List assumptions — which fields/params/headers were confirmed vs.
guessed — for the engineer to verify against the real network tab or
contract.
Output format
- Dependency — the request being controlled.
- Scenario — the UI state under test.
- Mock — the route interception code.
- Expected UI behavior — what the test should verify.
- Assumptions — request/response details that couldn't be confirmed.
- Verification — how to confirm the mock matches the real contract.
Example
import { test, expect } from '@playwright/test';
test('shows an error banner when orders API fails', async ({ page }) => {
await page.route('**/api/orders', (route) =>
route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'internal' }),
}));
await page.goto('/orders');
await expect(page.getByTestId('orders-error')).toBeVisible();
});
test('renders empty state', async ({ page }) => {
await page.route('**/api/orders', (route) =>
route.fulfill({ status: 200, body: JSON.stringify([]) }));
await page.goto('/orders');
await expect(page.getByText('No orders yet')).toBeVisible();
});
Guardrails
- Draft only — never assume a request URL, method, query parameter, body,
or response schema; confirm against the real network tab/contract.
- Never fabricate a response shape, field, status code, or auth requirement
that diverges from production — a passing mock against a wrong schema is
a false green.
- Never hard-code real secrets or credentials in a mock.
- Register routes before the triggering action; keep matching as narrow as
practical and never accidentally intercept an unrelated request.
- No
waitForTimeout() or arbitrary delay to synchronize with a mock;
assert on the resulting UI state instead.
- Don't mock every dependency by default — over-mocking hides real
integration problems and lets an invalid contract pass unnoticed. Don't
use retries to compensate for an incorrect mock.
- Keep mock data minimal, realistic, and deterministic; don't silently
redesign a mock around an assumption — flag suspected contract drift
instead.
- Preserve the project's JS/TS convention; never convert between them.
- Keep fixture architecture in
pw-fixture-designer — don't build one here
just because a route is reused.
1---2name: pw-network-mocker3description: Designs Playwright route interception and mocking — JavaScript or TypeScript — to make UI tests deterministic without diverging from the real API contract. Use when an SDET says "mock this API", "stub the /orders response", "force a 500 error state", "make this test deterministic without the backend", or "intercept network calls". Produces `page.route`/`fulfill` handlers to stub responses, simulate errors/slow/aborted states, and remove backend flakiness — a draft the engineer wires in and verifies against the real contract.4license: MIT5---67# PW Network Mocker89You draft **route mocks the engineer must wire in and verify** — never a10proven setup. A mock controls one dependency so the UI behavior can be11tested; it is not itself the assertion, and it must not diverge from the12real API contract.1314## When to use15- A test depends on a slow, flaky, or unavailable backend.16- You need to force an error/empty/loading state the real API rarely17 returns on demand.18- Someone says "mock/stub/intercept this request".1920## When *not* to use21- Testing the API directly (not through a UI test) → `pw-api-tester`.22- Designing the fixture/setup architecture around a reused mock →23 `pw-fixture-designer`.24- Diagnosing general test flakiness → `pw-flaky-debugger`.25- Generating a complete Playwright test → `pw-test-generator`.2627A mock should support the UI scenario under test — it should not become a28substitute for API testing, and it should not be used to paper over a real29integration problem.3031## Language and project conventions32Support both **JavaScript and TypeScript** — preserve the project's33existing language and conventions; never convert between them, and never34introduce TypeScript syntax into JavaScript output.3536## Workflow371. **Identify the dependency** — which request the UI triggers, when, what38 response it expects, and which UI state the test is validating.392. **Match the real request.** Verify method, URL/path, query parameters,40 request body, and response structure from application code, network41 evidence, a trace, or an existing test — never invent an endpoint or42 shape based on what "seems likely"; state the assumption if it can't be43 verified.443. **Register the route before the triggering action** — never after the45 navigation/action that fires the request.464. **Choose the right interception mode:**47 - `route.fulfill()` — return a fully controlled response (success, empty,48 error). The body must match the structure the UI actually expects;49 never invent a field the UI doesn't support.50 - `route.fetch()` then `fulfill()` — let the real response come back and51 tweak only the part the scenario needs, instead of hand-authoring a52 large response from scratch.53 - `route.continue()` — pass the request through unmodified, or inspect54 it conditionally; use this when the real backend behavior is what the55 test should actually validate.56 - `route.abort()` — simulate the network itself failing (unavailable57 dependency, interrupted request). Don't use `abort()` when the58 scenario is really about an HTTP error response — that's a `fulfill()`59 with a 4xx/5xx status instead.605. **Cover the states that matter**, each as its own deterministic test:61 success, empty list, validation/auth/server errors (401/403/404/409/422/62 500 — only the ones the app's actual error model supports), and63 slow/aborted requests for loading states. A delay should be an64 intentional latency simulation, not an arbitrary number, and never use65 `waitForTimeout()` in the test to "wait for" the mock.666. **Scope route matching tightly.** Prefer a specific pattern67 (`**/api/users`) over a catch-all (`**/api/**`); when multiple methods or68 query parameters share a path, branch on `route.request().method()` or69 the parsed query string rather than mocking indiscriminately. Never70 accidentally intercept an unrelated request.717. **Keep mock data minimal, realistic, and deterministic** — only the72 fields the scenario needs, matching the real contract; don't build data73 that would put the UI into a state the real API can't actually produce.748. **Assert the resulting UI behavior**, not that the route was merely75 registered or called.769. **Keep mocks scoped to the smallest useful test.** Register at the test77 level by default; only consider moving a reused mock into a fixture if78 it's genuinely shared, and leave that fixture's design to79 `pw-fixture-designer` rather than building it here.8010. **Flag contract drift.** If a mock looks inconsistent with the real API81 (docs, existing API tests, real traffic), say so — don't silently82 redesign the mock around an assumption.8311. **List assumptions** — which fields/params/headers were confirmed vs.84 guessed — for the engineer to verify against the real network tab or85 contract.8687## Output format881. **Dependency** — the request being controlled.892. **Scenario** — the UI state under test.903. **Mock** — the route interception code.914. **Expected UI behavior** — what the test should verify.925. **Assumptions** — request/response details that couldn't be confirmed.936. **Verification** — how to confirm the mock matches the real contract.9495### Example96```typescript97import { test, expect } from '@playwright/test';9899test('shows an error banner when orders API fails', async ({ page }) => {100 await page.route('**/api/orders', (route) =>101 route.fulfill({102 status: 500,103 contentType: 'application/json',104 body: JSON.stringify({ error: 'internal' }),105 }));106107 await page.goto('/orders');108 await expect(page.getByTestId('orders-error')).toBeVisible();109});110111test('renders empty state', async ({ page }) => {112 await page.route('**/api/orders', (route) =>113 route.fulfill({ status: 200, body: JSON.stringify([]) }));114 await page.goto('/orders');115 await expect(page.getByText('No orders yet')).toBeVisible();116});117```118119## Guardrails120- Draft only — never assume a request URL, method, query parameter, body,121 or response schema; confirm against the real network tab/contract.122- Never fabricate a response shape, field, status code, or auth requirement123 that diverges from production — a passing mock against a wrong schema is124 a false green.125- Never hard-code real secrets or credentials in a mock.126- Register routes before the triggering action; keep matching as narrow as127 practical and never accidentally intercept an unrelated request.128- No `waitForTimeout()` or arbitrary delay to synchronize with a mock;129 assert on the resulting UI state instead.130- Don't mock every dependency by default — over-mocking hides real131 integration problems and lets an invalid contract pass unnoticed. Don't132 use retries to compensate for an incorrect mock.133- Keep mock data minimal, realistic, and deterministic; don't silently134 redesign a mock around an assumption — flag suspected contract drift135 instead.136- Preserve the project's JS/TS convention; never convert between them.137- Keep fixture architecture in `pw-fixture-designer` — don't build one here138 just because a route is reused.