Senior 5 min · March 15, 2026

Playwright Network Mocking — Mock Response Shape Mismatch

Frontend renders no data; console shows 'undefined' errors because mock response keys don't match TypeScript interfaces.

N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • 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%.
✦ Definition~90s read
What is Advanced Network Interception and Mocking in Playwright Python?

Playwright's network mocking lets you intercept and control HTTP traffic in your browser tests without touching your backend. Instead of relying on a live API, you use page.route() to catch outgoing requests and return custom responses — JSON, status codes, headers, or even network errors.

Think of network mocking like a stunt double for your backend.

This is essential for isolating frontend behavior from backend flakiness, especially in CI where real APIs may be unavailable, rate-limited, or slow. The core idea: you define a URL pattern, and Playwright's built-in proxy intercepts matching requests before they leave the browser, giving you full control over what the application receives.

Where this gets tricky — and where most engineers hit the 'mock response shape mismatch' — is that your mock must exactly mirror the data structure your frontend expects. If your app parses data.items but your mock returns data.products, you'll get silent failures or cryptic errors that are hard to debug.

Playwright doesn't validate shape; it just returns what you give it. This contrasts with tools like MSW (Mock Service Worker) which run in the service worker layer and can share type definitions with your API, or WireMock which runs as a separate server.

Playwright's approach is simpler and faster for browser-only tests, but it shifts the burden of shape correctness entirely onto you.

Use Playwright mocking when you need to test UI behavior under controlled conditions — loading states, empty states, error boundaries, or slow networks. Don't use it for integration tests that need to verify end-to-end data flow; for that, use real APIs or a dedicated mock server.

The key insight: mocking in Playwright is about controlling the browser's network layer, not about simulating your backend's business logic. When you get the response shape wrong, you're not testing your app — you're testing a fake version of it.

Plain-English First

Think of network mocking like a stunt double for your backend. Instead of your browser talking to the real server, Playwright replaces the conversation with a pretend server that returns exactly what you want. This lets you test your UI in any scenario — success, failure, slow network — without waiting for the real server to be ready.

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.

How Playwright Network Mocking Actually Works

Playwright network mocking lets you intercept and override HTTP responses at the browser level, without touching your backend. The core mechanic is page.route() — you define a URL pattern and provide a custom response body, status, or headers. Playwright's Python API gives you full control over request interception, blocking, or modification before the browser processes the response.

What matters in practice: Playwright mocks at the network layer, not the application layer. When you mock an API call, the browser receives a synthetic response exactly as if the server sent it. This means your frontend code runs unchanged — no test doubles, no environment variables. You can simulate any HTTP status, delay, or malformed payload. The key property is that the mock response must match the shape your frontend expects; a mismatch causes silent failures or cryptic errors in your application logic.

Use this when you need deterministic, fast tests that isolate frontend behavior from backend availability, latency, or data variability. It's essential for testing error states, edge cases (empty arrays, missing fields), and race conditions. In real systems, teams rely on it to validate UI rendering without spinning up staging environments, cutting test execution time from minutes to seconds.

Shape Mismatch Is Silent
A mock that returns a string where an object is expected won't throw a network error — your frontend will fail later with a confusing TypeError.
Production Insight
A team mocked a /users endpoint with a flat list but the real API returned nested objects — the UI rendered empty tables for weeks.
Symptom: no 4xx/5xx errors, just blank pages and console warnings about undefined properties.
Rule: always validate mock response shape against the actual API schema before writing tests.
Key Takeaway
Mock at the network layer, not the application layer — your frontend code stays unchanged.
A shape mismatch between mock and real API causes silent failures that are hard to debug.
Use route interception for deterministic tests, but always verify the response structure matches production.
Playwright Network Mocking Flow THECODEFORGE.IO Playwright Network Mocking Flow From route interception to edge case simulation and testing page.route() Setup Intercept matching requests before they reach network Mock Response Shape Define status, headers, and body for fake response Simulate Failures Return 5xx, timeouts, or abort requests Modify Outgoing Requests Alter headers, payload, or URL before forwarding Assert Network Activity Verify request count, URL, and payload via route ⚠ Mock shape mismatch breaks tests silently Always validate mock response matches expected schema THECODEFORGE.IO
thecodeforge.io
Playwright Network Mocking Flow
Playwright Python Network Mocking

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.

You have three main options once a request is intercepted
  • 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).
ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)
Output
API request intercepted and fulfilled with mock data.
Middleware Chain Analogy
  • 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
