Cypress "Timed out retrying after 4000ms" - Fix Flaky Assertions
Cypress retried a command/assertion for its timeout window (4000 ms by default) and the condition never became true. Usually the element renders slower than the timeout in CI, or the selector no longer matches.
What this error means
A command fails with "Timed out retrying after 4000ms: Expected to find element: 'X', but never found it." It often passes locally and on the headed app but flakes on a slower CI runner.
Timed out retrying after 4000ms: Expected to find element:
`[data-testid="submit"]`, but never found it.Common causes
Element renders slower than the timeout in CI
On a loaded runner the app paints later than locally. The default 4s retry window expires before the element appears - a timing-only flake.
Selector no longer matches
A markup or data-testid change means the selector matches nothing. This is deterministic, not flaky, and a longer timeout will not help.
Asserting before the app settles
Asserting immediately after an action that triggers async work (XHR, animation) races the UI; without retry-able assertions it can miss the final state.
How to fix it
Assert on the actual readiness signal
Wait on the network or a visible state change rather than a fixed sleep, so the test is fast when possible and patient when needed.
cy.intercept('GET', '/api/cart').as('cart');
cy.visit('/cart');
cy.wait('@cart');
cy.get('[data-testid="submit"]').should('be.visible');Raise the timeout for genuinely slow steps
cy.get('[data-testid="submit"]', { timeout: 15000 }).should('exist');
// or globally in cypress.config.js: defaultCommandTimeoutHow to prevent it
- Wait on intercepted requests, never
cy.wait(<ms>). - Use stable
data-testidselectors decoupled from styling. - Tune
defaultCommandTimeoutfor the slowest CI tier.