skip to content

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

level: principalimportance: nice to knowfreq 22%

answer

  1. The attempt number is readable in a test
  2. Two attempts should be one experiment
  3. Observability versus assertions
  4. A green retry must still mean something
  5. Zero on the very first run

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.

solid answer

~40 s

`Cypress.currentRetry` is `0` on the first execution and increments per retry, readable inside a test or hook. The value exists for observability, and that is where it should stay. The moment a test asserts less, skips a step, or waits differently because it is on attempt two, the two attempts stop being the same experiment — a pass on the retry proves only that the weakened version passes, which is precisely the information the retry was supposed to give you. Defensible uses keep every assertion identical: emitting extra `cy.log()` context, taking an additional `cy.screenshot()`, turning up stub verbosity. Anything that repairs state the first attempt consumed is better done unconditionally in `beforeEach`, so every attempt starts the same way and nobody has to reason about which variant ran.

go deeper

for a junior

Know that the attempt number is readable inside a test and starts at zero, and that changing what a test checks based on it is not normal practice.

for a middle

Be ready to explain why an attempt-dependent branch destroys the inference a retry exists to support, and what belongs in beforeEach instead.

for a senior

Expect to find one of these branches in an inherited suite and to argue for removing it, including what you would fix so the branch is no longer tempting.

for a principal

Own the standard and its enforcement: where the attempt number may appear at all, and what happens to a specification that only passes when it knows it is being retried.

## What the value is, and what it invites `Cypress.currentRetry` is a number available inside a test or a test hook: `0` on the first execution, `1` on the first retry, and so on; it is `null` outside a test or hook. The documentation itself frames it as a low-level detail you would not ordinarily need — and the reason to be careful with it is not the API, it is what people write once they have it: ```javascript if (Cypress.currentRetry > 0) { // ... something the first attempt did not do } ``` Every branch of that shape is a decision about what a green run is allowed to mean. ## The cost of branching on the attempt number A retry earns its keep by being **the same test, run again**. That is what makes the second result informative: if an identical test passes on a rerun, the difference was in the environment, not in the test. Branch on the attempt number and that inference collapses. - **The experiment changes mid-flight.** Attempt one and attempt two are now different tests, and the report shows a single outcome for both. - **The weakest variant becomes the one that decides the run**, because it is the last to execute. - **Readers stop trusting the spec.** Anyone debugging has to work out which branch ran before they can interpret the failure they are looking at. - **The branch is nearly unreviewable.** It only executes in the run where nobody is watching. ## The narrow set of defensible uses These share one property: the assertions are byte-for-byte identical on every attempt. 1. **Extra evidence.** `cy.log()` with the attempt number, an additional `cy.screenshot()` on a retry, or a more verbose stub so the failing attempt leaves more behind. 2. **Recognising the situation in a report.** Recording that a specification is passing only on later attempts, so the pattern is visible rather than folded into a green tick. 3. **Re-establishing setup the first attempt consumed** — reseeding through `cy.task()`, forcing a fresh `cy.session()`. Legitimate in effect, but better written unconditionally in a `beforeEach` so every attempt runs identical setup and no branch exists at all. ## What to forbid outright - Relaxing or removing an assertion on a later attempt. - Skipping a step — a navigation, a form field, a check — to make the retry cheaper or likelier. - Waiting differently only on the retry, so the test's timing contract depends on the attempt. - Taking a different path through the application on the retry, so the covered flow varies by run. Each of these converts a real defect into a green run, and the defect then stops being visible to anyone. ## What the branch is usually standing in for An attempt-dependent branch is nearly always a workaround for something else, and naming that thing is more productive than arguing about the branch: - *"The data is gone on the retry."* The setup belongs in a per-test hook rather than a suite-level one, so every attempt seeds what it needs. - *"It is slower on the shared runner."* The suite is being asked to absorb an environment difference inside a conditional in a test file, which is the least visible place to put it. - *"It only fails the first time the specification runs."* Something outside the test is warming up, and the branch hides that from everyone who reads the report. In each case the conditional treats a symptom in the one place nobody reviews carefully: the path that executes only when the run is unattended. ## A standard a team can actually hold | Use | Verdict | Why | |---|---|---| | Extra logs or screenshots on a retry | Allowed | Adds evidence, changes no outcome | | Reseeding data on a retry | Prefer unconditional setup | Same effect, no branch to reason about | | Weakening an assertion on a retry | Forbidden | A pass no longer means the behaviour works | | Skipping steps on a retry | Forbidden | Coverage silently varies between attempts | Two rules follow, and both are cheap to enforce in review: 1. `Cypress.currentRetry` may appear in **observability code only**; a reviewer seeing it inside a conditional that guards an assertion or an action asks for it to be removed. 2. A test that needs the attempt number to pass is not a flaky test, it is a **broken or unrepeatable** one. Fix its setup so every attempt is identical, or take that specification out of the retrying population rather than making its second attempt easier.

  • What is Cypress.currentRetry outside a test or hook?
    It is `null`. The value is only meaningful while a test or a test hook is executing, so reading it at the top of a spec file or in plugin code gives you nothing. If you need it for logging, read it inside the `beforeEach`, the `afterEach`, or the test body.
  • If reseeding on a retry is acceptable, why prefer doing it unconditionally?
    Because an unconditional `beforeEach` makes every attempt identical, which is the property that makes a retry's result interpretable. A conditional reseed leaves two variants of the test in the file, and the one that decides the run is the one nobody watched execute.

saying these in an interview costs you the question

  • Relaxes an assertion when Cypress.currentRetry is above zero
  • Skips setup steps on later attempts to save pipeline time
  • Treats attempt-dependent behaviour as harmless test tuning
  • Reads Cypress.currentRetry outside a test and expects a number
  • Sees no difference between logging on a retry and asserting differently