Chaining and Retry-ability
Every Cypress command returns a chainable subject that the next command in the same statement automatically receives. Writing cy.get('.card').find('.title').should('contain', 'Cypress') builds a single command chain where each command operates on exactly what the previous one yielded, letting you drill from a broad selection down to a specific assertion in one readable line.
Cricket analogy: A relay of fielders throwing the ball from the boundary to the keeper, each one receiving exactly what the last one threw, mirrors how cy.get('.card').find('.title') passes its resulting subject directly into the next chained command.
How the Command Queue Works
Cypress commands aren't promises you await directly; they're enqueued into an internal command queue when your test function runs, and Cypress's driver executes them one at a time, waiting for each to resolve (including any retries) before starting the next. This is why placing a console.log() between chained commands logs immediately, before the commands themselves have actually executed.
Cricket analogy: A bowling coach writes out today's entire training plan before the session starts, and each drill runs only when its turn comes, similar to how Cypress enqueues cy.get(), cy.click(), and cy.type() as a plan and executes them one at a time, not instantly when written.
cy.get('.todo-list')
.find('li')
.should('have.length', 3)
.and('contain', 'Buy milk');Retry-ability: Assertions Re-run Automatically
When you chain .should() or .and() onto a command like cy.get(), Cypress doesn't just check the assertion once. It re-runs the preceding command (re-querying the DOM) and re-evaluates the assertion repeatedly until it passes or the timeout is reached. This is what lets a test written immediately after a button click reliably wait for a success message that appears only after an async API call resolves.
Cricket analogy: DRS keeps re-analyzing ball-tracking data until it can confidently rule lbw, rather than accepting the first frame it sees, similar to how cy.get('.spinner').should('not.exist') keeps re-querying the DOM until the assertion passes or the timeout runs out.
Retry-ability applies to the command immediately preceding a should()/and() chain, not to every command in the test. Commands like cy.click() or cy.type() run once; it's the querying commands like cy.get() combined with an assertion that retry.
Where Retry-ability Breaks Down
Using cy.then(($el) => { ... }) hands you the current subject as a one-time snapshot for imperative code, but Cypress has no way to know what condition inside that callback should be true, so it won't re-run the callback automatically. Any assertion made on data pulled out inside .then() needs its own explicit retry logic or should be moved before the .then() into a chained should().
Cricket analogy: Once a batter is given out and walks off, no amount of replaying the same delivery changes that recorded outcome, similar to how cy.then(($el) => {...}) captures a snapshot of the element at that instant, so it won't automatically re-run if the DOM later changes.
Resist the urge to add cy.wait(3000) to 'fix' a flaky test. Arbitrary waits either aren't long enough on a slow CI run or waste time on a fast one. Let Cypress's built-in retry-ability (should()/and()) wait for the actual condition instead of a fixed duration.
- Cypress commands chain by passing their yielded subject to the next command in the same statement.
- Commands are enqueued and run sequentially from an internal queue, not the instant they're written in code.
- should() and and() re-run the preceding command and re-check the assertion until it passes or times out.
- Retry-ability applies to the command right before should()/and(), not the entire test.
- cy.then() captures a one-time snapshot of the subject and does not automatically retry.
- Avoid cy.wait(ms) as a fix for timing issues; rely on retry-able assertions instead.
Practice what you learned
1. What does a Cypress command chain like cy.get('.list').find('li') do?
2. When are Cypress commands actually executed?
3. What makes should() different from a plain one-time check?
4. Why doesn't code inside cy.then() automatically retry?
5. What's the recommended alternative to cy.wait(3000) for handling timing issues?
Was this page helpful?
You May Also Like
Selecting Elements with cy.get()
Learn how Cypress's cy.get() command queries the DOM using CSS selectors, and how to choose selectors that are stable, readable, and resistant to markup changes.
Interacting with Elements (type, click, check)
Cover Cypress's core interaction commands — type(), click(), check(), select() — and the actionability checks Cypress performs before firing each one.
Assertions with should() and expect()
Learn the difference between Cypress's retrying should()/and() assertions and Chai's one-time expect() assertions, and when to use each.