Cypress cy.session(): Trading Test Isolation for Faster Authenticated Runs
Cypress cy.session() can eliminate redundant logins, but only if you disable testIsolation. Learn the trade-offs, version requirements, and how to verify the cache works.
14 Mar 2026, 14:32 UTC

If your Cypress suite logs in through the UI inside a beforeEach hook, every it block pays the full authentication cost. A suite with 30 specs and five tests each can spend minutes re-entering credentials and waiting for redirects. cy.session() promises to fix that by caching the authenticated browser state, but the optimization only works if you adjust how Cypress isolates tests. The useful takeaway: use cy.session() to skip redundant logins, but pair it with testIsolation: false and accept the isolation trade-off.
Why repeated logins dominate suite runtime
In a typical end-to-end spec, the beforeEach hook runs before every test. If that hook contains a UI login—typing an email, typing a password, submitting a form, and waiting for a dashboard URL—the same network requests and rendering work repeat dozens of times. Even an API-based login adds latency and token handling overhead to each test.
The problem compounds in continuous integration. Longer feedback loops mean developers context-switch while waiting, and flaky network conditions during repeated logins increase the chance of a false failure.
How cy.session() caches authenticated state
cy.session() wraps your login steps in a named session. When the first test calls it, Cypress executes the setup function, then captures the resulting cookies, localStorage, and sessionStorage (client-side key-value stores that persist data in the browser). Subsequent tests that request the same session name receive that captured state instead of repeating the login flow.
The cache is self-healing. If the browser context is cleared or the session expires, Cypress automatically re-runs the setup function. You do not need to write manual cache invalidation logic.
Sessions are scoped by name and can accept arguments. That means you can maintain separate cached states for different personas—for example, cy.session('admin', ...) and cy.session('viewer', ...)—without cross-contaminating their cookies or storage.
The isolation trade-off: testIsolation vs. session reuse
In many default configurations, Cypress enables testIsolation, which clears cookies and browser storage between every test. That safety net prevents one test's side effects from leaking into the next, but it also wipes the very state cy.session() tries to preserve. If you leave testIsolation enabled, your session cache is destroyed after each it block, and the login flow runs again.
To make session caching effective across tests in the same spec, you must set testIsolation: false in your E2E configuration. The cost is reduced isolation: a test that creates a database record, toggles a feature flag, or modifies user settings can leave that state behind for the next test. Your setup function and test bodies must therefore be written to tolerate—or clean up—shared state.
A worked configuration and spec
The following example assumes Cypress 12.2.0 or newer, where cy.session() is stable. First, disable test isolation in your E2E setup:
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
testIsolation: false,
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
})
Next, define a named session once and reuse it across multiple tests:
// cypress/e2e/dashboard.cy.js
describe('Authenticated dashboard', () => {
beforeEach(() => {
cy.session('admin', () => {
cy.visit('/login')
cy.get('[data-cy=email]').type(Cypress.env('ADMIN_EMAIL'))
cy.get('[data-cy=password]').type(Cypress.env('ADMIN_PASSWORD'))
cy.get('form').submit()
cy.url().should('include', '/dashboard')
})
})
it('displays the summary widget', () => {
cy.visit('/dashboard')
cy.get('[data-cy=summary]').should('be.visible')
})
it('navigates to reports', () => {
cy.visit('/reports')
cy.get('[data-cy=report-table]').should('exist')
})
})
Run this spec from your project root with npx cypress run --spec cypress/e2e/dashboard.cy.js. No elevated permissions are required. After the run, open the Command Log for the second test. You should see the login request and form submission appear only during the first test's session setup; the second test should start with the cached cookies and storage already restored. If the login steps appear in both tests, testIsolation is likely still clearing state between them.
Limitations and verification steps
Before adopting this pattern, confirm your Cypress version supports it. Run npx cypress --version in your terminal and compare the output against the Cypress release notes. Versions older than 12.2.0 require an experimental flag or community plugins that may not be maintained.
Applications that issue short-lived JSON Web Tokens (JWTs—compact, signed tokens used for authentication) or rotating refresh tokens may see sessions expire mid-run. The self-healing behavior handles this by re-running the setup function, but the re-authentication can introduce timing flakiness if the token endpoint is slow.
Another risk is artifact exposure. If your application stores sensitive tokens in localStorage or sessionStorage, those values can appear in CI videos, screenshots, or failure logs. Scrub or avoid storing raw tokens when possible.
What to do next
Measure your current suite runtime before changing anything. Identify specs where authenticated beforeEach hooks dominate execution time. For those specs, introduce cy.session() with a stable name, set testIsolation: false, and verify through the Command Log that the login flow executes only once. Keep tests that depend on a pristine browser state in separate specs where isolation remains enabled. Treat session caching as a targeted optimization for shared authentication journeys, not a blanket replacement for test isolation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.