17. Network Interception & Mocking
Playwright with Python · 244 pages source format
page.route() basics page.route() intercepts network requests matching a URL pattern before they reach the server, letting you inspect, modify, block, or fully replace the response. def handle_route(route): route.continue_() # let it through unchanged page.route("**/*.png", handle_route) page.route(url_pattern, handler) What it does: Registers an interceptor for any request matching a URL pattern.
What you'll learn
- Mocking API responses
- 500 for error-state testing
- Blocking resources (images, ads) for speed
Overview
the server, letting you inspect, modify, block, or fully replace the response.
page.route() basicsInteractive study board
page.route(url_pattern, handler)
What it does: Registers an interceptor for any request matching a URL pattern.
Types/params:
Pointers: Every matched request must be resolved by the handler (continue/fulfill/abort) or the request hangs indefinitely. Register routes before the navigation/action that triggers the request.
- url_pattern (string or regex, required)
- Glob string, e.g. "**/api/users", "**/*.png" — ** matches any path segment
- Regex, e.g. re.compile(r".*/api/.*") for more complex matching
- handler (function, required) — receives a route object; must call exactly one of
route.continue_(), route.fulfill(), or route.abort()Interactive study board
Mocking API responses
This lets you test UI behavior for scenarios that are hard to trigger naturally — server errors, empty states, slow responses — without needing the actual backend to cooperate.
What it does: Responds to the intercepted request with custom data instead of letting it reach the real server.
Types/params:
def mock_users_api(route):
route.fulfill(
status=200,
content_type="application/json",
body='{"users": [{"id": 1, "name": "Test User"}]}'
)Interactive study board
500 for error-state testing
Pointers: Use json= instead of manually building a body JSON string where possible
— less error-prone. Mocking error statuses (500, 403) is one of the highest-value uses here, since these are notoriously hard to trigger from a real backend on demand.
What it does: Lets the request proceed to the real server, optionally with modifications.
Types/params:
Pointers: Use this when you only want to observe or slightly tweak a request (e.g., inject a test header) rather than fully replace the response.
What it does: Blocks the request entirely, simulating a network failure.
Types/params:
"timedout", "connectionrefused"
Pointers: Useful for testing how the UI handles total network failure (not just an error response, but no response at all) — a distinct code path from a mocked 500.
- content_type (string, optional) — MIME type, e.g. "application/json", "text/html"
- body (string, optional) — the response body content, typically a JSON string for API mocks
- json (dict, optional alternative to body) — pass a Python dict directly and Playwright serializes it to JSON automatically
- headers (dict, optional) — override or add request headers
- method (string, optional) — override the HTTP method
- post_data (string, optional) — override the request body
- error_code (string, optional, default "failed") — e.g. "failed",
route.abort(error_code=...)Interactive study board
Blocking resources (images, ads) for speed
def block_images(route):
if route.request.resource_type == "image":
route.abort()
else:
route.continue_()Interactive study board
route.request.resource_type
What it does: Read-only property identifying the category of the intercepted request.
Types/params:
"xhr", "fetch", "font", "media"
Pointers: Blocking images/fonts/ad-tracker scripts on tests that don't need to visually verify them can meaningfully speed up a large suite — but don't block resources your test actually depends on rendering correctly (defeats visual regression testing in Chapter 19).