Production Insight
Engineers often register routes after navigation — they never fire.
Register all routes before page.goto().
Rule: routes are lazy — they intercept the next matching request, not past ones.
Key Takeaway
page.route() is the foundation.
Master fulfill, continue, abort — each serves a distinct purpose.
Pattern ordering matters: specific before generic.
Which Route Action Should You Use?
IfYou need to return fake data for a specific API call
UseUse route.fulfill() with a realistic JSON body.
IfYou want to add a header or modify payload without blocking
UseUse route.continue() with modified headers/body.
IfYou want to block analytics, images, or ads
UseUse 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.

ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
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()
Output
UI error state verified successfully.
Don't Forget Timeouts
When mocking failures, ensure your UI doesn't hang forever on a pending request. Use page.set_default_timeout(5000) to fail fast if the response doesn't come.
Production Insight
Mocking a 500 is easy, but intermittent failures (slow 504, malformed 200) are harder.
Engineers often forget to test the 'no response' scenario — route.abort() simulates a dropped connection.
Rule: cover at least three states: success, error, and no response.
Key Takeaway
Test errors, timeouts, and no-response scenarios.
route.abort() simulates dropped connections.
Cover all three failure modes for robust error handling.
What Failure State Are You Testing?
IfServer returns a specific HTTP error (500, 403)
UseUse route.fulfill() with the appropriate status code.
IfServer never responds (timeout)
UseUse route.fulfill() with a very long delay or call route.abort() after a timeout.
IfServer returns malformed or empty response body
UseUse 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 route.continue() for this.

ExamplePYTHON
1
2
3
4
5
6
7
8
9
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")
Output
Headers modified for all outgoing requests.
Mutable vs Immutable Headers
route.request.headers is a dict-like object. Modifying it and passing to route.continue_() works, but some headers (Content-Length, Host) are read-only. Playwright will warn if you try to override them.
Production Insight
Modifying headers is powerful, but beware of CORS preflight — modified headers may cause the browser to send an OPTIONS request.
If the mock doesn't handle OPTIONS, the actual request fails.
Rule: add a catch-all route for OPTIONS requests when modifying headers.
Key Takeaway
route.continue() modifies requests without mocking responses.
Watch for CORS preflight side effects.
Use this to inject auth tokens or test-specific headers.

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.

ExamplePYTHON
1
2
3
4
5
6
7
8
9
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")
Output
Successfully verified offline behavior
Network Throttling
Use browser_context.new_context(extra_http_headers={...}) to simulate specific network conditions. For realistic slow 3G, combine route interception with artificial delays via route.fulfill with a sleep.
Production Insight
Offline mode only tests complete disconnect — it doesn't simulate slow but working connections.
For slow networks, use a custom route handler that adds a delay: asyncio.sleep(2) before fulfill.
Rule: test both offline (abort) and slow (delay) to cover user experience fully.
Key Takeaway
Network simulation covers offline and slow states.
route.fulfill with artificial delay mimics slow networks.
Combine with route.abort for complete offline testing.

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 page.wait_for_request() and page.wait_for_response() for this. These are critical for confirming analytics events, logging calls, or form submissions actually fired correctly.

You can combine these with route interception to validate both the outgoing request and the incoming response.

ExamplePYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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"
Output
Analytics request verified successfully.
expect_request vs wait_for_request
page.expect_request() is a context manager that waits for the request to be made and captures it. It's cleaner than page.wait_for_request() for most test scenarios.
Production Insight
Engineers often assert the mock response but forget to verify the request was actually made.
If a route.abort() is too aggressive, the request never reaches the mock handler.
Rule: always use expect_request in a with block when testing request-driven functionality.
Key Takeaway
Mocking is not enough — verify requests were actually sent.
Use expect_request for clean assertion of outgoing calls.
Combine with explicit route patterns to avoid false positives.
When to Assert Network Activity
IfYou need to verify an API call was made with correct payload
UseUse page.expect_request() to capture the outgoing request.
IfYou need to verify the response data was received
UseUse page.expect_response() to capture the response (mocked or real).
IfYou want to ensure no extra requests were made
UseUse page.on('request', handler) to count requests and assert no unexpected ones.

The Python Mock Library: Why You’re Already Using It Wrong

Mocking isn’t about faking — it’s about controlling the uncontrollable. Your payment gateway, your weather API, your auth provider — any external service that can fail at 3 AM on a Sunday. The Python mock library gives you surgical control over those dependencies so your tests don’t explode when Stripe has an outage.

