58

I want to save/persist/preserve a cookie or localStorage token that is set by a cy.request(), so that I don't have to use a custom command to login on every test. This should work for tokens like jwt (json web tokens) that are stored in the client's localStorage.

Gal Margalit
  • 5,525
  • 6
  • 52
  • 56
kuceb
  • 16,573
  • 7
  • 42
  • 56

10 Answers10

58

To update this thread, there is already a better solution available for preserving cookies (by @bkucera); but now there is a workaround available now to save and restore local storage between the tests (in case needed). I recently faced this issue; and found this solution working.

This solution is by using helper commands and consuming them inside the tests,

Inside - cypress/support/<some_command>.js

let LOCAL_STORAGE_MEMORY = {};

Cypress.Commands.add("saveLocalStorage", () => {
  Object.keys(localStorage).forEach(key => {
    LOCAL_STORAGE_MEMORY[key] = localStorage[key];
  });
});

Cypress.Commands.add("restoreLocalStorage", () => {
  Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
    localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
  });
});

Then in test,

beforeEach(() => {
  cy.restoreLocalStorage();
});

afterEach(() => {
  cy.saveLocalStorage();
});

Reference: https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888

Kondasamy Jayaraman
  • 1,802
  • 1
  • 20
  • 25
53

From the Cypress docs

For persisting cookies: By default, Cypress automatically clears all cookies before each test to prevent state from building up.

You can configure specific cookies to be preserved across tests using the Cypress.Cookies api:

// now any cookie with the name 'session_id' will
// not be cleared before each test runs
Cypress.Cookies.defaults({
  preserve: "session_id"
})

NOTE: Before Cypress v5.0 the configuration key is "whitelist", not "preserve".

For persisting localStorage: It's not built in ATM, but you can achieve it manually right now because the method thats clear local storage is publicly exposed as Cypress.LocalStorage.clear.

You can backup this method and override it based on the keys sent in.

const clear = Cypress.LocalStorage.clear

Cypress.LocalStorage.clear = function (keys, ls, rs) {
  // do something with the keys here
  if (keys) {
    return clear.apply(this, arguments)
  }

}
jkinkead
  • 4,101
  • 21
  • 33
kuceb
  • 16,573
  • 7
  • 42
  • 56
  • 2
    Note: For cookies, this now seems to be `preserve` instead of `whitelist` – webnoob Oct 21 '20 at 08:47
  • 3
    I'm sorry newbie here: so where would I include this method? in a command.js file? and how would I use it to say, add a specific key? thank you – Citronex Aug 27 '21 at 01:55
  • @Citronex as for Cypress 10.8.0, put this method in [support/e2e.js](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Support-file) – O.Badr Sep 23 '22 at 17:33
11

You can add your own login command to Cypress, and use the cypress-localstorage-commands package to persist localStorage between tests.

In support/commands:

import "cypress-localstorage-commands";

Cypress.Commands.add('loginAs', (UserEmail, UserPwd) => {
  cy.request({
    method: 'POST',
    url: "/loginWithToken",
    body: {
      user: {
        email: UserEmail,
        password: UserPwd,
      }
    }
  })
    .its('body')
    .then((body) => {
      cy.setLocalStorage("accessToken", body.accessToken);
      cy.setLocalStorage("refreshToken", body.refreshToken);
    });
});

Inside your tests:

describe("when user FOO is logged in", ()=> {
  before(() => {
    cy.loginAs("foo@foo.com", "fooPassword");
    cy.saveLocalStorage();
  });

  beforeEach(() => {
    cy.visit("/your-private-page");
    cy.restoreLocalStorage();
  });

  it('should exist accessToken in localStorage', () => {
    cy.getLocalStorage("accessToken").should("exist");
  });

  it('should exist refreshToken in localStorage', () => {
    cy.getLocalStorage("refreshToken").should("exist");
  });
});
Javier Brea
  • 1,345
  • 12
  • 16
6

