Which of a ported suite's waits vanish into Cypress retry-ability, and which do not?
answer
- Ask which waits were about the DOM
- Queries retry, and assertions retry with them
- One timeout covers most DOM commands
- A network wait gets an alias, not a duration
- Nothing outside the browser retries itself
basics
~20 sWaits for an element to appear, become visible or change text vanish: cy.get() and .should() retry inside defaultCommandTimeout. Network waits become cy.intercept() plus cy.wait('@alias'). Waits on state outside the browser do not vanish at all.
solid answer
~50 sMost of them vanish, and that is the main payoff of the move. `cy.get('[data-cy=spec-row]')` retries until it matches, and a chained `.should()` retries the query until the assertion passes — both inside `defaultCommandTimeout`, **4000 ms** by default. So a wait for presence, visibility, enabled state, text or a URL change is not translated at all; it becomes the assertion you were going to write anyway, and a fixed sleep is deleted rather than turned into `cy.wait(3000)`. Three kinds survive in a new shape. A wait for one specific request becomes `cy.intercept()` plus `cy.wait('@alias')`, which is deterministic rather than timed. A page transition is already covered by `pageLoadTimeout` on `cy.visit()`. And a wait on something outside the browser — a queue draining, a file being written — has no DOM to retry against, so it needs `cy.request()` or `cy.task()`.
code
javascript · 13 linesdescribe('run dashboard', () => {
beforeEach(() => {
cy.intercept('GET', '/api/runs*').as('runs')
cy.visit('/runs')
cy.wait('@runs')
})
it('queues the failed specs again', () => {
cy.get('[data-cy=spec-row]').should('have.length', 12)
cy.contains('button', 'Re-run failed').click()
cy.get('[data-cy=run-status]', { timeout: 20000 }).should('have.text', 'Queued')
})
})go deeper
Be ready to name the two commands that do the waiting for you — cy.get() retrying until it matches, and .should() retrying until the assertion passes — and to say that a fixed sleep is deleted rather than converted.
Explain that retry-ability is bounded by defaultCommandTimeout at 4000 ms and exits as soon as the condition holds, and be able to sort a list of old waits into the ones that vanish and the ones that become an aliased cy.wait().
Show that you know a faithful line-by-line port carries the old flake across, because assertions parked inside .then() never retry. Talk about how you would spot that pattern in a review of a large migration.
Own the standard the migrated suite waits by: aliased requests over durations, per-command timeouts over a global raise, and a rule about what a test may poll outside the browser.
## What retry-ability absorbs In Cypress a query command such as `cy.get('[data-cy=spec-row]')` does not run once and give up. It re-queries the DOM until it matches or its budget runs out, and an assertion chained onto it — `.should('be.visible')`, `.should('have.length', 12)` — re-runs the query until the assertion passes. That budget is `defaultCommandTimeout`, **4000 ms** unless the project changes it, and retry-ability finishes the command the instant the condition holds, so the number is a ceiling rather than a duration. That one behaviour removes most of an old suite's synchronisation code: - A wait for an element to exist is just `cy.get(selector)`. - A wait for it to become visible is `cy.get(selector).should('be.visible')`. - A wait for it to become enabled is usually nothing at all, because Cypress checks actionability before `.click()` and retries until the element can be clicked. - A wait for text to settle is `.should('have.text', 'Queued')` or `.should('contain', 'Queued')`. - A wait for the address to change is `cy.url().should('include', '/runs/482')`. - A fixed sleep is **deleted**, not converted; the documentation is explicit that it should not reappear as `cy.wait(<number>)`. Name the shape of the change, because it is what the interviewer is listening for: the wait and the check stop being two lines and become one. You do not translate the wait — you write the condition, and the wait is implied by it. ## The waits that survive in a new shape Three kinds do not disappear, and a migration plan that assumes everything does will stall on them. | The old wait was for… | In Cypress it becomes | Governed by | |---|---|---| | an element, its visibility, text or count | a query plus `.should()` | `defaultCommandTimeout` (4000 ms) | | a page transition | `cy.visit()`, `cy.go()`, `cy.reload()` | `pageLoadTimeout` (60000 ms) | | one specific network call | `cy.intercept()` then `cy.wait('@alias')` | `requestTimeout` (5000 ms), `responseTimeout` (30000 ms) | | work happening outside the browser | `cy.request()` or `cy.task()` | `taskTimeout` (60000 ms) for tasks | The network row is where the port pays off most. A wait on a duration is a guess about the backend; an alias is a fact about it. `cy.intercept('GET', '/api/runs*').as('runs')` followed by `cy.wait('@runs')` resolves when that request actually completes, which is faster on a fast day and still correct on a slow one. The last row is the one teams forget. **Cypress retries the DOM, not the world.** If the old step waited for an import job to drain a queue, no assertion on the page retries that into existence: you poll the source, with a `cy.task()` that loops in Node or a small recursive helper around `cy.request()`. Chaining `.should()` onto `cy.request()` does not help, because only query commands retry — the request is issued once. ## Why a mechanical port keeps the old flake The failure mode of a careful, faithful translation is that it preserves the shape that made the old suite flaky in the first place: 1. **Assertions moved inside `.then()`.** A `.then(rows => expect(rows).to.have.length(12))` callback runs against whatever the DOM held at that moment. The retry lives in `.should()`, so a literal "find, then assert" port throws away the mechanism the team migrated for. 2. **Command results assigned to variables.** Reading a "result" out of a queued command is the same timing guess in new syntax. 3. **Sleeps translated instead of deleted.** A converted sleep is slower than the assertion it replaced on every good run and no more reliable on a bad one. The rule that keeps a port honest: the last thing in a chain should be an **assertion about the state you care about**, not a callback that inspects it. ## Sizing the budget while you translate - Leave `defaultCommandTimeout` at its default during the port, so genuinely broken steps fail fast and loudly instead of hiding inside a long budget. - Where one step is legitimately slow, pass `{ timeout: ms }` on that command rather than raising the global setting for everything. - Where a step is slow because it is waiting on the backend, alias the request instead of stretching a timeout. - Treat a step that needs a large timeout and has no request to alias as a question about the application, not about the test.
- A ported step waited for a spinner to disappear. What is the Cypress form?`cy.get('[data-cy=spinner]').should('not.exist')`. The assertion retries until the element leaves the DOM, so the wait and the check are the same command. If the element stays in the DOM and only hides, `.should('not.be.visible')` is the honest assertion instead.
- Where does an old sleep go when nothing on the page changes?It usually means the condition lives outside the DOM. Poll the source rather than guessing a duration: a `cy.task()` that loops in Node until the queue drains, or a small recursive helper around `cy.request()`. Chaining `.should()` onto `cy.request()` will not re-issue it, because only query commands retry.
The old suite scheduled the looking; Cypress keeps looking until the condition holds or the budget runs out. You stop writing waits and start writing conditions.
saying these in an interview costs you the question
- Translates every explicit wait into cy.wait with a number
- Replaces a sleep with cy.wait(3000) instead of asserting the state
- Believes .then() retries the way .should() does
- Assumes defaultCommandTimeout also governs cy.visit()
- Claims Cypress waits automatically for backend work outside the browser