Reducing Flaky End-to-End Tests with Cypress.io’s Automatic Waiting and Network Stubbing
Learn how Cypress.io’s automatic waiting and cy.intercept() eliminate test flakiness, with a concrete login‑flow example, debugging tips, and configuration guidance.
25 Sept 2026, 22:06 UTC

Problem: Tests that pass locally but fail in CI
Many teams notice that end-to‑end (E2E) suites built with Cypress.io occasionally flicker: a test that clicks a button works on a developer’s laptop but times out in the pipeline. The root cause is usually timing‑related – the application hasn’t finished rendering, or an API request is slower than expected – and teams often resort to hard‑coded cy.wait() calls, which only mask the symptom and make suites slower.
Thesis: Leverage Cypress’s built‑in waiting and network stubbing to eliminate flakiness without adding sleeps
Cypress.io already provides two powerful mechanisms that, when used together, make tests resilient to variable load times: automatic retries on DOM assertions and the cy.intercept() command for mocking network calls. By relying on these features, you can write deterministic tests that run fast and stay reliable across environments.
1. Automatic waiting removes the need for explicit sleeps
Cypress automatically retries commands and assertions until they succeed or a default timeout (4 seconds) expires. For example, cy.get('.submit-btn').should('be.visible') will keep querying the DOM until the button appears, eliminating the guesswork of how long to wait.
Verification: Run a test locally with the DevTools open and watch the Command Log. You’ll see each command accompanied by a retry count and the elapsed time. No extra cy.wait() is needed.
2. Isolate network dependencies with cy.intercept()
Instead of waiting for a real backend, you can stub the request and control the response payload and latency. This makes the test deterministic and fast.
// cypress/e2e/login.spec.js
describe('Login flow', () => {
beforeEach(() => {
// Stub the auth endpoint; delay the response by 200 ms to simulate latency
cy.intercept('POST', '/api/auth/login', (req) => {
req.reply({ statusCode: 200, body: { token: 'fake-jwt' } });
}).as('loginRequest');
cy.visit('/login', { timeout: 10000 });
});
it('shows dashboard after successful login', () => {
cy.get('[data-cy=email]').type('[contact removed]');
cy.get('[data-cy=password]').type('secure123{enter}');
// Wait for the stubbed request to finish (optional, for clarity)
cy.wait('@loginRequest');
// Assert that the app navigates to the dashboard
cy.url().should('include', '/dashboard');
cy.get('[data-cy=welcome-message]').should('contain', 'Welcome');
});
});
Place this file in cypress/e2e/ and run it with npx cypress open or npx cypress run --spec cypress/e2e/login.spec.js. You’ll see the intercepted request appear in the Command Log as loginRequest with the status you defined.
3. Use the Cypress Dashboard for time‑travel debugging
When a test does fail, the Dashboard (free tier available) records a snapshot after each command. You can inspect the DOM, network traffic, console logs, and even view a video of the run. This visibility turns a flaky failure into a quick root‑cause analysis.
Verification: After a run, open the Dashboard, select the failed spec, and click a command in the timeline. The corresponding snapshot shows exactly what the page looked like at that moment.
4. Configure baseUrl and viewport for cross‑device checks
To avoid repeating the full URL in every test, set baseUrl in cypress.config.js. Combine it with viewport settings to emulate different screen sizes without launching external device labs.
// cypress.config.js
module.exports = {
baseUrl: 'https://app.example.com',
viewportWidth: 1280,
viewportHeight: 720,
};
Now a simple cy.visit('/') will resolve to https://app.example.com/. Adjust the viewport values to test tablets or mobiles.
Trade‑off: Browser coverage and parallel execution
Cypress runs tests in a Chromium‑based browser by default. If you need Firefox or Safari coverage, you must install the cypress-browser-install plugin or rely on a third‑party service. Additionally, the single test‑runner process can become a bottleneck for very large suites; true parallelism requires either Cypress Cloud (paid) or splitting specs across CI containers/matrix jobs.
Practical check: Run npx cypress run --browser firefox locally. If the command fails, you know the browser isn’t supported out‑of‑the‑box and you’ll need the extra setup.
Actionable closing
- Audit your existing specs for any
cy.wait()calls and replace them with assertions that rely on automatic retrying. - Identify network‑heavy steps (login, data fetches) and stub them with
cy.intercept(), controlling latency and payload. - Enable the Cypress Dashboard (set
CYPRESS_RECORD_KEYin CI) to capture recordings and snapshots for every run. - Define
baseUrland suitable viewport sizes incypress.config.jsto simplify URL handling and support responsive checks. - If you need broader browser coverage or parallel execution, plan for the Cypress‑browser‑install plugin or a CI matrix; otherwise, accept the Chromium‑only limitation.
By leaning into Cypress’s native waiting and network‑mocking capabilities, you reduce flaky tests, speed up feedback, and keep your E2E suite maintainable without resorting to brittle sleep statements.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.