Stopping the Flake: Controlling Network State with cy.intercept()
Learn how to eliminate flaky E2E tests by using cy.intercept() to stub network requests, manage API state, and synchronize your UI assertions with network behavior.
21 Jul 2026, 06:46 UTC

The Problem with Live Backend Dependencies
End-to-end (E2E) tests often fail not because the frontend code is broken, but because the backend is unpredictable. A slow API response, a database record that was deleted by another test, or a temporary network hiccup can trigger a test failure. These are "flaky tests," and they erode trust in your CI/CD pipeline.
The solution is to decouple the frontend's behavior from the backend's state during UI testing. By using cy.intercept(), you can simulate specific server responses, ensuring your UI handles various data scenarios—like empty states, server errors, or massive datasets—without needing to manipulate a real database.
How cy.intercept() Works
cy.intercept() acts as a proxy between your browser and the network. It allows you to spy on requests (observe them without changing them), stub responses (replace the server's answer with your own), or modify requests and responses in transit.
Unlike the deprecated cy.route(), cy.intercept() handles all HTTP traffic, including fetch and XMLHttpRequest. To make it useful, you assign an alias to the intercept using .as('aliasName'). This allows you to explicitly tell Cypress to wait for that specific network call to complete before moving to the next assertion.
Worked Example: Stubbing a User List
Imagine a dashboard that fetches a list of users from /api/users. To test that the UI renders these users correctly regardless of who is actually in the database, you can use a fixture file.
1. Create a fixture file (cypress/fixtures/users.json):
[
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]2. Write the test spec:
it('should render the user list from a stubbed response', () => {
// 1. Setup the intercept before the action that triggers the request
// Requires Cypress 6.0+; confirm syntax against your installed version
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
// 2. Trigger the request
cy.visit('/dashboard');
// 3. Wait for the network call to finish to avoid race conditions
cy.wait('@getUsers');
// 4. Assert the UI reflects the fixture data
cy.get('.user-item').should('have.length', 2);
cy.get('.user-item').first().should('contain', 'Alice');
});Key Execution Details
- Where to run: These commands run within the Cypress Test Runner in the browser context, inside a spec file under
cypress/e2e/. - Permissions: No special OS permissions are required, as Cypress intercepts traffic at the browser level.
- Expected check: The
cy.wait('@getUsers')command ensures the test doesn't check for.user-itemelements before the API has responded. - Risk: If the intercept is defined after the action that triggers the request (e.g., after
cy.visit()), the request may fire before Cypress is listening, causing the test to hang or fail.
The Trade-off: Stubbing vs. Integration
While stubbing makes tests fast and deterministic, it introduces a risk: contract drift. This happens when the backend API changes (e.g., renaming a field from name to fullName), but your stubbed tests continue to use the old field. The tests will pass, but the application will be broken in production.
To mitigate this, adopt a tiered testing strategy:
| Test Type | Network Strategy | Goal |
|---|---|---|
| Component/UI Tests | Stubbed (cy.intercept) | Edge cases, UI layout, error states. |
| Smoke/E2E Tests | Live Backend | Verify the actual integration and API contracts. |
Verification and Limitations
To verify your intercepts are working, open the Cypress Test Runner and look at the Command Log on the left. You will see the (XHR/Fetch) request listed. Clicking it will show you the request headers and the stubbed response you provided.
Limitations to keep in mind:
- Scope:
cy.intercept()only catches requests made by the browser under test. It cannot intercept server-to-server communication. - Matching order: With overlapping routes, declare more specific intercepts before broader wildcard ones so the intended handler wins; confirm matching precedence against your installed version's documentation, since behavior has changed across major releases.
Closing Action
Audit your current test suite for cy.wait(number) calls. Every time you see a fixed millisecond delay, replace it with a cy.intercept() and a cy.wait('@alias'). This transition alone typically reduces CI failure rates by eliminating timing-related flakiness.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.