In Cypress, what replaces a ported suite's soft assertions that collected every failure?
answer
- Ask why the checks were batched at all
- Which assertion library ships with the runner?
- The first throw ends the test
- A callback can hold several expectations
- One behaviour per it block
basics
~20 sNothing does. Cypress bundles Chai and the test ends at the first failed assertion. Port a batch either into separate it blocks, one per behaviour, or into a single .should() callback whose expect calls are retried together.
solid answer
~50 sCypress has no soft-assert mode. Assertions run through Chai behind the retrying `.should()` command and the one-off `expect()`, and the first one that throws ends the test, so a batch of eight checks now reports one failure per run. There are two honest ports. Split the batch into separate `it()` blocks: each check then reports independently, its name says which behaviour broke, and it retries on its own budget — the cost is that each test rebuilds its own state. Or keep genuinely coupled checks together in one `.should(callbackFn)`, which holds several `expect()` calls and is retried as a unit until none of them throws; that callback must be free of side effects and may not call Cypress commands, and it still surfaces only the first failure. Chaining `.should().and()` reads well but is not a soft assert either.
go deeper
Be ready to say that a Cypress test stops at the first failed assertion, and that one behaviour per it block is the usual shape. You are not expected to know the callback form yet.
Explain that Chai is what Cypress asserts with, and that a .should() callback groups several expectations and is retried as a whole. Name its restrictions: no side effects and no Cypress commands inside it.
Show judgment about which batches split and which stay together, and be able to explain why a hand-rolled try/catch collector breaks retry-ability and produces a Command Log that lies about what passed.
Own the convention for the migrated suite: what a single test is allowed to assert, and how the team keeps failures diagnosable when the runner reports only the first one.
## Why the batch existed in the old suite In a suite that drives a browser from outside, finding an element is expensive and waiting for one is expensive, so tests grow batches: navigate once, find the page, then run eight checks against it and report them together. A soft-assert mode makes that batch bearable — every check runs, every failure is collected, and one run tells you all eight results. Teams porting such a suite usually reach for the same feature on day one. ## What Cypress does instead Cypress bundles **Chai** and exposes it two ways: the retrying `.should()` command and the one-off `expect()`. Both throw on failure, and Mocha ends the test at the first throw. There is **no soft-assert mode**, no collecting variant of `expect()`, and no option that downgrades a failed assertion to a warning. A batch of eight checks therefore reports one failure per run instead of eight. Two related shapes are often mistaken for a soft assert and are not: - `.should('be.visible').and('have.text', 'Queued')` chains assertions onto one query. They retry together, which is useful, but the first one to fail still ends the test. - `.then(el => { ... })` runs your code once. It neither retries nor collects; it just moves the throw inside a callback. ## Port one: split the batch into separate tests The default answer is that each check becomes its own `it()`. - Each failure is reported independently, so one broken behaviour does not mask seven others. - The test name says which behaviour broke, which is worth more than a stack trace. - Each test retries on its own budget rather than sharing one with unrelated checks. The cost is that every test sets its own state up again — usually a `cy.visit()` and whatever seeding the flow needs. Against a locally running application that is normally cheap enough to prefer, and the setup itself can be pushed into `beforeEach`, a `cy.request()` seed, or a `cy.session()` where a sign-in is involved. ## Port two: one retrying callback Where the checks are genuinely one fact about one element, keep them together in a `.should()` callback: ```javascript cy.get('[data-cy=run-summary]').should(($summary) => { expect($summary).to.contain('12 specs') expect($summary).to.contain('3 failed') expect($summary).to.have.attr('data-status', 'complete') }) ``` The rules that come with it are worth stating out loud, because they are what interviewers probe: 1. The callback is **retried** until no assertion inside it throws, or the timeout expires. 2. It must therefore be **free of side effects** — anything it mutates or fires happens an unpredictable number of times. 3. It may **not call Cypress commands**; Cypress throws if you try. 4. Its return value is **ignored**, and the original subject is yielded onwards. 5. It still reports only the first failing expectation, so it is a grouping tool, not a soft assert. ## Comparing the three shapes | Shape | Retries | Reports | Best for | |---|---|---|---| | separate `it()` per check | each on its own budget | every failure, named | independent behaviours | | one `.should(callbackFn)` | whole callback as a unit | first failure only | several facts about one element | | `.should().and()` chain | all assertions together | first failure only | a short, readable chain | The practical consequence is diagnosability. A batch that reports one failure per run turns a migration into a queue: fix, rerun, discover the next failure, rerun again. Splitting the batch converts that queue into a single run that names every broken behaviour at once, which is usually worth more during a port than the seconds the extra setup costs. Decide the shape per batch rather than once for the whole suite, and let the question be whether the checks describe one fact or several. ## The anti-port The tempting third option is to write the soft assert yourself: wrap each `expect()` in a `try`/`catch`, push the errors into an array, and throw at the end of the test. Do not. Swallowing the throw inside a `.should()` callback breaks retry-ability, because the callback stops signalling that it should be retried; and swallowing it outside one produces a test that reports a summary nobody can act on, with a Command Log that shows green steps for checks that failed. If the batch really is eight independent behaviours, the honest fix is eight tests.
- Why must a Cypress .should() callback be free of side effects?Because Cypress retries it. The callback runs again on every retry until no assertion throws or the timeout expires, so a counter incremented inside it, a fixture it mutates, or a request it fires happens an unpredictable number of times. Its return value is ignored as well — the original subject is yielded onwards.
- Splitting a batch costs a fresh setup per test. When is that too expensive?When the setup is genuinely slow and the checks are read-only views of the same state — a long report render, a large seeded data set. Then one test with a grouped `.should()` callback, or setup pushed into `cy.session()` or a `cy.request()` seed, beats eight tests each rebuilding it. Against a local application, the split is usually cheap enough to prefer.
saying these in an interview costs you the question
- Claims Cypress has a soft assertion mode or plugin flag
- Wraps each expect in try/catch and reports at the end
- Thinks .and() collects failures rather than chaining them
- Calls Cypress commands inside a .should() callback
- Batches unrelated checks into one test to save a visit