Playwright Network Mocking & API Testing
End-to-end tests are most valuable when they exercise the real stack, but sometimes you need control over the network: simulating a payment provider's error, testing an empty state without seeding data, freezing a flaky third-party widget, or checking how the UI behaves when an API is slow. Playwright intercepts every request a page makes, and lets tests fulfill them with mock data, modify real responses, abort them, or delay them.
The same toolkit includes HAR recording and replay for deterministic snapshots of backend traffic, and a built-in request fixture for direct API testing, which is also ideal for creating test data and verifying backend state alongside UI tests.
TL;DR
- Intercept with
page.route(pattern, handler)(orcontext.routefor all pages in a context). - In handlers:
route.fulfill()(mock),route.continue()(modify the request),route.fetch()(get the real response, then modify it),route.abort()(fail it). - Simulate errors, empty states, and latency to test resilience UI.
- HAR files record real traffic and replay it with
routeFromHARfor deterministic tests. - Wait for specific calls with
page.waitForResponse/waitForRequest, instead of timeouts. - Use the
requestfixture for API tests and for setting up or verifying data.
Quick Example
Core Concepts
Routing
page.route(url, handler) registers an interceptor for matching requests. URL patterns can be globs (/api/), regexes, or predicate functions. context.route applies to every page in the context, including popups and new tabs. Handlers must resolve each route exactly once:
Remove routes with page.unroute, or register them in fixtures scoped to specific tests.
Simulating Failure Modes
Resilience UI is hard to test against real backends. With routing you can simulate:
- HTTP errors (500, 503, 429 with
Retry-After) and validation errors (422 with field details). - Network failures (
route.abort('internetdisconnected')) and offline mode (context.setOffline(true)). - Slow responses (delay before fulfilling) to verify loading states, skeletons, and timeouts.
- Malformed data, to check that the UI fails gracefully.
Blocking Third Parties
Analytics, chat widgets, ads, and A/B testing scripts slow tests and add flakiness. Block them globally in a fixture (context.route(/googletagmanager|hotjar/, r => r.abort())), or stub them with minimal responses.
HAR Record and Replay
A HAR (HTTP Archive) file captures request and response pairs:
It's useful for frontend tests needing realistic data without a live backend, and for isolating UI changes from backend variability. Refresh HARs when APIs change, since stale HARs silently test outdated contracts.
Waiting on Network Events
Always create the wait promise before the action that triggers the request, to avoid races. page.on('request') and page.on('response') listeners help assert that no unexpected calls happen.
API Testing With request
The request fixture (an APIRequestContext) sends HTTP requests with the config's baseURL, shared auth storage state, and cookies:
- Pure API tests: validate endpoints, status codes, schemas, and auth rules, fast and without a browser. See API testing.
- Hybrid tests: create data via API, verify it in the UI, then check backend state via API.
playwright.request.newContext({ baseURL, extraHTTPHeaders })creates clients with different credentials, for example to test authorization across users.
When to Mock vs Use the Real Backend
A healthy suite does both: a smaller set of true E2E journeys against real services, plus mocked tests covering UI edge cases. Keep mocks aligned with real API schemas, generated from OpenAPI types or validated against contracts. See mocking and stubbing.
Best Practices
Scope Mocks Tightly
Match the specific endpoint and method you intend to mock. Broad patterns (**/*) can accidentally intercept assets, other APIs, or auth calls, causing confusing failures.
Type Mock Payloads
Build mock responses from shared TypeScript types or factories, so API changes break mocks at compile time instead of letting tests pass against outdated shapes.
Assert on Requests Too
When behavior depends on what the UI sends (query parameters, payload shape, headers), inspect route.request() or the result of waitForRequest, and assert on it.
Don't Mock What You're Testing
If the goal is to verify checkout end to end, mocking the order API defeats the purpose. Mock only boundaries outside the test's scope.
Common Mistakes
Registering Routes After Navigation
Register routes before the action that triggers the request.
Starting waitForResponse After the Click
Create the promise first, then click, then await it.
Stale HAR Files
Replaying months-old recordings hides breaking API changes. Regenerate HARs as part of API change workflows, or validate them against current schemas.
FAQ
How do I mock an API response in Playwright?
Use page.route('**/api/endpoint', route => route.fulfill({ status: 200, json: data })) before triggering the request. For partial changes, call route.fetch() to get the real response and fulfill with modified JSON.
Can Playwright be used for API testing without a browser?
Yes. The request fixture (or playwright.request.newContext()) sends HTTP requests directly, with assertions via expect. It's fast and shares configuration and authentication with UI tests.
How do I simulate a slow network?
Delay inside a route handler before fulfilling or continuing (for example await new Promise(r => setTimeout(r, 3000))) to test loading states. For full throttling in Chromium, CDP network emulation is available via context.newCDPSession.
What is HAR replay good for?
Deterministic frontend tests using realistic recorded backend responses, without running or seeding the backend. It's useful for UI-focused suites and for reproducing specific data scenarios. Keep recordings fresh so they match current APIs.
Related Topics
- Playwright — The testing framework overview
- Playwright Fixtures — Packaging mocks as fixtures
- API Testing — Testing APIs directly
- Mocking & Stubbing — When and how to fake dependencies
- Playwright Locators — Asserting on the resulting UI
- Integration Testing — Real vs mocked dependencies