The standard library’s unittest.mock provides two core classes: Mock and MagicMock. The difference? MagicMock pre-implements magic methods like __len__ and __iter__. Use Mock for plain objects, MagicMock when you need dunder methods. That’s it. Don’t overthink it.

The real power comes from patch(). It’s your scalpel for temporarily replacing real objects with mocks. Use it as a decorator for whole functions, or as a context manager when you need surgical precision. Each gives you a fresh mock that won’t leak between tests.

PaymentGatewayMock.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — python tutorial

from unittest.mock import patch, MagicMock
from payments import process_checkout

def test_checkout_retries_on_timeout():
    # Patch the external payment client
    with patch('payments.PaymentClient') as mock_client:
        mock_instance = MagicMock()
        mock_client.return_value = mock_instance
        
        # Simulate timeout on first call, success on retry
        mock_instance.charge.side_effect = [
            TimeoutError("Gateway timeout"),
            {"status": "success", "id": "txn_789"}
        ]
        
        result = process_checkout(user_id=42, amount=29.99)
        
        assert result["status"] == "success"
        assert mock_instance.charge.call_count == 2
Output
✓ Test passes — retry logic verified without touching real payments
Production Trap:
Never patch what you don't own. Patching requests.get globally will break every other test running in your suite. Patch at the module boundary — your own code that wraps the external call.
Key Takeaway
Patch at the call site, not the library. Mock the thing your code imports, not the library internals.

Managing Side Effects: Realistic Failure Simulation Without the Pain

A mock with a static return value is a toy. A mock with side effects is a weapon. The side_effect attribute is how you simulate real-world chaos — timeouts, rate limits, intermittent failures, and conditional responses.

Pass an iterable to side_effect and each call pops the next value. Pass a callable and get dynamic behavior based on input arguments. Raise an exception when you need to test your error handling path. This is how you prove your circuit breakers actually break, your retries actually retry, and your logging actually logs.

The alternative — hoping your staging environment reproduces these scenarios — is how production incidents get shipped. You don’t need a chaos monkey. You need side_effect.

Pro tip: chain exceptions and return values in a single list to simulate flaky services. First call raises, second returns success. Your code handles both, or you find the bug before it finds you.

SideEffectSimulation.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// io.thecodeforge — python tutorial

from unittest.mock import Mock

# Simulate a flaky rate-limited API
api_mock = Mock()
api_mock.fetch_data.side_effect = [
    Exception("429 Too Many Requests"),
    {"results": ["item1", "item2"]},
    {"results": ["item3"]}
]

def get_with_retry(api_client, max_retries=3):
    for attempt in range(max_retries):
        try:
            return api_client.fetch_data()
        except Exception as e:
            print(f"Attempt {attempt + 1} failed: {e}")
    raise Exception("All retries exhausted")

# First call fails, second succeeds
result = get_with_retry(api_mock)
print(f"Final result: {result}")

# Third call also works — side_effect iterator advances
result2 = get_with_retry(api_mock)
print(f"Next result: {result2}")
Output
Attempt 1 failed: 429 Too Many Requests
Final result: {'results': ['item1', 'item2']}
Attempt 1 failed: 429 Too Many Requests
Final result: {'results': ['item3']}
Senior Shortcut:
Need conditional side effects? Pass a function instead of a list. Use side_effect = lambda args: success_response if args['id'] == 42 else error_response for request-dependent mocking.
Key Takeaway
Side effects are state machines for your mocks. Use iterables for sequence-based failures, callables for conditional logic.

Understanding Lazy Attributes and Methods

Lazy attributes are properties or methods that aren't evaluated until accessed. In Playwright network mocking, this becomes a trap when you mock a route handler that references lazy objects like request headers or response bodies. If your mock checks request.post_data in a conditional but the handler lazily evaluates it during assertion, you get inconsistent results. For example, page.route('**/api', lambda route: route.fulfill(body=json.dumps({'fake': True}))) works fine, but if you lazily fetch body from an external source, the mock may return stale or partial data. Always compute lazy attributes inside the route handler before passing them to assertions. A common pattern: capture request.url and request.headers into local variables on arrival. This ensures that by the time you inspect them in your test assert statements, they reflect the exact state of the request at interception time, not some deferred evaluation.

mock_lazy.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — python tutorial
from playwright.sync_api import sync_playwright

