Files
magnus919_agent-skills/playwright/references/03-network-interception-and-mocking.md
T
Magnus HedemarkGitHubfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
ac1beb117d feat(skill): add Playwright skill (E2E testing + scraping + headless browsing) (#264)
Add ONE tool skill for Playwright: SKILL.md covering E2E test authoring,
selector robustness, network interception/mocking, parallel workers, CI
integration, scraping/headless patterns, accessibility snapshot checks, and
headed debugging; scripts/pwrun (agent-first smoke harness with --json,
fixture-tested); templates/ test-suite scaffold; eight dated references; a
schema-valid evals/evals.json (6 cases); a human-facing README; reverse
routing from qa-methodology and frontend-engineering; top-level README index
entry; and regenerated catalogs (llms.txt, marketplace, codex).

Closes #244.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-03 17:59:38 -04:00

88 lines
3.1 KiB
Markdown

# Network Interception and Mocking
> **Last Updated:** 2026-08-03
Intercept browser traffic with `page.route()` to make E2E tests hermetic,
deterministic, and fast — and to assert on what the app actually sent.
## The route API
```ts
// Fulfill: answer an intercepted request with canned data.
await page.route('**/api/payments/tokenize', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ token: 'tok_test_123' }),
}),
);
// Abort: block a request entirely (trackers, analytics, heavy media).
await page.route('**/analytics/*', (route) => route.abort());
// Continue: let the real request through (with optional overrides).
await page.route('**/api/feature-flags', (route) =>
route.continue({ headers: { ...route.request().headers(), 'x-experiment': 'on' } }),
);
```
Guidance:
- **Mock at the boundary.** The app under test is real; the world outside it
(payment providers, third-party APIs, message queues) is faked. Never patch
in-page code (`window.fetch = ...`) — that tests a tampered app.
- **Realistic bodies win.** Mock payloads that match the real schema and
content type; a mock that differs from production can pass a test the app
would fail.
- **Glob `**` patterns, not exact URLs** — hosts, query strings, and CDN
prefixes change; `**/api/orders` covers `https://api.example.com/api/orders`.
- **Register routes before navigation** — routes apply to requests made after
registration, so set them up in `test.beforeEach` before `page.goto`.
## What NOT to mock
- The server under test: if the suite verifies the app + its backend
integration, intercepting that API makes the test meaningless. Mock only
*third-party* boundaries, or test the real API via a controlled
environment.
- `page.goto()` navigation that the test depends on — routing over the app's
own document requests breaks the flow the test is verifying.
- Responses whose timing you are testing (e.g., loading states): use
`route.fulfill` with a small artificial delay or the API's
`page.clock`/routing delay, never a fixed `waitForTimeout`.
## Asserting on traffic
Capture what the app sent, then assert on it:
```ts
let tokenizeBody: string | undefined;
page.on('request', (request) => {
if (request.url().includes('/api/payments/tokenize')) tokenizeBody = request.postData();
});
// ... drive the flow ...
expect(JSON.parse(tokenizeBody!)).toMatchObject({ amount: 4990, currency: 'usd' });
```
Use `page.on('response')` to wait for a specific status instead of guessing
when the network settled:
```ts
await page.waitForResponse((response) => response.url().includes('/api/orders') && response.status() === 201);
```
## Emulating network conditions
```ts
await page.route('**/*', (route) =>
route.continue().catch(() => {})
);
await page.context().setOffline(true); // test offline/error states
// or throttle via context options: offline, latency, downloadThroughput...
```
## Related
- Locators that survive the mocked world: `02-selectors.md`.
- Running these tests in parallel without interference: `04-parallel-workers-and-sharding.md`.