Intercepting HTTP Requests with Playwright

Intercept, block, or mock API calls in Playwright to eliminate flaky tests caused by unstable backends.

Last updated: 29 July 2026 12 min read

Key Takeaways

  • Playwright intercepts calls at the browser engine level; every matched route must explicitly call continue(), fulfil(), or abort() to prevent tests from hanging.
  • Use specific URL matchers and capture request data inside route callbacks to perform payload assertions after request completion, avoiding unhandled timeouts.
  • Keep tests clean by centralising reusable route helpers, enforcing route cleanup between runs, or offloading complex mock rules to dedicated tools like Requestly.

Your Playwright suite just went red. Not because a button broke or a third-party API payment timed out, but because you lost your browser proxy.

If you are an SDET who’s spent a morning debugging a “failed” test that had nothing to do with your app, the fix isn’t a longer timeout. It’s HTTP interception.

Playwright’s built-in request interception lets you decouple tests from flaky or slow backends by mocking, modifying, or blocking traffic directly in the browser automation layer.

Intercepting HTTP requests in Playwright gives you fine-grained control over how your application talks to backend services, making tests faster, more reliable, and easier to debug.

This article walks through how Playwright’s network interception works, the key APIs, common patterns, and best practices with practical, test-orientated examples.

What Does ‘Interception’ Mean in Playwright?

In Playwright, intercepting HTTP requests means monitoring proxy calls from Playwright’s server to the browser, verifying which call is genuine, which is required, and which needs to be aborted.

‘Playwright interception’ means verifying whether your server has end-to-end connectivity with browsers. It happens that the browser actually sends the request over to the server or processes the response, giving the test runner full control over both directions of traffic.

Why intercept HTTP in tests?

Teams intercept requests in Playwright tests mainly to:

  • Mock unstable or third-party APIs so tests are deterministic and do not depend on external uptime or rate limits.
  • Speed up suites by skipping slow resources (fonts, analytics, ads) and reducing server round-trips.
  • Simulate edge cases like server errors, timeouts, partial data, or different user roles without needing specific backend fixtures.

How Playwright Interception Works in the Backend (and Key APIs)

Playwright controls browser engines (Chromium, Firefox, and WebKit) using a persistent WebSocket connection.

Because it sits directly between your test code and the browser engine, Playwright can hook into network events before they ever leave the client or reach the network layer.

How Playwright Interception Works in the Backend (and Key APIs)

When a network request matches a target pattern, the browser pauses the request and hands control over to Playwright. The playwright then exposes the event to your test runner through two core objects:

  1. Request: Contains metadata about the outgoing call (URL, headers, payload, HTTP method).
  2. Route: Controls the lifecycle of the intercepted call.

Core Interception API Surface in Playwright

You control this mechanism in JavaScript/TypeScript using a handful of core methods. The core interception-related APIs in the JavaScript/TypeScript test runner are:

1. Registering & Removing Handlers
  • page.route(url, handler): Attaches a network listener to a specific page. Requests matching the URL pattern (glob or regex) trigger your custom handler.
  • browserContext.route(url, handler): Applies interception globally across all pages and tabs in the context. Ideal for app-wide mocks or blocking global trackers.
  • page.unroute(url, handler?): Detaches an active route handler when you want to restore normal network behaviour.
2. Inspecting the Traffic
  • route.request(): Returns the underlying request object so you can inspect headers, query parameters, HTTP methods, or payload body before deciding how to proceed.
3. Terminating the Route
  • route.continue(options?): Unblocks the request and lets it proceed to the real server, with optional overrides to modify headers, post data, or URL on the fly.
  • route.fulfill(options): Intercepts the request entirely and returns a custom, mocked response (status code, headers, JSON body) directly to the browser without hitting the network.
  • route.abort(errorCode?): Simulates network or client failures (e.g., failed, blocked by client, connection refused) to test how your UI recovers from broken endpoints.

Intercepting and modifying HTTP requests

To intercept and modify a http request, use page.route (or context.route) with a glob, regex, or predicate:

await page.route(‘**/signup/partners’, async (route, request) => { const body = request.postDataJSON();
const modifiedBody = {
…body,
agentId: ‘test-agent’,
};

await route.continue({
postData: JSON.stringify(modifiedBody),
});
});

This pattern is useful when:

  • Sanitizing or normalizing payloads (e.g., random IDs, timestamps) so assertions stay stable.
  • Injecting feature flags, headers, or auth tokens without altering application code.

Running request-interception tests locally often hides environment-specific issues like proxy behavior, browser quirks, or header mutations. With BrowserStack Automate, Playwright interception logic can be validated on real browsers and OS combinations at scale, ensuring request modifications behave consistently across production-like environments.