def test_lazy_attr():
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        captured = {}
        def handler(route):
            captured['url'] = route.request.url
            captured['method'] = route.request.method
            route.fulfill(body='ok')
        page.route('**/api/data', handler)
        page.goto('http://localhost:3000')
        assert captured['url'] == 'http://localhost:3000/api/data'
        assert captured['method'] == 'GET'
        browser.close()
Output
No output (test passes)
Production Trap:
Lazy evaluation of request attributes inside high-order functions can cause race conditions in async contexts. Always snapshot values immediately.
Key Takeaway
Capture request attributes eagerly inside route handlers to avoid stale or inconsistent mock behavior.

Common Mocking Problems: Interface Changes and Misspellings

A frequent error in Playwright network mocking is misspelling the route pattern or method name. For instance, page.route('/api', handler) instead of '*/api' will silently match nothing. Worse, if the test expects a request to be made but the mock never fires, assertions on route.activity will pass falsely. Another problem: when the object interface changes — e.g., Playwright updates request.post_data to request.post_data_buffer — old mocks break. To avoid this, use specifications: define a clear dict of expected request properties (url, method, headers) and compare them against captured data using strict assertions. For example, expected = {'url': '/api/login', 'method': 'POST'} then assert captured == expected. This catches both misspellings and interface drifts. Also, always validate that the route was actually executed by checking a counter or flag inside the handler, not just relying on side effects.

spec_mock.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — python tutorial
from playwright.sync_api import sync_playwright

def test_with_spec():
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        hit = False
        def handler(route):
            nonlocal hit
            hit = True
            route.fulfill(body='{"status":"ok"}')
        page.route('**/api/login', handler)
        page.goto('http://localhost:3000')
        assert hit, 'Route never called!'
        assert route.context is not None  # ensures interface intact
        browser.close()
Output
No output (test passes)
Production Trap:
Silent misspellings in route patterns cause false positives. Always assert that the handler executed by incrementing a counter.
Key Takeaway
Use specification dicts and handler-execution flags to catch mock interface changes and route pattern misspellings.
● Production incidentPOST-MORTEMseverity: high

Mock Response Shape Mismatch

Symptom
Frontend renders no data after navigation. Console shows multiple 'undefined' errors in JavaScript.
Assumption
Engineer assumed the mock response keys matched the API contract exactly.
Root cause
Mock response object keys did not align with the TypeScript interfaces used in the frontend. The UI accessed 'user_name' but the mock returned 'username'.
Fix
Manually compare mock response structure against real API response (use browser devtools network tab). Use the same serialisation library if possible. For TypeScript projects, generate mock data from the same type definitions.
Key lesson
  • Always align mock response structure exactly with the API contract.
  • Use TypeScript types or OpenAPI specs as the source of truth for mock shapes.
  • Add a test that validates mock response against the real API schema — catch mismatches early.
Production debug guideWhen your mock isn't working or requests aren't being intercepted4 entries
Symptom · 01
Request not intercepted (bypasses route handler)
Fix
Check URL pattern: use '*/api/' for glob, or regex with r'pattern'. Playwright uses first-match wins — a broader pattern may capture the request before your specific handler.
Symptom · 02
Mock response causes JavaScript errors in the console
Fix
Inspect the Trace Viewer (playwright show-trace trace.zip) to see exactly what the browser received. Verify mock JSON fields match the expected interface. Use the real API response as a starting template.
Symptom · 03
Test execution is slow despite having mocks
Fix
Ensure you abort unnecessary resource requests — images, fonts, analytics scripts. Add page.route('*/.{png,jpg,gif,svg,woff2}', lambda r: r.abort()) before any navigation.
Symptom · 04
Mock is applied in one page but not in another
Fix
Routes are scoped to the page where they are registered. Use context.route() to apply a route handler to all pages in the same browser context.
★ Quick Reference for Network Mocking DebuggingCommands and patterns for diagnosing interception issues in Playwright Python.
Request not intercepted
Immediate action
Enable Playwright's verbose logging to see interception decisions
Commands
page.route('**/api/users', lambda r: r.fulfill(status=200, body='[]'))
context.tracing.start(snapshots=True, screenshots=True, sources=True)
Fix now
Wrap route registration in a try/finally and assert page.locator('text=expected').is_visible() after navigation to verify mock was applied.
Mock response causes blank page+
Immediate action
Open Trace Viewer: playwright show-trace trace.zip
Commands
page.route('**/api/data', handler) with a print('Intercepted:', route.request.url) for debugging
route.fulfill(headers={'Content-Type': 'application/json'}, body='{"key":"value"}')
Fix now
Copy the real response body from browser DevTools Network tab and use it as the mock body — then shape it.
Test times out waiting for network idle+
Immediate action
Reduce timeouts: page.set_default_timeout(5000)
Commands
page.route('**/*', lambda r: r.continue_()) to see all requests being made
page.goto(url, wait_until='domcontentloaded') instead of 'networkidle'
Fix now
Abort heavy assets early in the test setup — add a global route to abort images/fonts before any navigation.
Network Interception Strategies
StrategyBest ForComplexity
route.abort()Blocking ads, tracking, or images to speed up testsLow
route.fulfill()Testing UI states with specific API dataMedium
route.continue()Modifying headers or simulating specific query paramsMedium
Request Monitoring (expect_request)Verifying that an analytics event was actually sentHigh
Combined routes (fulfill + abort)Full control over mock data and performance simultaneouslyHigh

