skip to content

In Cypress, what does the id argument to cy.session() do, and when does setup run?

level: juniorimportance: must knowfreq 78%

answer

  1. Think about what makes two sessions different
  2. Cypress compares one argument, not the function
  3. A miss runs setup, a hit restores state
  4. Arrays and objects are deterministically stringified
  5. Every varying setup input belongs in the id

basics

~20 s

The id is the cache key. Cypress runs the setup function only when no valid session is saved under that id; every later call with the same id skips setup and re-applies the cached cookies, localStorage and sessionStorage.

solid answer

~40 s

`cy.session(id, setup)` keys a cached browser session by `id`. On the first call for that `id`, Cypress clears cookies, `localStorage` and `sessionStorage` in every domain, runs `setup`, collects whatever state `setup` left behind, and saves it under the `id`. Every later call with the same `id` restores that saved state instead of running `setup` again. The `id` may be a string, an array or an object, and arrays and objects are deterministically stringified, so `['approver', '[email protected]']` becomes a stable key. The practical rule is that the `id` must vary with everything `setup` varies on: an expense-report helper that signs in with a role and an email but passes only the role will hand the second email the first user's session, because the keys collided. `cy.session()` yields `null`, so read state with a later command.

code

javascript · 26 lines
javascript
const signInAsApprover = (email) => {
  cy.session(['approver', email], () => {
    cy.request('POST', '/api/sessions', {
      email,
      password: 'expense-r3port',
    })
      .its('status')
      .should('eq', 201)
  })
}

describe('reimbursement queue', () => {
  beforeEach(() => {
    // setup runs on the first test only; the rest restore
    signInAsApprover('[email protected]')
    cy.visit('/expenses/pending-approval')
  })

  it('lists reports waiting on this approver', () => {
    cy.get('[data-test=expense-row]').should('have.length', 3)
  })

  it('shows the total awaiting reimbursement', () => {
    cy.get('[data-test=queue-total]').should('contain', '1,240.00')
  })
})

go deeper

for a junior

Be ready to say in one sentence that the id keys the cache and that setup runs only on a miss. Interviewers ask this to check you understand cy.session is more than a login wrapper.

for a middle

Explain the create path in order: clear session data in all domains, run setup, collect cookies and both storages, save under the id. Say what an array id serialises to.

for a senior

Show how you would audit an existing suite for colliding ids, and explain why side effects that are not cookies or storage must not live inside setup, since a restore skips it.

for a principal

Own the convention: who owns the sign-in helper, how ids are composed across roles and environments, and what stops two helpers from silently sharing one key.

## The problem it solves An end-to-end suite that signs in through the application in every test pays the same cost hundreds of times. Cypress's `cy.session(id, setup)` lets a suite pay it roughly once: it runs a block of sign-in work, snapshots the browser state that block produced, and hands that snapshot back on later calls. The state it snapshots is exactly three things - **cookies**, **`localStorage`** and **`sessionStorage`** - collected across every domain the browser has touched. ## The `id` is the entire cache key `cy.session()` never inspects your `setup` function to decide whether it has seen this session before. It compares the `id` you passed, and nothing else. Two consequences follow directly: - **Two calls with the same `id` are the same session**, even when their `setup` functions build completely different logins. - **Two calls with different ids are different sessions**, even when their `setup` functions are byte-for-byte identical. The `id` may be a `String`, an `Array` or an `Object`. Arrays and objects are deterministically stringified for you, so `['approver', '[email protected]']` yields the key `["approver","[email protected]"]` on every run and in every spec. Large or cyclical structures are slow or impossible to serialise, and the `id` is printed in the Command Log, so passwords and tokens do not belong in it. ## What runs on a cache miss The first call for an `id` takes the create path: 1. Cypress clears cookies, `localStorage` and `sessionStorage` in **all** domains, so `setup` starts from a clean browser context. 2. Your `setup` function runs. Whatever it leaves behind in cookies or storage *is* the session - if it drives the reimbursement portal's UI it has to do its own `cy.visit()`, because the page is blank when `setup` starts. 3. Cypress re-collects cookies and both storages and attaches them to the session. 4. A `validate` function, if you supplied one, runs against the fresh session. 5. Only once validation has passed is the session saved under the `id`. ## What runs on a cache hit Every later call with the same `id` takes the restore path: Cypress clears the current session data, writes the saved cookies and storage back, runs `validate` if there is one, and returns **without executing `setup` at all**. That last point catches people out. Anything the sign-in code did *besides* setting cookies and storage - inserting an expense report to approve, priming a server-side counter - does not happen on a restore. Side effects like that belong in their own command, outside `setup`. ## Choosing an id that cannot collide The rule is mechanical: **the `id` must include every input to `setup` that can vary.** | helper signature | id passed | verdict | |---|---|---| | `signIn(email)` | `email` | fine - the only varying input is in the key | | `signIn(role, email)` | `role` | collides - two emails share one session | | `signIn(role, email)` | `[role, email]` | fine - both varying inputs are in the key | | `signInByForm(email)` plus `signInByApi(email)` | `email` in both | collides - different setups, one key | The last row is the one teams actually hit. If an expense-report suite has one helper that signs in through the portal's form and another that posts to `/api/sessions`, the two produce different session data, so they need distinguishable keys such as `['form', email]` and `['api', email]`. Constants that never change, such as a hard-coded integration key, need not appear in the `id` at all. ## How long a key stays warm "Later call" needs a boundary, and the default one is the spec file: - A session cached under an `id` lives for the **duration of the spec file** that created it. The next spec starts cold and pays `setup` again. - The `cacheAcrossSpecs` option widens that scope to the whole run on that machine. - `Cypress.session.clearAllSavedSessions()` throws away every cached session, spec-scoped and global, so the next call is a guaranteed miss. - In `cypress open`, sessions are additionally kept across **reruns of the same spec**, so editing a test and saving does not re-drive the sign-in each time. That last one surprises people mid-debugging: you change the seeded approver, rerun the spec, and the old session is still there because the `id` did not change. ## Reading the result `cy.session()` yields `null`, so there is nothing to chain assertions onto; assert inside `setup`, or afterwards with `cy.getAllCookies()` and `cy.getAllLocalStorage()`. In `cypress open` an Instrument Panel appears above the test listing each session `id` and whether it was created, restored or recreated, and clicking an `id` prints its cached cookies and storage to the browser console. `Cypress.session.getSession(id)` returns the same data programmatically, which is the quickest way to answer "is my key what I think it is?".

  • What happens if two helpers pass the same id but different setup functions in one spec?
    Cypress throws a duplicate-id error rather than silently reusing the session. Once an id is registered in a spec, calling `cy.session()` again with that id and a different `setup`, a different `validate`, or a different `cacheAcrossSpecs` value is treated as a mistake. The fix is a distinguishable id, such as `['form', email]` versus `['api', email]`.
  • Why does a session id show up in the Cypress Command Log, and what does that rule out?
    The id is the session's label in the Command Log and in the Instrument Panel, and clicking it prints the cached data to the console. Because it is displayed, secrets do not belong in it. Key on a role, a username or an opaque handle, and keep passwords and tokens inside `setup` where they are not rendered.

saying these in an interview costs you the question

  • Thinks Cypress hashes the setup function to key sessions
  • Says setup re-runs in every test that calls cy.session
  • Passes only the role when the helper also takes an email
  • Puts a password or bearer token in the session id
  • Expects cy.session() to yield the signed-in user object