For advanced request rewriting, header injection, and payload manipulation beyond test code, Requestly complements Playwright by enabling live HTTP overrides without changing application or test logic-making complex network scenarios easier to simulate and debug in CI pipelines

Mocking API responses with route interception

Mocking is the most common use of interception: you intercept a request and respond with synthetic data via route.fulfill.

await page.route(‘**/api/v1/fruits’, async (route) => { const json = [{ id: 21, name: ‘Strawberry’ }];
await route.fulfill({ json });
});

Key capabilities when mocking:

  • Return JSON, text, or binary bodies with custom status codes and headers.
  • Wrap an existing real response using page.request.fetch(route.request()) and then mutate body or headers before fulfilling.
  • Simulate error states like 500 or 503 to validate UI error handling.

Blocking and aborting network requests in Playwright

Use route.abort() when you want to cancel requests entirely, such as:

  • Blocking analytics, tracking pixels, and third-party ads to reduce noise and speed up tests.
  • Preventing large asset downloads (images, fonts) during headless runs for performance.

Example:

await page.route(‘**/*’, (route) => { const url = route.request().url();
if (url.includes(‘google-analytics’) || url.endsWith(‘.png’)) {
return route.abort();
}
return route.continue();
});

Being too broad (‘**/*’) can accidentally block navigations, so route conditions must be carefully tuned.

URL patterns and conditional interception

Playwright supports several ways to target requests:

  • Glob patterns: ‘**/api/**’, ‘**/*.json’.
  • Regular expressions: //graphql?operation=GetUser/.
  • Predicate functions: (route) => route.request().postData()?.includes(‘priority=high’).

Predicate-based routing is ideal when matching on:

  • HTTP method (GET vs POST vs PUT).
  • Query parameters, headers, or JSON body shapes.
  • Specific microservice domains in a micro-frontend architecture.

Managing Multiple and Concurrent Network Intercepts

Modern web applications frequently trigger multiple parallel requests in response to a single user interaction (e.g., loading a dashboard that fetches user profile, metrics, and orders simultaneously). To keep your test suite fast and deterministic, follow these patterns:

  • Keep Handlers Modular and Isolated: Define a focused, URL-specific page. route() listeners rather than a single monolithic handler that branches with complex if/else logic for every endpoint.
  • Avoid Heavy Logic Inside Handlers: Route callbacks execute in the event loop and block the underlying network stream. Keep your handlers lean and purely asynchronous.
  • Use Promise. All for parallel assertions: When a single UI trigger fires multiple API calls, combine the page. waitForResponse() with action triggers to prevent race conditions.
// Wait for concurrent API calls triggered by a single UI action

const [userResponse, ordersResponse] = await Promise. All

([

  page. waitForResponse(r => r.url().includes('/api/user') && r.status() === 200),

  page. waitForResponse(r => r.url().includes('/api/orders') && r.status() === 200),

  page.click('button#load-dashboard'),

]);

Managing Multiple and Concurrent Network Intercepts Terminal

Managing Multiple and Concurrent Network Intercepts Localhost

Validating Request Payloads and Response Data

Network interception also allows you to make assertions directly at the network boundary rather than relying purely on DOM changes.

This lets you verify outgoing POST payloads, confirm custom headers, and extract server values (like auth tokens or generated IDs) for subsequent test steps.

Note: Avoid placing raw expect() statements directly inside a route() handler without error handling. If an assertion fails inside an unhandled route callback, the request stalls, causing the test to time out with a misleading error instead of failing cleanly on the assertion

Instead, extract the request payload within the handler, allow the request to terminate, and perform assertions on the captured data:

let capturedPayload: Array<{ id: string; quantity: number }> | null = null;



// Intercept, capture data, and allow the call to proceed

await the page. request. route('**/api/checkout', async (route) => {

  const request = route.request();

  capturedPayload = request. postDataJSON();

  

  await the route. continue();

});



// Perform UI action

await the page. click('button#submit-order');



// Assert on the captured payload after request execution

expect(capturedPayload).not.toBeNull();

expect(capturedPayload).toHaveLength(3);

Validating Request Payloads and Response Data Terminal

Validating Request Payloads and Response Data Localhost

While local network mocking validates application logic, it doesn’t account for browser-specific engine behaviour, proxy variations, or platform-level SSL/TLS handling. Executing these network-level assertions across real browsers ensures payload validation remains consistent.

Common mistakes with Playwright HTTP interception:

