Stop guessing waits: stub HTTP responses with cy.intercept() in Cypress
Replace cy.server() and cy.route() with cy.intercept() and alias waits to reduce flaky E2E tests.
24 Aug 2026, 12:48 UTC

The problem: tests that pass when they feel like it
An E2E test clicks "Load products," then immediately checks for a product card. Sometimes it passes. Sometimes it fails because the API has not responded yet. Teams often patch this with cy.wait(1000), which slows down the suite and still does not guarantee the request finished.
Older Cypress tests also use cy.server() and cy.route() to control HTTP traffic. Those APIs are legacy in modern Cypress versions. The current supported approach is cy.intercept(), which lets you spy on, stub, and wait for specific requests.
What cy.intercept() does
cy.intercept() intercepts HTTP/XHR/fetch requests. You can match a URL string, regular expression, or route handler object. Attaching .as('alias') gives the request a name, and cy.wait('@alias') pauses the test until that request completes—or fails with a timeout if it never arrives.
This is different from a fixed timer. The test proceeds as soon as the network response arrives, up to Cypress's default timeout, rather than waiting an arbitrary second whether the backend needs 100 ms or 10 seconds.
Version assumption and check
This post assumes a modern Cypress installation where cy.intercept() is the recommended HTTP stubbing API and cy.route()/cy.server() are legacy or removed. Verify before adopting:
# Run in the project root
npx cypress --version
No special permissions are required. Compare the major version against your installed Cypress documentation. If cy.route() is absent or deprecated in your version, migrate to cy.intercept(). Do not assume removal based on this post alone; check the documentation for your exact version.
Worked example: empty state and error path
Imagine a product listing page that calls GET /api/products. You want to verify two client behaviors without changing the backend:
- When the API returns an empty array, the page shows "No products found."
- When the API returns a 500 error, the page shows an error banner.
Example specification
describe('Product listing network states', () => {
beforeEach(() => {
// Define the intercept before the page triggers the request.
cy.intercept('GET', '/api/products', {
fixture: 'empty-products.json'
}).as('getProducts');
cy.visit('/products');
});
it('shows empty state when the API returns no products', () => {
// Wait for the intercepted request before asserting UI.
cy.wait('@getProducts');
cy.contains('No products found.');
});
it('shows error banner on a 500 response', () => {
cy.intercept('GET', '/api/products', {
statusCode: 500,
body: { message: 'Internal server error' }
}).as('getProductsError');
cy.wait('@getProductsError');
cy.contains('Something went wrong.');
});
});
Placeholders: replace /api/products with your real endpoint, empty-products.json with an existing fixture in cypress/fixtures, and /products with your route. Run the spec with npx cypress run --spec cypress/e2e/products.cy.js; the path varies by project. Expected check: the first test passes only after the intercepted response arrives and the empty-state text is present. Risk: if the page never requests /api/products, cy.wait('@getProducts') times out and fails the test, which is the intended signal that your intercept or page flow is wrong.
Trade-offs and limitations
- Stubbing is a contract you control. It verifies the client against a fixed response. It does not catch backend schema drift or real integration failures.
- Aliases are test-scoped. Define intercepts in
beforeEachor immediately before the user action that triggers the request. Waiting on an alias that never fires causes a timeout. - Over-stubbing creates brittleness. Stub slow third-party APIs and edge cases. Keep critical checkout or payment paths against real endpoints when possible so network failures are not hidden.
Practical check and closing
To verify the migration, compare two specs: one using cy.wait(1000) after clicking a button, and one using cy.intercept() plus cy.wait('@alias'). Run both against a deliberately delayed API response. The alias-based test should proceed immediately when the response arrives, while the timer test either waits the full second or fails when the backend is slower than expected.
Replace legacy route calls where they exist, but keep real-backend integration coverage for paths that matter. Stub the network to control timing and edge cases, not to pretend the backend does not exist.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.