skip to content

Retry-Ability

Cypress re-runs some steps until they succeed and runs others exactly once. Interviewers probe it because knowing which is which is the difference between a stable suite and a guessy one.

on this pageshow

explore

questions

9

In Cypress, what is the difference between a query command and an action command?

level: juniorimportance: must knowfreq 78%

answer

  1. Three kinds of step, not one
  2. One kind re-runs, one fires once
  3. Reads are safe to repeat
  4. Actions change the app under test
  5. cy.get() and .find() versus .click()

basics

~20 s

A Cypress query such as cy.get() or .find() is a repeatable read: it re-runs and relinks the chain until the next assertion passes. An action such as .click() changes the app, so it fires once and never relinks.

solid answer

~50 s

Cypress sorts every step into three kinds. **Queries** — `cy.get()`, `cy.contains()`, `.find()`, `.eq()`, `.first()`, and since Cypress 16 `cy.getCookie()` and `cy.getAllLocalStorage()` — are synchronous, side-effect-free reads. They link into a chain that Cypress re-runs from the top, so the subject is re-resolved against the page as it is now on every attempt. **Assertions**, `.should()` and `.and()`, are a special kind of query: they judge the subject and keep the queries in front of them retrying until they pass or the command times out. **Actions** such as `.click()`, `.type()`, `.check()` and `.select()`, and other non-queries such as `cy.visit()` and `cy.request()`, change something, so Cypress runs them exactly once. Cypress re-runs the queries leading up to an action while it waits for the element to become actionable, but once the action has fired nothing before it retries again.

go deeper

for a junior

Be ready to sort a short Cypress chain into queries and actions on the spot, and to name cy.get(), .find() and .first() on one side against .click() and .type() on the other.

for a middle

Explain why repeating a read is safe and repeating a click is not, and say exactly what Cypress re-resolves on each retry attempt.

for a senior

Show how you use the split when a suite goes flaky — where a chain has to be broken so that a re-render cannot hand an action an element that no longer exists.

for a principal

Own the standard: decide which shared steps in your suite are allowed to be queries, and be clear about what the team gives up whenever a helper turns out to be an action.

## Three kinds of step, not one Everything you chain off `cy` in a Cypress test is a command, but Cypress runs commands by three different rules, and knowing which rule applies to a given step is most of what "retry-ability" means in practice. - **Queries** read the page or the browser without changing it. `cy.get()`, `cy.contains()`, `.find()`, `.filter()`, `.not()`, `.eq()`, `.first()`, `.last()`, `.children()`, `.parent()`, `.siblings()`, `.closest()`, `.shadow()`, `cy.root()`, `cy.focused()`, `cy.url()`, `cy.readFile()` are queries, and as of Cypress 16 so are the browser-state reads `cy.getCookie()`, `cy.getCookies()`, `cy.getAllCookies()`, `cy.getAllLocalStorage()` and `cy.getAllSessionStorage()`. - **Assertions** are `.should()` and `.and()`. Cypress treats them as a special kind of query that is drawn differently in the Command Log. They never touch the application; they judge the current subject and hold the linked queries in front of them in a retry loop until the judgement passes. - **Non-queries** are everything else, and the interesting members are the actions: `.click()`, `.dblclick()`, `.rightclick()`, `.type()`, `.clear()`, `.check()`, `.uncheck()`, `.select()`, `.trigger()`, `.selectFile()`. Commands such as `cy.visit()`, `cy.request()`, `cy.task()` and `cy.intercept()` are non-queries too. ## Why the split exists A query is a **pure read**, so Cypress is free to run it as many times as it likes. That is the whole trick: instead of holding a reference to an element and hoping the framework does not replace it, Cypress throws the reference away and re-resolves the selector on every attempt. An action is the opposite. Clicking a **Borrow** button in a library catalogue posts a loan; typing into the search box fires input events. Running either of those twice would be a different test, so Cypress runs each one exactly once and never replays it. The practical payoff is that Cypress hands your assertions a subject that was resolved moments ago rather than one captured at the start of the statement. Elements go stale far less often here than in a runner that resolves a handle once and then operates on it, because the handle is re-derived on every attempt of the loop. ## The rules side by side | kind | examples | how often it runs | relinks on a retry | |---|---|---|---| | Query | `cy.get()`, `.find()`, `.first()`, `cy.getCookie()` | as many times as the retry loop needs | yes, re-resolved from the top of the chain | | Assertion | `.should()`, `.and()` | until it passes or the command times out | it is what drives the relinking | | Action | `.click()`, `.type()`, `.check()` | exactly once | no | | Other non-query | `cy.visit()`, `cy.request()`, `cy.task()` | exactly once | no | ## Reading a chain in a catalogue spec Take this fragment of a library catalogue test: ```javascript cy.visit('/catalogue') cy.get('#catalogue-search').type('dune{enter}') cy.get('[data-cy=catalogue-results]').find('.book-row').should('have.length', 5) ``` Sorted by kind, that is: 1. `cy.visit('/catalogue')` — a non-query. It runs once. 2. `cy.get('#catalogue-search')` — a query. It retries until the search box exists, then hands it on. 3. `.type('dune{enter}')` — an action. It fires once, into whatever element the query in front of it resolved to. 4. `cy.get('[data-cy=catalogue-results]')` and `.find('.book-row')` — two linked queries. 5. `.should('have.length', 5)` — an assertion that keeps both of those queries re-running until five rows exist. Nobody wrote a wait anywhere in that snippet, and none is needed: the last line keeps re-reading the results container and its rows for as long as the assertion is failing. ## Actions do get fresh elements — but only beforehand The one nuance worth carrying into an interview is that an action is not blind to a changing page. Before `.type()` or `.click()` fires, Cypress waits for the element to become actionable, and while it waits it **keeps re-running the query chain leading up to the action**, so the element it finally acts on is freshly resolved. What stops is the replay *afterwards*: once the action has fired, Cypress will not re-run any query that came before it. That is why the shape of a Cypress chain matters, and why actions belong at the end of a chain rather than in the middle of one. ## Things people get wrong - **"Cypress retries everything."** It does not. Only queries and assertions are retried; actions and other non-queries execute once. - **"A retry re-runs my test."** No — the retry loop covers a linked run of queries plus the assertion gating them, not the test body. - **"Reading a cookie is a one-shot thing."** It was, until Cypress 16 made the cookie and storage reads queries that re-read and retry like any other.

  • In Cypress, is .should() a query or an action?
    Neither, strictly — Cypress classes assertions as a special type of query. `.should()` and `.and()` never touch the application; they judge the current subject and force the linked queries in front of them to re-run until the judgement passes or the command times out. They are drawn distinctly in the Command Log.
  • In Cypress 16, which cookie and storage commands are queries and which are not?
    The reads are queries: `cy.getCookie()`, `cy.getCookies()`, `cy.getAllCookies()`, `cy.getAllLocalStorage()` and `cy.getAllSessionStorage()` re-read and retry under `defaultCommandTimeout`. The writes and clears — `cy.setCookie()`, `cy.clearCookie()`, `cy.clearCookies()`, `cy.clearAllCookies()` — remain one-shot commands governed by `responseTimeout`.

