Advanced Network Interception and Mocking in Playwright Python
- Use
page.routefor fine-grained control over individual HTTP requests based on URL patterns. - Mocking allows you to test frontend components without a working backend, drastically increasing test stability.
- Aborting unnecessary requests (like heavy images or analytics) can reduce test execution time by up to 40%.
- page.route() intercepts HTTP requests before they leave the browser.
- route.fulfill() returns a fake response with custom status, headers, and body.
- route.continue() lets the request proceed, optionally modifying headers or payload.
- route.abort() blocks unwanted requests (trackers, images, fonts).
- Mocking eliminates backend flakiness — tests become deterministic and fast.
- Performance: aborting heavy assets can cut test execution time by up to 40%.
Request not intercepted
page.route('**/api/users', lambda r: r.fulfill(status=200, body='[]'))context.tracing.start(snapshots=True, screenshots=True, sources=True)Mock response causes blank page
page.route('**/api/data', handler) with a print('Intercepted:', route.request.url) for debuggingroute.fulfill(headers={'Content-Type': 'application/json'}, body='{"key":"value"}')Test times out waiting for network idle
page.route('**/*', lambda r: r.continue_()) to see all requests being madepage.goto(url, wait_until='domcontentloaded') instead of 'networkidle'Production Incident
Production Debug GuideWhen your mock isn't working or requests aren't being intercepted
r.abort()) before any navigation.context.route() to apply a route handler to all pages in the same browser context.Testing a modern frontend often feels like a hostage situation — you are at the mercy of the backend's availability and speed. Playwright's network interception capabilities break this dependency.
By leveraging the route API, you can turn your browser automation into a powerful mocking engine. Whether you need to simulate a 500 Internal Server Error, mock a slow 3G connection for performance testing, or entirely replace a JSON response to test a specific UI state, Playwright handles it natively via the DevTools protocol. This guide moves beyond basic automation into advanced network manipulation strategies for production-grade test suites.
The Core Concept — page.route()
In Selenium, mocking network traffic usually required a separate proxy like BrowserMob. In Playwright, you simply define a routing rule. When the browser makes a request that matches your pattern (URL or Glob), Playwright intercepts it and gives you control over the route object.
route.fulfill(): Provide a mock response (status, headers, body).route.continue(): Let the request proceed to the internet (possibly with modifications).route.abort(): Kill the request (useful for blocking ads or analytics).
from playwright.sync_api import sync_playwright def run(playwright): browser = playwright.chromium.launch() page = browser.new_page() # Intercept all calls to the user profile API page.route("**/api/v1/user/profile", lambda route: route.fulfill( status=200, content_type="application/json", body='{"id": 1, "username": "forge_admin", "role": "superuser"}' )) page.goto("https://example.com/dashboard") # The UI now sees 'forge_admin' regardless of the actual database state browser.close() with sync_playwright() as p: run(p)
- Patterns are checked in order of registration
- First match wins, so define specific routes before generic fallbacks
- You can chain multiple handlers by calling
route.fallback()in advanced scenarios
page.goto().route.fulfill() with a realistic JSON body.route.continue() with modified headers/body.route.abort() — no response sent.Simulating Edge Cases: API Failures and Timeouts
One of the hardest things to test is how your UI behaves when the server dies. Using Playwright, you can force a 500 error or even a total connection failure to ensure your error handling actually works.
def test_error_handling(page): # Simulate a server crash for the login endpoint page.route("**/api/login", lambda route: route.fulfill( status=500, body="Internal Server Error" )) page.goto("https://example.com/login") page.get_by_role("button", name="Sign In").click() # Verify the UI shows the correct error message expect(page.get_by_text("Server is currently unavailable")).to_be_visible()
route.abort() simulates a dropped connection.route.fulfill() with the appropriate status code.route.fulfill() with a very long delay or call route.abort() after a timeout.route.fulfill() with an empty body or invalid JSON.Advanced: Modifying Outgoing Requests
Sometimes you don't want to mock the whole response, but you need to inject a specific header (like an Auth token) or modify a payload before it leaves the browser. Use for this.route.continue()
def test_header_injection(page): def handle_route(route): headers = route.request.headers headers["X-Test-Source"] = "Automated-Test" # Continue the request with the new header route.continue_(headers=headers) page.route("**/*", handle_route) page.goto("https://example.com")
route.continue_() works, but some headers (Content-Length, Host) are read-only. Playwright will warn if you try to override them.Performance Testing: Mocking Slow Networks
Playwright's browser_context lets you simulate network conditions like offline mode or specific throughput limits — useful for testing how your app degrades gracefully.
async def test_slow_network(browser): # Create a context that simulates an offline device context = await browser.new_context(offline=True) page = await context.new_page() try: await page.goto("https://example.com") except: print("Successfully verified offline behavior")
Asserting Network Activity: Testing That Requests Were Made
Sometimes you don't need to mock a request — you just need to verify that a specific API call was made with certain parameters. Playwright provides and page.wait_for_request() for this. These are critical for confirming analytics events, logging calls, or form submissions actually fired correctly.page.wait_for_response()
You can combine these with route interception to validate both the outgoing request and the incoming response.
# io.thecodeforge.playwright.network.assertions from playwright.sync_api import expect def test_analytics_event_fired(page): page.route("**/api/analytics", lambda route: route.continue_()) # allow through with page.expect_request("**/api/analytics") as request_info: page.get_by_role("button", name="Submit").click() request = request_info.value assert request.method == "POST" assert "/api/analytics" in request.url payload = request.post_data_json assert payload["event"] == "form_submit"
page.wait_for_request() for most test scenarios.route.abort() is too aggressive, the request never reaches the mock handler.page.expect_request() to capture the outgoing request.page.expect_response() to capture the response (mocked or real).| Strategy | Best For | Complexity |
|---|---|---|
| route.abort() | Blocking ads, tracking, or images to speed up tests | Low |
| route.fulfill() | Testing UI states with specific API data | Medium |
| route.continue() | Modifying headers or simulating specific query params | Medium |
| Request Monitoring (expect_request) | Verifying that an analytics event was actually sent | High |
| Combined routes (fulfill + abort) | Full control over mock data and performance simultaneously | High |
🎯 Key Takeaways
- Use
page.routefor fine-grained control over individual HTTP requests based on URL patterns. - Mocking allows you to test frontend components without a working backend, drastically increasing test stability.
- Aborting unnecessary requests (like heavy images or analytics) can reduce test execution time by up to 40%.
- Always prefer fulfilling with a real JSON structure rather than an empty string to avoid unexpected JavaScript 'undefined' errors.
- Assert outgoing requests with
expect_requestto verify that the right data was sent. - Test at least three failure modes: HTTP error, timeout, and no response.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QHow does Playwright's network interception differ from using a library like 'Responses' or 'HTTPretty' in Python?SeniorReveal
- QYour test suite is slow because it loads heavy 4K hero images. How would you solve this using Playwright routes?Mid-levelReveal
- QHow do you verify that an application sent the correct JSON payload to a /log endpoint without actually writing to the database?Mid-levelReveal
- QExplain the difference between
route.fulfillandroute.continue. In what real-world scenario would you use both together?SeniorReveal - QHow would you simulate a Gateway Timeout (504) for a specific backend service?SeniorReveal
Frequently Asked Questions
Can I mock WebSocket connections with Playwright?
Currently, Playwright's route API is designed for HTTP/HTTPS. While you can monitor WebSockets via page.on('websocket'), full interception and mocking of WebSocket frames require low-level CDP (Chrome DevTools Protocol) commands. For most end-to-end tests, you can mock the initial HTTP upgrade handshake and then leave the WebSocket flow as-is.
How do I mock a specific API but let all others pass through?
Playwright is 'first match wins'. You define a specific route for your API (e.g., **/api/v1/target) and don't define routes for others. All unrouted requests will proceed to the network naturally. If you also have a broad route (like for analytics), make sure the specific API route is registered first.
Will mocked responses show up in the Playwright Trace Viewer?
Yes. The Trace Viewer records mock responses, including status, headers, and body. This makes it easy to debug exactly what data the UI received during a test failure. You can open the trace with playwright show-trace trace.zip and inspect each network request.
Can I use regex patterns with route()?
Yes, page.route() accepts a string glob or a compiled regex object. Example: page.route(re.compile(r'https?://api\.example\.com/v2/.*'), handler). Regex gives you more precision for complex patterns.
How do I mock a response for a specific HTTP method (GET vs POST)?
Inside your route handler, check route.request.method. You can conditionally call fulfill, continue, or abort based on method. Example: if route.request.method == 'POST': route.fulfill(...) else: route.continue_().
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.