Typical pitfalls when working with Playwright routes include:

  • Register Handlers Before Navigation: Always declare page.route() prior to initiating actions like page. goto() or early network calls will execute unmocked.
  • Avoid Overly Broad Match Patterns: Using generic wildcards like **/* forces Playwright to process every asset and stylesheet, significantly slowing down test execution.
  • Always terminate the route lifecycle: every handler branch must explicitly call route. continue(), route.fulfill(), or route.abort() to prevent requests from stalling and causing test timeouts.
  • Watch Out for Handler Overrides: When multiple page.route() listeners match the exact same URL, only the last-registered handler executes, silently overwriting earlier ones.
  • Balance Mocking to Prevent Schema Drift: Over-mocking decouples tests from your backend so completely that you risk missing breaking API changes, schema mismatches, or real-world CORS issues.

Debugging failed or missed intercepts

When interception does not behave as expected, focus on visibility and timing. Helpful techniques:

  • Log the URL, method, and headers inside route handlers to verify what is actually being matched.
  • Use Playwright trace or network views to confirm whether a request was fulfilled or actually sent to the server.
  • Check for service workers that may intercept traffic before Playwright and disable them with { serviceWorkers: ‘block’ } on the context when necessary.
const context = await browser.newContext({ serviceWorkers: ‘block’ });

Best practices for HTTP interception using Playwright

Network interception makes test suites fast and resilient, but maintaining clear test isolation and preventing false positives requires a disciplined strategy.

Following these practical guidelines ensures your network mocks remain maintainable, isolated, and representative of real app behaviour.

  • Centralise Common Mocks in Helper Utilities: Abstract repetitive setup logic such as user authentication states, feature flag overrides, and base API schemas into reusable helper functions across your test suite.
  • Scope Matchers with Precision: Restrict route patterns using specific endpoint paths, HTTP methods, or body predicates to prevent accidental interception of unrelated background requests.
  • Verify Mock Usage with Assertions: Always validate that your mock actually executed by asserting on the rendered UI data or checking the request count to avoid silent fallback to live endpoints.
  • Clean Up Routes to Prevent Test Bleed: Reset intercepted routes between tests by using the page. unroute() or running individual tests inside fresh browser contexts.

How Requestly Enhances HTTP Interception in Playwright

Requestly acts as a dedicated network management layer that complements Playwright by moving interception rules out of your codebase and into a centralised, reusable engine.

Here is how Requestly simplifies network-level testing for growing engineering teams:

  • Decouples mock rules from test code: Instead of cluttering your Playwright spec files with complex conditional page. route() handlers, Requestly handles mocking, redirects, and header modifications through rule-based logic.
  • Simplifies API & Third-Party Service Mocking: Easily stub static or dynamic responses for unstable backends, payment gateways, analytics tools, or feature flags. Testing against simulated responses eliminates test failures caused by backend downtime or rate limits.
  • Simulates Edge Cases & Fault Injection: Modify headers, query parameters, status codes, and response bodies on the fly. You can easily test how your app handles 500 errors, broken payloads, or auth failures without changing backend code.
  • Validates Behaviour Under Poor Network Conditions: Introduce artificial latency or drop requests entirely to verify how your Playwright suite behaves during timeouts, slow connections, or server crashes, helping catch subtle race conditions before release.
  • Shared Rules Across Local Dev and CI: Requestly rules can be shared across your team and executed consistently across local developer machines and CI/CD pipelines. This eliminates “works on my machine” inconsistencies without duplicating mock setups across spec files.

By offloading complex network logic to a specialised tool, teams using Playwright at scale can maintain faster, cleaner, and significantly more stable test suites.

Conclusion

Network interception turns Playwright from a simple UI automation runner into a powerful, API-aware testing framework.

By leveraging methods like route. continue(), route.fulfill(), and route.abort(); alongside scoped matchers and clean lifecycle management, you can build fast, deterministic tests that aren’t held hostage by backend instability.

Whether you rely entirely on Playwright’s native API surface or extend it with centralised tools like Requestly for enterprise scale, mastering HTTP interception is essential for shipping reliable, resilient web applications.

Version History

  1. Jul 29, 2026 Current Version

    Added 2 new sections, edited the intro and conclusion, merged sections, and tailored best practices as per intent.

    Abdul Qadir Khan
    Reviewed by Abdul Qadir Khan Senior Automation Expert
Tags
Playwright
Manoj Kumar Masini
Manoj Kumar Masini

Senior Automation Expert

Manoj Kumar is an Senior Automatiom expert with 7+ years of experience in test automation and quality engineering. He writes about automation testing, QA best practices, and strategies for building reliable, scalable software delivery pipelines.

Intercepting HTTP requests without breaking tests?
Mock and modify Playwright HTTP requests easily with Requestly for stable, reliable tests.