Key takeaways

1
Use page.route for fine-grained control over individual HTTP requests based on URL patterns.
2
Mocking allows you to test frontend components without a working backend, drastically increasing test stability.
3
Aborting unnecessary requests (like heavy images or analytics) can reduce test execution time by up to 40%.
4
Always prefer fulfilling with a real JSON structure rather than an empty string to avoid unexpected JavaScript 'undefined' errors.
5
Assert outgoing requests with expect_request to verify that the right data was sent.
6
Test at least three failure modes
HTTP error, timeout, and no response.

Common mistakes to avoid

4 patterns
×

Using hardcoded full URL instead of glob pattern

Symptom
Interception works on local dev environment but fails in CI or on different deployments where the base URL changes.
Fix
Use glob patterns like '*/api/' or regex r'.api.' to match regardless of domain. Avoid hardcoding 'https://staging.example.com/api/v1/...'.
×

Forgetting to register routes before page navigation

Symptom
The request goes out before the route handler is attached, so the mock is never applied.
Fix
Always call page.route() before page.goto() or before the action that triggers the request. Routes are attached per-page and only intercept future requests.
×

Mocking a response with incorrect Content-Type

Symptom
The browser rejects the response as invalid, leading to console errors like 'Unexpected token < in JSON at position 0'.
Fix
Set the Content-Type header in route.fulfill() to match the expected MIME type, e.g., content_type='application/json'. Use the browser's network panel to copy the original response headers.
×

Overusing route.continue() without proper handling

Symptom
The test becomes slow because many requests are processed unnecessarily, or the browser gets confused by unexpected header modifications.
Fix
Only use route.continue() when you need to modify the request. For most test scenarios, route.fulfill() or route.abort() is sufficient. Reduce the glob pattern scope to avoid intercepting every resource.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Playwright's network interception differ from using a library l...
Q02SENIOR
Your test suite is slow because it loads heavy 4K hero images. How would...
Q03SENIOR
How do you verify that an application sent the correct JSON payload to a...
Q04SENIOR
Explain the difference between `route.fulfill` and `route.continue`. In ...
Q05SENIOR
How would you simulate a Gateway Timeout (504) for a specific backend se...
Q01 of 05SENIOR

How does Playwright's network interception differ from using a library like 'Responses' or 'HTTPretty' in Python?

ANSWER
Playwright intercepts at the browser level via the Chrome DevTools Protocol, so it controls all HTTP traffic from the browser — including fetch, XHR, and even navigation requests. 'Responses' and 'HTTPretty' patch Python's HTTP client libraries (like requests), so they only work for server-side Python code. Playwright's approach is transparent to the JavaScript code running in the browser, making it ideal for end-to-end testing of frontend applications.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Can I mock WebSocket connections with Playwright?
02
How do I mock a specific API but let all others pass through?
03
Will mocked responses show up in the Playwright Trace Viewer?
04
Can I use regex patterns with route()?
05
How do I mock a response for a specific HTTP method (GET vs POST)?
N
Naren Founder & Principal Engineer

20+ years shipping production Python across data and backend systems. Notes here come from systems that actually shipped.

Follow
Verified
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
🔥

That's Python Libraries. Mark it forged?

5 min read · try the examples if you haven't

Previous
Playwright Python — Browser Automation and Testing
24 / 51 · Python Libraries
Next
NumPy Broadcasting — How It Actually Works