Cypress doesn’t manage cookies and sessions in the way you might expect, which is precisely why it’s so powerful for end-to-end testing.

Let’s see it in action. Imagine you have a simple login flow.

// cypress/integration/login_spec.js
describe('Login Flow', () => {
  beforeEach(() => {
    // Visit the login page. Cypress automatically clears cookies and local storage
    // before each test run by default.
    cy.visit('/login');
  });

  it('should successfully log in and maintain session', () => {
    cy.get('input[name="username"]').type('testuser');
    cy.get('input[name="password"]').type('password123');
    cy.get('button[type="submit"]').click();

    // After login, we expect to be on the dashboard page.
    cy.url().should('include', '/dashboard');

    // Crucially, Cypress *remembers* the session.
    // If we navigate to another page that requires authentication,
    // we shouldn't need to log in again.
    cy.visit('/profile');
    cy.url().should('include', '/profile');
    cy.contains('Welcome, testuser!');
  });

  it('should not allow access to protected route without login', () => {
    // This test starts with a fresh state, no cookies from the previous test.
    cy.visit('/dashboard');
    cy.url().should('include', '/login'); // Automatically redirected to login
  });
});

In this example, cy.visit() is the magic. By default, Cypress runs each test in an isolated browser instance. Before each it() block (and beforeEach/afterEach), Cypress automatically clears all cookies, local storage, and session storage. This ensures that each test starts with a clean slate, simulating a new user or a fresh browser session.

When you log in, the application sets session cookies. Because Cypress is running within the browser, it has direct access to these cookies. It doesn’t need to re-authenticate on subsequent cy.visit() calls within the same test. This is why the second cy.visit('/profile') in the first test works seamlessly – the browser, controlled by Cypress, still holds the valid session cookie.

The problem Cypress solves is brittle test setup. Historically, E2E tests often relied on complex mechanisms to seed databases, bypass authentication, or manually inject session tokens. Cypress bypasses much of this by leveraging the browser’s native cookie and session management. You interact with your application as a user would, and Cypress observes and controls the browser, including its storage.

The key levers you control are:

  • cy.visit(): As shown, this is the primary way to navigate. Its default behavior of clearing state is your friend for test isolation.
  • cy.getCookie(name) / cy.setCookie(name, value, options): If you need more granular control or want to simulate a logged-in state without going through the UI login, you can directly manipulate cookies. This is useful for testing specific edge cases or if your login process is particularly slow.
    it('can set a cookie to bypass login', () => {
      cy.setCookie('session_id', 'fake_session_token', { domain: 'localhost', secure: false });
      cy.visit('/dashboard'); // Assuming /dashboard checks for session_id cookie
      cy.url().should('include', '/dashboard');
    });
    
  • cy.clearCookies() / cy.clearCookie(name): Explicitly clear cookies if you need to break a session mid-test. This is less common because beforeEach usually handles isolation.
  • cy.wrap(): Sometimes, you might get a cookie object and need to interact with its properties. cy.wrap() helps bridge the gap between Cypress commands and standard JavaScript.

The one thing most people don’t realize is that cy.visit() doesn’t just load a URL; it’s a state-resetting operation by default. When you see cy.visit('/some/page') at the start of every it block, it’s not just navigating; it’s also saying "forget everything from the last test run." This isolation is the bedrock of reliable E2E tests. If you want to maintain state across tests, you have to explicitly tell Cypress not to clear cookies, often done in the cypress.config.js file or by using cy.request() to log in and then cy.visit() without the automatic clearing.

Beyond cookies, you’ll eventually need to consider how your application handles authentication tokens stored in localStorage or sessionStorage, as Cypress also provides commands like cy.window().its('localStorage').invoke('getItem', 'token') to interact with these.

Want structured learning?

Take the full Cypress course →