In Cypress, what does cy.session()'s validate option do when a restored session fails it?
answer
- It runs more than once per session
- Two failure points, two different outcomes
- A restore failure is recovered, not thrown
- Yielding false counts as a failure
- Recreate means setup runs a second time
basics
~20 svalidate re-checks a session after setup creates it and after a cached one is restored. When a restored session fails, Cypress recreates it by re-running setup and validates again; a failure straight after setup fails the test.
solid answer
~40 s`validate` is a function on `cy.session()`'s options object, and Cypress runs it in two places: immediately after `setup` creates a session, and immediately after a cached session is restored. Cypress treats the session as invalid if `validate` throws, contains a failing Cypress command, returns a promise that rejects or resolves to `false`, or ends with a Cypress command that yielded `false`. The two failure points behave differently. Fail after a **restore** and Cypress takes the recreate path: it clears the browser context, re-runs `setup`, and validates the fresh session. Fail after `setup` - on the original creation or on that recreate - and the test fails outright. A session is only saved once validation has passed, so an invalid one is never carried into the next test.
code
javascript · 20 linesconst signInAsApprover = (email) => {
cy.session(
['approver', email],
() => {
cy.request('POST', '/api/sessions', {
email,
password: 'expense-r3port',
})
},
{
validate() {
// a 401 here fails cy.request(), which invalidates
// the restored session and triggers a recreate
cy.request('/api/expenses/pending-approval')
.its('status')
.should('eq', 200)
},
}
)
}go deeper
Know that validate is an optional function in cy.session()'s third argument and that its job is to confirm the restored session still works before the test uses it.
Be able to list what makes a session invalid and to separate the two outcomes: a restore failure recreates the session, a post-setup failure fails the test.
Talk about cost and specificity. validate runs on every restore, so it must be cheap, and it must be strict enough to catch an expired token rather than merely a present cookie.
Frame the policy: whether validate is mandatory in the team's sign-in helper, what it is allowed to cost, and how a suite that recreates on every test gets noticed rather than tolerated.
## Why a cached session needs re-checking A session saved by Cypress's `cy.session()` is a snapshot of cookies, `localStorage` and `sessionStorage`. Nothing about that snapshot guarantees the server still honours it. An expense-report API can expire the token minutes after it was minted, an approver's role can be revoked by another spec, or the sign-in that produced the snapshot may never have finished before the command ended. `validate` is the hook that turns "I have some cookies" into "I am actually signed in", and it is the only thing that lets a cached session be repaired instead of silently poisoning the test. ## Where `validate` runs `validate` is a function on `cy.session()`'s third argument, the options object. Cypress runs it in exactly two places: - **Immediately after `setup`**, on the create path and on the recreate path. - **Immediately after a cached session is restored**, before the command yields. That is the whole difference between the two failure modes, and interviewers push on it. | where validation fails | what Cypress does | |---|---| | right after `setup` created the session | the session is **not saved**, and the **test fails** | | right after a cached session was restored | Cypress **recreates** the session by re-running `setup` | | right after the **recreate**'s `setup` | the session is **not saved**, and the **test fails** | ## What counts as a failure Cypress marks the session invalid when any of these is true: - `validate` throws an exception. - `validate` contains a failing Cypress command - a `cy.request()` that got a `401`, a `cy.visit()` bounced to the sign-in page, a `.should()` that never passed. - `validate` returns a promise that rejects, or that resolves to `false`. - The last Cypress command inside `validate` yielded `false`. Note the asymmetry in that last rule: yielding `false` is a failure, but yielding anything else - including `undefined` - is a pass. A `validate` that runs no assertion and no failing command therefore always passes, which is the quiet way to end up with a `validate` that validates nothing. ## The restore ladder, step by step 1. Cypress finds a saved session for the `id` and takes the restore path. 2. It clears the current cookies and storage, then writes the saved ones back. 3. `validate` runs. If it passes, `cy.session()` finishes and the test continues. 4. If it fails, the failure is **recovered, not thrown**: the Command Log marks the session recreated and Cypress clears the browser context again. 5. `setup` runs a second time, producing a fresh session. 6. `validate` runs again. Pass and the new session is saved; fail and the test fails with the validation error. So a suite with a good `validate` heals itself once per invalid session and costs one extra `setup`. A suite without one carries the stale snapshot straight into the test, where it surfaces as a `401` or a redirect several commands later - a failure whose cause is nowhere near the command that reported it. ## Writing a `validate` that actually validates The cheapest useful check is a request to an endpoint only a signed-in caller can reach: - `cy.request('/api/expenses/pending-approval').its('status').should('eq', 200)` fails the session on a `401`, because a non-2xx response fails `cy.request()` by default. - `cy.visit('/expenses/pending-approval')` works too when the application redirects an anonymous visitor to a sign-in page, since the redirect makes the visit or a following assertion fail. - Returning `false` explicitly is fine for a check you compute yourself. Keep it small. `validate` runs on every restore, so an expensive check gives back the time `cy.session()` was supposed to save. Keep it *specific* too: a check that only confirms a cookie exists will happily pass on an expired token. ## Reading it in the Command Log The session group in the Command Log reports one of three outcomes for each call - created, restored, or restored-then-recreated - and expanding it shows the commands that ran inside `setup` and `validate`. A session that shows "recreated" in every test is telling you the cached snapshot never survives validation, which usually means the token lifetime is shorter than the spec or that `setup` finished before the application had finished writing its storage.
- Why does a validate function with no assertion in it always pass?Cypress only marks a session invalid if `validate` throws, contains a failing command, rejects or resolves `false`, or ends on a command that yielded `false`. A body that merely reads a cookie or visits a page that never fails satisfies none of those, so it passes. Make the check fail loudly: assert a status, or assert on content only a signed-in approver can see.
- How many times can Cypress re-run setup for one cy.session() call?Once. The restore path validates, and on failure moves to the recreate path, which runs `setup` again and validates again. There is no second recreate: if validation fails after that `setup`, the session is not saved and the test fails with the validation error. So one call costs at most two `setup` runs.
saying these in an interview costs you the question
- Says a failed validate always fails the test
- Thinks validate runs only when a session is created
- Writes a validate that just checks a cookie exists
- Believes Cypress retries validate until it passes
- Assumes an invalid session is still saved for later tests