Skip to content

Cypress Waiting

Waiting correctly is the difference between a stable Cypress suite and a flaky one. This lesson introduces the core philosophy behind Cypress waiting before diving deeper into retries, timeouts, and cy.wait() in later lessons.

Cypress's Waiting Philosophy

Older browser automation tools often required developers to add explicit waits, sleep(2000) or similar, guessing how long an async operation might take. Guess too short, and the test is flaky; guess too long, and the whole suite runs unnecessarily slowly.

Cypress's design goal is to eliminate almost all of these manual waits by making its query and assertion commands automatically retry against the actual DOM/network state, waiting exactly as long as needed and no longer.

// Anti-pattern from other tools: guessing a fixed wait time
// sleep(2000)

// Cypress equivalent: wait for the real condition, not a fixed time
cy.get('[data-cy="results"]').should('have.length.greaterThan', 0);

The second version waits only as long as actually needed for results to appear, whether that's 200ms or 3 seconds.

Where Automatic Waiting Applies

cy.get(selector)             // retries until found (or times out)
.should(assertion)           // retries until it passes
.click() / .type()           // waits for actionability first
cy.intercept() + cy.wait('@alias')  // waits for a specific network call
  • Query commands automatically retry when chained with an assertion.
  • Action commands wait briefly for the target to become actionable before acting.
  • cy.wait('@alias') waits for a specific aliased network request, covered in a later lesson.
  • Nearly every built-in command already has sensible waiting behavior without extra configuration.

Waiting Strategy Cheat Sheet

Preferred waiting strategies mapped to common scenarios.

Scenario Preferred Approach
Waiting for an element to appear cy.get(x).should('exist')
Waiting for text/state to update cy.get(x).should('have.text', y)
Waiting for a specific API call cy.wait('@aliasName')
Waiting for a fixed amount of time Avoid; use an assertion-based wait instead
Waiting for navigation cy.url().should('include', y)

Assertion-Based Waiting Is the Default Pattern

The single most important habit for writing stable Cypress tests is expressing waits as assertions about the state you actually care about, rather than as fixed time delays. If you find yourself wanting to "wait a couple seconds" for something, there is almost always a more specific condition you can assert on instead.

  • Ask: "what observable DOM or network state indicates this is actually done?"
  • Express that state as a .should() assertion instead of a time delay.
  • Reserve fixed waits for the rare cases with no observable signal at all (and treat that as a code smell to investigate).

Waiting for a Page to Fully Load

cy.visit() already waits for the page's load event before resolving, so you generally do not need extra waiting just to confirm a page "loaded." What usually still needs an explicit assertion is client-side data that arrives after an initial async request.

cy.visit('/dashboard');
// cy.visit already waited for the page load event.
// Now wait specifically for the data-dependent content:
cy.get('[data-cy="dashboard-summary"]').should('be.visible');

Common Mistakes

  • Adding cy.wait(2000) as a default habit whenever a test seems flaky, without identifying the real condition being waited on.
  • Assuming cy.visit() alone guarantees all client-side data has finished loading.
  • Treating every timing issue as something only a longer wait can fix, instead of a more specific assertion.
  • Not distinguishing between waiting for page navigation and waiting for asynchronous data within that page.

Key Takeaways

  • Cypress is designed to eliminate manual, fixed-time waits through automatic retrying.
  • Assertion-based waiting (waiting for a real condition) should be the default strategy.
  • cy.visit() waits for page load, but not necessarily for async data fetched afterward.
  • A perceived need for "more waiting" is usually a sign a more specific assertion is missing.

Pro Tip

Whenever you're tempted to add a fixed wait, ask "what does the DOM look like right before this becomes true, and right after?" That difference is almost always assertable, and turning it into a .should() check is more reliable and usually faster than any fixed delay.