How Cypress Implements Network Request Interception with cy.intercept()
Cypress implements cy.intercept() through a three-layer architecture that separates the driver command API, a browser-agnostic NetworkInterceptionCore, and browser-specific transports using Chrome DevTools Protocol (CDP) or WebDriver BiDi to pause, match, and manipulate HTTP requests.
The cy.intercept() command in Cypress enables developers to stub, spy, and modify network traffic during end-to-end tests. According to the cypress-io/cypress source code, this feature relies on a sophisticated pipeline that bridges the test driver with low-level browser automation protocols. Understanding this implementation reveals how Cypress achieves consistent network stubbing across Chrome, Edge, Firefox, and WebKit without relying solely on proxy-based MITM techniques.
The Three-Layer Architecture
Cypress’s network interception splits responsibilities across distinct layers to ensure cross-browser compatibility and maintainable code.
Layer 1: Driver Command Registration
When a test calls cy.intercept(), the driver processes the command in packages/driver/src/cy/net-stubbing/add-command.ts. This module validates arguments, creates a RouteMatcher object, and registers the route with the core interception engine. If the command chain includes .as('alias'), the packages/driver/src/cy/net-stubbing/aliasing.ts module associates the route with a Cypress alias for later reference via cy.wait().
Layer 2: Browser-Agnostic Interception Core
The NetworkInterceptionCore in packages/network-interception/lib/core/index.ts serves as the central coordination hub. When the driver calls NetworkInterceptionCore.registerRoute(matcher, handler), the core instantiates an HttpIntercept class containing the matcher, handler function, and a unique interception ID. The core applies default policies—such as blocked-hosts handling in packages/network-interception/lib/policies/blocked-hosts.ts—to determine how requests should be treated before they ever reach the network transport.
Layer 3: Browser-Specific Transport
The server creates a NetworkInterceptionRuntime instance (wired in packages/server/lib/network-runtime.ts) that pairs the core with a transport adapter specific to the browser:
- Chrome and Edge: Uses
packages/server/lib/browsers/cdp-protocol/cdp-fetch-transport.tsto enable the CDP Fetch domain (Fetch.enable) and listen forFetch.requestPausedevents. - Firefox and BiDi-capable browsers: Uses
packages/server/lib/browsers/bidi_automation.tsto callnetworkAddInterceptvia the WebDriver BiDi protocol and handlebeforeRequestSentevents.
Both transports convert paused browser requests into CyHttpMessage objects and forward them to the core for matching.
The Request Interception Flow
The complete lifecycle of an intercepted request progresses through six distinct stages.
1. Command Parsing and Route Registration
The intercept command parses its arguments (URL, method, handler) and constructs a RouteMatcher. It then invokes NetworkInterceptionCore.registerRoute() to store the matcher and handler (which may be a function, static response, or middleware) in the core’s registry.
2. Core Registration and Policy Application
Upon registration, the core builds an HttpIntercept instance and executes registerDefaultNetworkPolicies. This applies global rules such as blocked-host restrictions and content-type handling, ensuring requests are filtered according to project configuration before matching occurs.
3. Transport Initialization
When the spec run begins, the server instantiates the runtime. For Chrome, CdpFetchTransport sends Fetch.enable to the browser and attaches a listener on Fetch.requestPaused. For Firefox, BidiAutomation calls the BiDi networkAddIntercept command to obtain an intercept ID and prepares to receive beforeRequestSent events.
4. Request Matching and Interception Creation
When a request occurs, the transport pauses it and queries the core via getRoutesForRequest(). If a RouteMatcher matches, the core creates an Interception object that tracks the full lifecycle (request, response, error). The driver’s proxy logging module (packages/driver/src/cypress/proxy-logging.ts) records this event to display in the Cypress Command Log.
5. Handler Execution and Resolution
The core invokes the registered handler, which may:
- Continue the request to the destination server
- Stub a static response using the provided
statusCodeandbody - Modify the request headers or body before continuation
Once the handler completes, the transport either releases the request to the network or returns the stubbed response, then notifies the core to mark the interception as completed or errored.
6. Waiting and Assertions
Commands like cy.wait('@alias') utilize packages/driver/src/cy/net-stubbing/wait-for-route.ts to poll the core’s stored interceptions. The utility waits until the interception reaches the desired state (e.g., response received) before yielding control back to the test for assertions.
Practical Implementation Examples
// Spy on GET requests to /api/users and log them
cy.intercept('GET', '/api/users', (req) => {
console.log('Intercepted:', req.method, req.url)
})
// Stub a JSON response without hitting the server
cy.intercept('GET', '/api/todos', {
statusCode: 200,
body: [{ id: 1, title: 'Write article' }],
})
// Modify request body and wait for the response
cy.intercept('POST', '/api/login', (req) => {
req.body = { ...req.body, extra: 'data' }
}).as('login')
// Later in the test
cy.wait('@login').its('response.statusCode').should('eq', 200)
Key Source Files
packages/driver/src/cy/net-stubbing/add-command.ts: Implements theinterceptcommand and argument validation.packages/driver/src/cy/net-stubbing/aliasing.ts: Handles.as()aliasing for intercepted routes.packages/network-interception/lib/core/index.ts: Entry point for the browser-agnostic interception core.packages/network-interception/lib/policies/blocked-hosts.ts: Implements blocked-host policy enforcement.packages/server/lib/network-runtime.ts: Wires the core into the server’s request handling pipeline.packages/server/lib/browsers/cdp-protocol/cdp-fetch-transport.ts: CDP Fetch domain implementation for Chrome/Edge.packages/server/lib/browsers/bidi_automation.ts: WebDriver BiDi implementation for Firefox.packages/driver/src/cypress/proxy-logging.ts: Logs interceptions to the Cypress Command Log UI.packages/driver/src/cy/net-stubbing/wait-for-route.ts: Provides the polling mechanism forcy.wait('@alias').
Summary
- Cypress splits network interception into driver commands, a generic core, and browser-specific transports to maintain consistency across CDP and BiDi protocols.
- Route registration begins in
add-command.tswith a RouteMatcher, but the actual interception logic lives in the NetworkInterceptionCore. - Browser transports like
CdpFetchTransportandBidiAutomationpause requests and convert them into CyHttpMessage objects for the core to process. - Handlers attached to routes can spy, stub, or modify requests before the transport releases them or returns a mocked response.
- Waiting and assertions rely on
wait-for-route.tspolling the core’s stored Interception objects until they reach the desired lifecycle state.
Frequently Asked Questions
What is the difference between the driver layer and the transport layer in cy.intercept()?
The driver layer (in packages/driver) handles the test-side API, parsing arguments and creating RouteMatcher objects. The transport layer (in packages/server/lib/browsers) manages low-level browser protocols like CDP Fetch or WebDriver BiDi to actually pause and manipulate network traffic. This separation allows the core interception logic to remain browser-agnostic while the transport adapts to specific automation protocols.
How does Cypress handle network interception in Firefox compared to Chrome?
For Chrome and Edge, Cypress uses the Chrome DevTools Protocol (CDP) Fetch domain via cdp-fetch-transport.ts, enabling Fetch.enable and processing Fetch.requestPaused events. For Firefox, Cypress uses WebDriver BiDi via bidi_automation.ts, calling networkAddIntercept and handling beforeRequestSent events. Both transports forward requests to the same NetworkInterceptionCore, ensuring consistent behavior across browsers.
What happens to a request after it matches a cy.intercept() route?
Once a request matches, the core creates an Interception object and invokes the registered handler. The handler can modify the request, immediately return a stubbed response, or allow the request to continue to the destination server. The transport either fulfills the request from the handler’s instructions or releases the paused request, then notifies the core to update the interception status to completed or errored.
Where does Cypress store intercepted requests for cy.wait() assertions?
Intercepted requests are stored as Interception objects within the NetworkInterceptionCore. When cy.wait('@alias') executes, the wait-for-route.ts module polls the core’s registry via waitForRoute, resolving when the interception reaches the required state (such as receiving a response). This allows tests to assert on request and response properties after the network exchange has occurred.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →