In Cypress, when a .should() at the end of cy.get().find() fails, what re-runs?
answer
- Retrying is not per command
- Where does the re-run begin?
- The chain restarts from its top
- The DOM is re-read, never cached
- defaultCommandTimeout bounds the loop
basics
~10 sThe whole linked run of queries re-runs from the top. Cypress re-executes cy.get() against the current DOM, feeds the result into .find(), and re-checks the assertion, looping until it passes or defaultCommandTimeout expires.
solid answer
~40 sQueries link up and retry as a single unit. When the trailing assertion on `cy.get('[data-cy=catalogue-results]').find('.book-row').should('have.length', 5)` fails, Cypress neither re-evaluates the assertion on its own nor restarts the test. It goes back to the top of that linked run of queries, re-executes `cy.get()` against the DOM as it is at that instant, feeds the fresh result into `.find()`, and re-checks `have.length`. Because every attempt starts from a fresh `cy.get()`, a row that the framework replaced between attempts is simply found again rather than reported as detached. The loop repeats until the assertion passes or the command's timeout runs out — `defaultCommandTimeout`, 4000 ms by default — and the failure message then prints the whole subject chain it was retrying. Nothing outside that chain repeats: statements earlier in the test have already completed.
code
javascript · 9 lines// cypress/e2e/catalogue.cy.js
it('shows five matching books', () => {
cy.visit('/catalogue') // non-query: runs once
cy.get('#catalogue-search').type('dune{enter}')
cy.get('[data-cy=catalogue-results]') // query, replayed
.find('.book-row') // query, replayed
.should('have.length', 5) // assertion drives the loop
})go deeper
Learn that a chained Cypress assertion keeps the query in front of it running rather than failing on the first look, so you do not reach for a manual wait.
Be able to walk one retry attempt step by step and say where it restarts from — the top of the linked queries, against a freshly read DOM.
Use the model to read failures: a timeout that names the subject chain tells you which link stopped producing elements, which is usually not the assertion that reported the failure.
Decide how much your team leans on this loop versus asserting on an explicit application signal, and where that trade-off makes a suite slow to fail rather than fast to diagnose.
## The unit of retry is the linked run of queries The common mental model — "Cypress retries the failing command" — is too small. The unit Cypress retries is a **linked run of queries terminated by an assertion**. Queries chain into each other, each one taking the previous query's output as its subject, and Cypress treats that whole run as one retriable expression. Consider a library catalogue spec: ```javascript cy.get('[data-cy=catalogue-results]') // query .find('.book-row') // query .should('have.length', 5) // assertion ``` Cypress cannot ask "are there exactly five `.book-row` elements?" once and be done, because the results may still be rendering when the statement is reached. So it treats the three steps as a loop. ## What one attempt actually does 1. Run `cy.get('[data-cy=catalogue-results]')` against the document **as it is right now**, discarding whatever it found last time. 2. Pass the resulting jQuery collection into `.find('.book-row')`, which searches inside it and produces a new collection. 3. Hand that collection to `.should('have.length', 5)`. 4. If the assertion passes, the statement is finished and the subject is whatever the last query yielded. 5. If it fails, wait a short interval and go back to step 1 — **the top of the chain**, not step 2. That step 5 is the whole answer. Cypress does not keep the elements it found on the previous attempt; it re-resolves the selector from scratch each time. ## Why relinking beats holding a reference If the runner cached the elements from the first attempt and only re-ran the assertion, every re-render would break the test. In a catalogue that re-renders its results list after the search response lands, the rows from attempt one are detached nodes by attempt two, and asserting on them would report a stale-element failure that has nothing to do with the behaviour under test. Because each attempt re-derives the subject, the failure modes collapse to two honest ones: - The elements genuinely never appear, and the command times out with a message naming the subject chain it kept retrying. - The elements appear but the assertion is genuinely false — five rows expected, three rendered. ## Where the retry window starts and stops The window is bounded on both sides, and this is what people miss: - It **starts** at the first query after the last non-query. `cy.visit()` and any action before the chain have already run; they are not part of the loop. - It **ends** at the assertion. Once `.should()` passes, that statement is complete and Cypress moves on. - It covers **only queries and assertions**. A non-query inside the run stops the linking, because Cypress will not replay something that changes the application. | step | kind | replayed on each attempt? | |---|---|---| | `cy.visit('/catalogue')` | non-query | no — already completed | | `cy.get('[data-cy=catalogue-results]')` | query | yes | | `.find('.book-row')` | query | yes | | `.should('have.length', 5)` | assertion | yes — it drives the loop | ## Timeouts, and the cost of passing first time The loop is bounded by the command's timeout. By default that is `defaultCommandTimeout`, which is 4000 ms. When the timeout expires the command fails and prints the chain it was retrying, so a failure tells you which link stopped producing elements rather than just which assertion was false. There is no cost when the page is already correct: if the assertion passes on the first attempt, nothing repeats and Cypress advances immediately. Retry-ability is a recovery mechanism for a page that is still settling, not a delay added to every statement. ## Implicit assertions keep the loop alive too Even without a `.should()`, a query carries a built-in expectation that it yields something. `.eq(3)` will keep retrying until an element at index 3 exists; `cy.get('.book-row')` will keep retrying until at least one row matches. So a bare chain of queries is still a retrying chain — the assertion you did not write is "this subject exists", and it re-runs the linked queries from the top exactly the same way. This is why a Cypress test that never mentions waiting still copes with a catalogue whose rows arrive after a network round trip. The default expectation attached to `cy.get('.book-row')` keeps the chain looping while the list is empty, and the moment the first row is painted the query yields it and the statement completes. You only need to write an explicit `.should()` when the condition you care about is stronger than existence — a count, a piece of text, a class.
- In Cypress, does the retry loop also re-run cy.visit() or an earlier .type() in the same test?No. Only the linked run of queries directly in front of the failing assertion re-runs. `cy.visit()` and `.type()` are non-queries that already completed, and Cypress replays the queries after them rather than the test body.
- In Cypress, what does the loop cost when the assertion passes on the first attempt?Nothing repeats. The linked queries run once, the assertion passes, and Cypress moves to the next command immediately. Retry-ability only spends time while the assertion is failing, so it is not a delay added to every statement.
saying these in an interview costs you the question
- Says only the last query retries, not the chain
- Thinks a failed assertion re-runs the whole test
- Believes cy.get() caches elements between attempts
- Claims the assertion alone is re-evaluated