Learn Cypress - Automatic Waiting & Retries
Episode 5 of 23

Learn Cypress - Automatic Waiting & Retries

This episode dissects Cypress's automatic waiting: how to wait for elements without manual sleeps, retry-ability on commands and assertions, handling timing issues, and best practices for reliable, non-flaky tests.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

One of the main reasons people switch from Selenium to Cypress: no more random sleep calls scattered everywhere. Episode 5 explains why. You will understand automatic waiting, retry-ability on commands and assertions, and how to avoid patterns that actually make tests slow and flaky.

The understanding in this episode is what separates tests that live in a team forever from tests that get deleted because they fail too often.

The Automatic Waiting Concept

Cypress doesn't execute commands blindly. When a command can't succeed immediately — for example, an element hasn't appeared yet because a fetch is still running — Cypress waits and retries until it succeeds or times out.

JSWaiting for elements without sleeps
cy.visit("/dashboard");
 
cy.get("[data-cy=chart]").should("be.visible");
cy.get("[data-cy=loading-spinner]").should("not.exist");

cy.get("[data-cy=chart]") will wait until the element appears in the DOM, while .should("not.exist") waits until the spinner is truly gone. No cy.wait(3000) — Cypress knows when the condition is met.

Timeout Limits

Automatic waiting doesn't run indefinitely. The limit is set by defaultCommandTimeout, which is 4000 milliseconds by default. You can adjust it per command:

JSAdjusting timeout per command
cy.get("[data-cy=analysis-result]", { timeout: 15000 }).should("be.visible");

{ timeout: 15000 } extends the wait for this command alone to 15 seconds — useful for genuinely slow processes like computations or large loads.

Handling Timing Issues Without Explicit Waits

The classic pattern that creates flakiness is guessing at time:

JSPattern to avoid
cy.wait(5000);
cy.get("[data-cy=result]").should("be.visible");

cy.wait(5000) waits for a guessed number. Enough on a fast machine, not enough on a slow one. Replace that pattern with an assertion that waits until the condition is met:

JSCorrect pattern
cy.get("[data-cy=result]").should("be.visible");

For asynchronous loading with a clear source, wait for the network event captured by cy.intercept() — not a time guess. We will dig into this in episode 12.

Don't Wait for Elements That Disappear

Be careful with elements that appear and then disappear quickly. Waiting for them to exist and then expecting them to still be there on the next action can be risky. It's safer to wait for the final condition, such as the result content becoming visible, rather than an intermediate element.

Retry-Ability on Commands and Assertions

Commands That Can Be Retried

Not all commands are retried. Those that can be: cy.get, cy.contains, cy.find, cy.should, and assertions. Those that are not retried: cy.visit, cy.request, cy.then, and actions like cy.click — actions are only run once against the element already found.

JSRetry on assertions
cy.get("[data-cy=title]").should("have.text", "Welcome");
 
cy.wrap([1, 2, 3]).should("have.length", 3);

.should("have.text", "Welcome") is retried: Cypress re-reads the element's text until it matches or times out. Likewise cy.wrap(...).should(...) — assertions inside should are rerun automatically.

Callbacks Inside should

For more complex logic, use a callback. Important: the callback must use expect, not assert, for retry to work:

JSAssertion with callback
cy.get("[data-cy=product-list]").should(($items) => {
  expect($items).to.have.length(8);
});

expect($items).to.have.length(8) inside the callback lets Cypress retry the entire callback until the assertion passes. This is the correct way to validate several elements at once without guessing at render time.

Best Practices for Reliable Tests

Start From What Really Matters

The fewer timing assumptions, the more stable the test. Prioritize:

  • Always assert the final result users see, not internal elements.
  • Use cy.intercept() and cy.wait("@alias") for network flows.
  • Set defaultCommandTimeout to what your app needs, don't force a short timeout.
  • Avoid cy.wait(ms) with guessed numbers.

Example of a Stable Test

JSStable test pattern
it("displays search results", () => {
  cy.intercept("GET", "/api/search*").as("search");
 
  cy.get("[data-cy=query]").type("book");
  cy.get("[data-cy=search]").click();
 
  cy.wait("@search");
  cy.get("[data-cy=results]").should("have.length.gt", 0);
});

Here cy.wait("@search") waits for the API response to finish, then the assertion ensures the results are actually rendered. There isn't a single random sleep — every step waits until its condition is genuinely true.

Closing

Episode 5 changed the way you think about timing: Cypress waits for conditions, not for guessed time. Automatic waiting works through retry-ability — element-finding commands and assertions are retried until they succeed or time out. Timing issues are handled by waiting for the final condition, not random cy.wait(ms).

The key takeaways:

  • Automatic waiting eliminates the need for manual sleeps.
  • defaultCommandTimeout (default 4000 ms) bounds the wait; it can be overridden per command.
  • Only find-and-assertion commands are retried, not actions or cy.visit.
  • cy.wait(ms) with guessed numbers is a source of flakiness — avoid it.
  • Wait for network events via cy.intercept() for async flows.

In the next episode, episode 6, we will cover fixtures and test data — managing test data with fixtures, loading JSON via cy.fixture(), data-driven testing with external datasets, and mocking data and request stubs. Your tests will start separating data from logic.

Learn Cypress - Automatic Waiting & Retries | Learn Cypress