Q15 of 48 · Cypress

When would you use cy.session over a custom login command?

CypressMidcypresscy-sessionauthmid

Short answer

Short answer: `cy.session` caches the logged-in state across tests (and optionally specs), avoiding re-login for every test. A plain custom command logs in every time. Use `cy.session` whenever the same user logs in repeatedly — it can cut suite time by 40-70%.

Detail

A naive custom command logs in on every beforeEach:

Cypress.Commands.add('login', (email) => {
  cy.request('POST', '/api/login', { email }).then(({ body }) => {
    window.localStorage.setItem('token', body.token);
  });
});

That works but pays the login cost on every test. cy.session adds a caching layer:

Cypress.Commands.add('login', (email) => {
  cy.session(email, () => {
    cy.request('POST', '/api/login', { email }).then(({ body }) => {
      window.localStorage.setItem('token', body.token);
    });
  });
});

Cypress runs the setup function once per unique session ID (the first argument), captures cookies + localStorage + sessionStorage, and restores them on subsequent calls. Pass the user role/email as the key so different personas don't collide.

Two flags worth knowing:

  • cacheAcrossSpecs: true persists the session across spec files. With test isolation enabled (default), each spec otherwise re-runs setup; this flag avoids that.
  • validate callback — runs on every restore; if it throws or returns false, Cypress re-runs setup. Use it to verify the cached session is still valid (e.g., token not expired).

Use plain custom commands for one-off auth flows or when the user changes per test. Use cy.session whenever the same user is reused.

// EXAMPLE

support/commands.ts

Cypress.Commands.add('loginAs', (role: 'admin' | 'viewer') => {
  cy.session(
    role,
    () => {
      cy.request({
        method: 'POST',
        url: '/api/login',
        body: {
          email: Cypress.env(`${role}Email`),
          password: Cypress.env('password'),
        },
      }).then(({ body }) => {
        window.localStorage.setItem('token', body.token);
      });
    },
    {
      cacheAcrossSpecs: true,
      validate() {
        cy.request('/api/me').its('status').should('eq', 200);
      },
    },
  );
});

// In specs:
beforeEach(() => {
  cy.loginAs('admin');
  cy.visit('/dashboard');
});

// WHAT INTERVIEWERS LOOK FOR

Knowing the caching benefit, `cacheAcrossSpecs`, and the `validate` callback for stale-session detection.

// COMMON PITFALL

Using `cy.session` without a `validate` callback — when the cached token expires mid-run, every restore silently uses an invalid token.