A query is like re-reading the shelf label every time you glance at it — harmless however often you do it. An action is taking the book off the shelf: you only get to borrow it once.

saying these in an interview costs you the question

  • Says every Cypress command retries until it passes
  • Thinks .click() is retried the way cy.get() is
  • Cannot name a single Cypress query command
  • Believes a failed assertion re-runs the whole test body
  • Calls cy.visit() a query because it loads a page
open as a page

In Cypress, how many times does a .then() callback run while a later assertion retries?

level: middleimportance: must knowfreq 62%

basics

~10 s

Exactly once. .then() is not a query, so Cypress never re-runs it or anything before it; an assertion after it keeps retrying against the single value the callback already yielded, then times out.

open as a page

In Cypress, when a .should() at the end of cy.get().find() fails, what re-runs?

level: middleimportance: must knowfreq 66%

basics

~10 s

The 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.

open as a page

A Cypress test that borrows the last copy of a book passes, then fails on every retry. Why?

level: seniorimportance: must knowfreq 42%

basics

~20 s

A Cypress retry repeats only the test and its beforeEach and afterEach hooks. Suite-level seeding in a before hook is not repeated, and the backend is not rewound, so the second attempt runs against a catalogue whose last copy is already on loan.

open as a page

In Cypress, what do retries.runMode and retries.openMode control, and what are their defaults?

level: juniorimportance: should knowfreq 55%

basics

~20 s

They set how many extra attempts Cypress gives a failing test: runMode applies to cypress run, openMode to cypress open. Both default to 0, so an unconfigured suite never retries a failing test at all.

open as a page

In Cypress, why can a .should() in the middle of a chain leave the next query with a stale element?

level: seniorimportance: should knowfreq 46%

basics

~20 s

A mid-chain assertion is a retry boundary. Once it passes, Cypress locks in the subject at that point, so later queries retry from that element instead of re-querying from the top - and a re-render leaves it detached.

open as a page

Which shared Cypress helpers should be registered with Cypress.Commands.addQuery()?

level: principalimportance: should knowfreq 36%

basics

~20 s

Only helpers that are a pure, synchronous, repeatable read of the page or browser state. Anything that acts, awaits, or drives other cy commands must be a command — and a command stops the chain relinking after it.

open as a page

Should a Cypress test behave differently when Cypress.currentRetry is greater than zero?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Almost never for anything that changes what is asserted. Branching on the attempt number makes the retry a different test, so a green second attempt no longer tells you the first failure was environmental. Extra diagnostics are the defensible exception.

open as a page