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.
You now understand the philosophy behind Cypress waiting. Next, look closer at Automatic Retry and exactly how it works internally.