skip to content

How far should a Cypress suite go to work around not being able to store command results?

level: principalimportance: should knowfreq 38%

answer

  1. Three reasons a spec wants a value
  2. Most of them are really assertions
  3. Knowing beats discovering
  4. Watch the nesting depth in review
  5. A test should not recompute the product

basics

~20 s

Only as far as the chain naturally goes. Treat deep callback nesting as a signal that the spec is discovering what it should already know, and move that knowledge into seeded data or a retried assertion.

solid answer

~50 s

The constraint is real: a Cypress command yields nothing you can hold, so a spec that needs a value has to either read it inside a callback or already know it. My standard ranks those. **First**, prefer an assertion that never extracts the value at all — `.should('have.text', '2 copies available')` is retried and reads as a specification. **Second**, if a later step genuinely needs a value the app computed, seed it: create the loan through an API call so the spec knows the due date up front. **Third**, and only then, read it inside a callback and keep that callback short. What I refuse is machinery built to simulate variables — helper layers that pass values between chains, or specs nested three callbacks deep — because it converts a readable specification into a program that reimplements the application's arithmetic.

go deeper

for a junior

You are not expected to set this standard, but know the default: prefer an assertion over pulling a value out, and ask before adding a third layer of callbacks to a spec.

for a middle

Be ready to justify a specific spec's shape. Explain why you asserted instead of extracting, or why a value had to be read in a callback, in terms a reviewer can check.

for a senior

Show the tradeoff with real consequences: what deep nesting costs when the app changes shape, and what a seeding seam costs to maintain. Bring the reasoning, not a preference.

for a principal

Own the ranking and the exceptions. Say where you draw the line, how the standard is enforced without a review argument every time, and how you would revise it for a suite whose state genuinely lives in the browser.

Every team adopting Cypress hits the same wall in week two: a test needs a number the application produced, and the runner will not hand it over. What the team does next shapes the suite for years, because the workarounds compound. This is a judgement call with no single right answer, and the useful version of it is a ranked default plus a clear line you will not cross. ## Why the pressure exists at all The suite wants a value for one of three reasons, and they deserve different answers: - **To assert on it.** "The catalogue should show two copies left." Nothing needs to be extracted; this is an assertion. - **To use it in a later step.** "Open the loan whose reference the borrow page generated." Something must carry the value forward. - **To decide what to do next.** "If the book is already on loan, join the waiting list instead." Something must branch. Only the second and third create real pressure. The first is the majority of cases, and treating it as though it needed a variable is where most of the damage starts. ## A ranked default 1. **Assert, do not extract.** A chained assertion is retried until it holds and reads as a statement about the product. It is also the only option that survives a slow render without extra work. 2. **Make the value known before the run.** If the spec creates the loan through an API call, it already holds the reference and the due date, and the browser steps become navigation and confirmation. This is the single largest reduction in callback code most suites can make. 3. **Name the thing, not the value.** An alias created with `.as('firstResult')` lets a later step refer back to a subject without a variable crossing the boundary. 4. **Read inside a callback, briefly.** When a value genuinely originates in the browser, a short `.then()` is the right tool. Keep it to one decision or one assertion. 5. **Refuse the rest.** Helper layers that shuttle values between chains, module-scoped state written in one callback and read in another, and three-deep nesting are the shapes to reject in review. ## What each choice costs | Approach | What it buys | What it costs | |---|---|---| | Chained assertion | retried, readable, no extraction | cannot feed a later navigation step | | Seed the value up front | deterministic, short specs | a maintained API seam for test data | | Alias a subject | avoids a variable, stays in the chain | the name must stay meaningful as the app changes | | Short `.then()` | handles genuinely dynamic values | runs once; the value is a snapshot | | Helper layer over chains | feels like the old runner | hides ordering, and every reader must learn it | ## The line I hold **A spec should describe what the product does, not recompute it.** The moment a callback is doing arithmetic the application also does — deriving a due date, re-deriving a fee, rebuilding a sort order — the test has stopped checking behaviour and started duplicating it. Two things then go wrong at once: the duplicate drifts from the real implementation, and the test still passes while the product is wrong. The second line is about **depth**. One callback is a reasonable place to make one decision. Three nested callbacks are almost always a spec that should have been given its data instead of discovering it, and I would rather spend the effort on the seeding seam than on making the nesting readable. ## Making the standard stick - Turn on the lint rule for assigning command return values so the crudest form is caught mechanically rather than argued about in review. - Write the seeding path **first** when a new area is automated, so the callback-heavy version never gets written and then defended. - In review, ask what a callback is for. "Assert" and "decide once" are good answers; "carry this to the next step" is a prompt to look at where the value could come from instead. - Accept exceptions consciously. Some values only exist in the browser, and a well-named short callback is the correct answer there; the standard exists to make that the exception, not the habit. ## Where reasonable people differ A suite that mostly exercises a server-rendered catalogue can lean much harder on seeded data than one testing a heavily client-side reading view, where more state genuinely originates in the browser. Team experience matters too: a group fluent in the chain writes flatter specs than one still translating from a promise-based runner, and for the latter the seeding-first rule buys more than any amount of teaching about callbacks. The ranking above is a default to argue against with evidence, not a rule to apply blindly.

  • When is a short `.then()` clearly the right answer rather than a compromise?
    When the value genuinely originates in the browser and cannot be known in advance — a client-generated identifier, a rendered ordering, a computed layout. Reading it in one short callback and acting immediately is correct there; the standard exists to keep that the exception rather than the default.
  • How do you tell a team the seeding seam is worth building?
    Show the cost it removes. Count the callbacks that exist only to discover something the API could have told the spec, and the flakes traceable to reading a value mid-render. Then scope the seam narrowly: one endpoint, one entity, and only the fields the specs actually need.

saying these in an interview costs you the question

  • Builds a wrapper to make commands feel awaitable
  • Treats deep callback nesting as unavoidable
  • Recomputes application arithmetic inside a spec
  • Extracts a value when an assertion would do
  • Applies one rule with no room for exceptions