skip to content

In Cypress, what does cy.session()'s validate option do when a restored session fails it?

level: middleimportance: must knowfreq 66%

answer

  1. It runs more than once per session
  2. Two failure points, two different outcomes
  3. A restore failure is recovered, not thrown
  4. Yielding false counts as a failure
  5. Recreate means setup runs a second time

basics

~20 s

validate 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 lines
javascript
const 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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