Here is the solution that worked for me:

 Cypress.LocalStorage.clear = function (keys, ls, rs) {
    return;
 before(() => {
    LocalStorage.clear();
    Login();
  })

Control of cookie clearing is supported by Cypress: https://docs.cypress.io/api/cypress-api/cookies.html

Leo Davtyan
  • 203
  • 4
  • 11
  • 1
    Can you please explain how this solution solves the issue? https://stackoverflow.com/help/how-to-answer – Marcello B. Oct 17 '19 at 21:50
  • with each spec having a describe() containing a sequence of tests, with a single login at the start. Each test is in an it(), and we want to preserve both DOM and localStorage between the it()s as we use JWT login. – Leo Davtyan Oct 17 '19 at 21:54
  • Please add that to your answer. – Marcello B. Oct 17 '19 at 21:55
  • 1
    This is only a partial answer, your function is missing quite a bit of info and isn't complete. Maybe update to show the entire process. – Daniel Mar 23 '21 at 16:56
5

I'm not sure about local storage, but for cookies, I ended up doing the following to store all cookies between tests once.

beforeEach(function () {
  cy.getCookies().then(cookies => {
    const namesOfCookies = cookies.map(c => c.name)
    Cypress.Cookies.preserveOnce(...namesOfCookies)
  })
})

According to the documentation, Cypress.Cookies.defaults will maintain the changes for every test run after that. In my opinion, this is not ideal as this increases test suite coupling.

I added a more robust response in this Cypress issue: https://github.com/cypress-io/cypress/issues/959#issuecomment-828077512

I know this is an old question but wanted to share my solution either way in case someone needs it.

pgarciacamou
  • 1,602
  • 22
  • 40
2

2023 Updated on Cypress v12 or more:

Since Cypress Version 12 you can use the new cy.session()

it cache and restore cookies, localStorage, and sessionStorage (i.e. session data) in order to recreate a consistent browser context between tests.

Here's how to use it

// Caching session when logging in via page visit
cy.session(name, () => {
  cy.visit('/login')
  cy.get('[data-test=name]').type(name)
  cy.get('[data-test=password]').type('s3cr3t')
  cy.get('form').contains('Log In').click()
  cy.url().should('contain', '/login-successful')
})
Ahmed Khashaba
  • 788
  • 7
  • 14
  • 1
    I dont understand how you restore that session, for example in another test in the same spec file... – Stiegi Mar 13 '23 at 14:54
  • yes, you can see it as well in the documentation link i added above in my answer "Cache and restore cookies, localStorage, and sessionStorage (i.e. session data) in order to recreate a consistent browser context between tests – Ahmed Khashaba Apr 06 '23 at 14:58
1

For keeping a google token cookie, there is a library called cypress-social-login.
It seems to have other OAuth providers as a milestone.
It's recommended by the cypress team and can be found on the cypress plugin page.

https://github.com/lirantal/cypress-social-logins

This Cypress library makes it possible to perform third-party logins (think oauth) for services such as GitHub, Google or Facebook.

It does so by delegating the login process to a puppeteer flow that performs the login and returns the cookies for the application under test so they can be set by the calling Cypress flow for the duration of the test.

Gal Margalit
  • 5,525
  • 6
  • 52
  • 56
0

I can see suggestions to use whitelist. But it does not seem to work during cypress run. Tried below methods in before() and beforeEach() respectively:

Cypress.Cookies.defaults({
  whitelist: "token"
})

and

Cypress.Cookies.preserveOnce('token');

But none seemed to work. But either method working fine while cypress open i.e. GUI mode. Any ideas where I am coming short?

0

Try this is working in my case

// Save session data
let savedCookies;
let savedLocalStorage;
let savedSessionStorage;

before(() => {
  // Save session data
  cy.visit('https://example.com/login'); // Visit the login page

  // Log in and save session data
  cy.get('#username').type('your_username');
  cy.get('#password').type('your_password');
  cy.get('#login-button').click();

  // Save cookies
  cy.getCookies().then((cookies) => {
    savedCookies = cookies;
  });

  // Save localStorage
  savedLocalStorage = window.localStorage;

  // Save sessionStorage
  savedSessionStorage = window.sessionStorage;
});

// Restore session data
beforeEach(() => {
  // Restore cookies
  cy.clearCookies();
  savedCookies.forEach((cookie) => {
    cy.setCookie(cookie.name, cookie.value, {
      domain: cookie.domain,
      path: cookie.path,
      secure: cookie.secure,
      httpOnly: cookie.httpOnly,
      expiry: cookie.expires,
    });
  });

  // Restore localStorage
  Object.keys(savedLocalStorage).forEach((key) => {
    window.localStorage.setItem(key, savedLocalStorage.getItem(key));
  });

  // Restore sessionStorage
  Object.keys(savedSessionStorage).forEach((key) => {
    window.sessionStorage.setItem(key, savedSessionStorage.getItem(key));
  });
});

// Your Cypress test code
describe('My tests', () => {
  it('Test 1', () => {
    // The user is already logged in due to the before() hook

    // Your test assertions
  });

  it('Test 2', () => {
    // The user is still logged in due to the before() hook

    // Your test assertions
  });

  // More tests...
});
0

for cypress 12, we can pass option cacheAcrossSpecs in the cy.session function. Its useful if we have multiple specs file that require session. We can request for the session once.

This is the reference from the cypress docs. Reference