Cypress automatically waits for elements to exist and for assertions to pass, retrying commands for a configurable timeout (default 4 seconds) before failing — so you rarely need explicit waits.
Most Cypress commands (cy.get, cy.contains) and their chained assertions are 'retry-able': Cypress re-queries the DOM and re-checks the assertion repeatedly until it passes or the timeout elapses. This handles asynchronous rendering, animations, and network updates without sleeps. Actionability checks (visible, not disabled, not covered) also retry before commands like .click(). The key rule: put your assertion on the SAME chain as the query so Cypress retries the whole thing. Storing cy.get() results in a variable breaks retry-ability because the reference goes stale.
A test asserts a toast message appears after an async save: cy.get('[data-cy=toast]').should('contain', 'Saved'). Cypress retries the get+assertion for up to 4s, so it passes as soon as the toast renders — no cy.wait needed.
A tester writes const btn = cy.get('#submit'); btn.click(); btn.click(); and gets a 'detached from DOM' error. Why?