## TL;DR
Raise defaultCommandTimeout in your CI config and wait on network requests instead of the clock. CI runners are slower and shared, so elements that appear in a second locally can take several seconds there. A timeout that only happens in CI is almost always an optimistic wait, not a broken test.

## Error
```text
CypressError: Timed out retrying after 4000ms: Expected to find element: `.submit-btn`, but never found it.
```

## Steps
1. Run the failing spec locally three times in a row. Expected: green all three times, which confirms the failure is CI-only.
2. Replace fragile selectors with stable data attributes, for example cy.get('[data-cy=submit]'). Expected: the selector matches exactly one element and does not depend on CSS classes or copy.
3. In cypress.config.js, raise the retry timeout:
```js
module.exports = {
  e2e: {
    defaultCommandTimeout: 10000,
  },
}
```
Expected: the config loads and the new value appears in the Cypress settings panel.
4. If the element renders after an API call, wait on the request instead of guessing:
```js
cy.intercept('GET', '/api/orders').as('orders')
cy.visit('/orders')
cy.wait('@orders')
cy.get('[data-cy=order-row]').should('have.length', 3)
```
Expected: the test proceeds when the response arrives, with no fixed sleep.
5. Delete any cy.wait(2000) style fixed sleeps in the spec. Expected: no hard-coded sleeps remain.
6. Re-run the spec in CI. Expected: green.

## When to use
- cy.get() or cy.contains() times out in CI but passes on your machine
- Failures cluster on specs that render after API responses
- The suite recently gained selectors tied to animations or lazy loading

## When not to use
- The timeout also fails locally (fix the selector or the app instead)
- Every spec fails in CI (check baseUrl, services, and the Cypress binary install)
- The failure is a real assertion mismatch rather than a missing element

## Tool compatibility
- Cypress 12 through 14, end-to-end testing
- Any CI provider that runs cypress run, including GitHub Actions, CircleCI, and Jenkins
- Chrome, Edge, Firefox, and Electron runners

## Variant phrasings
### Timed out retrying after 4000ms in GitHub Actions only
Same fix. Shared Actions runners are slow, so the 4 second default is the first thing to raise.
### Element never found in the Jenkins pipeline but fine locally
Check whether Jenkins serves a different baseUrl or an older build. If the page itself differs, no timeout will help.

## Why it happens
Cypress retries cy.get() until defaultCommandTimeout expires. Local machines are fast and warm. CI runners are cold, shared, and often throttled, so rendering and API responses land later. A selector that needs five seconds in CI fails at the four second default while passing locally in one.

## Edge cases
- Still red after raising the timeout to 15000ms: the element genuinely never renders in CI. Check baseUrl, feature flags, and seeded test data.
- Flaky in only one CI browser: browser-specific rendering or animation timing. Scope the timeout change to that browser config.
- Passes alone in CI but fails in the full suite: test pollution. Check testIsolation and state shared between specs.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_xJ80SjbgL9m2A9iwPAaSfw
