DEV Community

Javier Brea
Javier Brea

Posted on

How to preserve localStorage between Cypress tests

Cypress by default clears localStorage between tests, which may be a problem when you are trying features related to it. But there is a Cypress plugin that allows preserving localStorage between tests and disabling localStorage.

The problems

  • You want to preserve localStorage between Cypress tests.
  • You want to disable localStorage to check error handling.

The solution

The cypress-localstorage-commands plugin allows you to use all browser localStorage methods through Cypress commands, and preserve it between tests. It also allows to simulate that localStorage is disabled in the browser.

Installation

The module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:

npm i --save-dev cypress-localstorage-commands
Enter fullscreen mode Exit fullscreen mode

Usage

cypress-localstorage-commands extends Cypress' cy command.

Add this line to your project's cypress/support/commands.js:

import "cypress-localstorage-commands"
Enter fullscreen mode Exit fullscreen mode

You can now use all next commands:

Commands

cy.saveLocalStorage()

Saves current localStorage values into an internal "snapshot".

cy.restoreLocalStorage()

Restores localStorage to previously "snapshot" saved values.

cy.clearLocalStorageSnapshot()

Clears localStorage "snapshot" values, so previously saved values are cleaned.

cy.getLocalStorage(item)

Gets localStorage item. Equivalent to localStorage.getItem in browser.

  • item (String): Item to get from localStorage.
cy.setLocalStorage(item, value)

Sets localStorage item. Equivalent to localStorage.setItem in browser.

  • item (String): Item to set value.
  • value (String): Value to be set.
cy.removeLocalStorage(item)

Removes localStorage item. Equivalent to localStorage.removeItem in browser.

  • item (String): Item to be removed.
cy.disableLocalStorage(options)

Disables localStorage. It produces localStorage methods to throw errors.

  • options (Object): Options to use when disabling localStorage.
    • withError (Error): If provided, invocations to localStorage methods will throw this error.

Preserving local storage between tests

Use cy.saveLocalStorage() to save a snapshot of current localStorage at the end of one test, and use the cy.restoreLocalStorage() command to restore it at the beginning of another one. The usage of beforeEach and afterEach is recommended for this purpose.

Examples

Cookies button example

Next example shows how the plugin can be used to test a "cookies button" (which theorically sets a flag into localStorage and can be clicked only once)

describe("Accept cookies button", () => {
  const COOKIES_BUTTON = "#accept-cookies";

  before(() => {
    cy.clearLocalStorageSnapshot();
  });

  beforeEach(() => {
    cy.restoreLocalStorage();
    cy.visit("/");
  });

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

  it("should be visible", () => {
    cy.get(COOKIES_BUTTON).should("be.visible");
  });

  it("should not be visible after clicked", () => {
    cy.get(COOKIES_BUTTON).click();
    cy.get(COOKIES_BUTTON).should("not.be.visible");
  });

  it("should not be visible after reloading", () => {
    cy.get(COOKIES_BUTTON).should("not.be.visible");
  });
});
Enter fullscreen mode Exit fullscreen mode

Note the usage of beforeEach and afterEach for preserving localStorage between all tests. Also cy.clearLocalStorageSnapshot is used in the before statement to avoid possible conflicts with other test files preserving localStorage.

localStorage assertions

Based on the previous example, assertions could be added to check values of localStorage:

describe("localStorage cookies-accepted item", () => {
  beforeEach(() => {
    cy.restoreLocalStorage();
    cy.visit("/");
  });

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

  it("should be null first time page is visited", () => {
    cy.getLocalStorage("cookies-accepted").should("equal", null);
  });

  it("should be true after clicking cookies button", () => {
    cy.get("#accept-cookies").click();
    cy.getLocalStorage("cookies-accepted").should("equal", "true");
  });

  it("should be true after reloading", () => {
    cy.getLocalStorage("cookies-accepted").then(cookiesAccepted => {
      expect(cookiesAccepted).to.equal("true");
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Disabling localStorage

Use cy.disableLocalStorage() to simulate that localStorage is disabled, producing that any invocation to localStorage.setItem, localStorage.getItem, localStorage.removeItem or localStorage.clear will throw an error. As MDN docs recommend, "developers should make sure to always catch possible exceptions from setItem()". This command allows to test that possible exceptions are handled correctly.

Note that:

  • Only pages loaded after calling this command will have localStorage disabled, so always use cy.reload or cy.visit after executing it.
  • The localStorage only remains disabled for all pages loaded during the current test. If you want to disable it for multiple tests, execute it in all of them, or in a beforeEach statement.
  • If any of the other plugin commands (except clearLocalStorageSnapshot) is executed while localStorage is disabled, it will do nothing but producing a Cypress log as: "localStorage.setItem is disabled"

Examples

Disabling localStorage in a single test

Based on previous "Accept cookies button" example, next tests could be added:

//...
const LOCALSTORAGE_DISABLED_WARNING = "#localstorage-disabled-warning";
const LOCALSTORAGE_ERROR = "#localstorage-error";

//... should not be visible after clicked

it("should still be visible when reloading if localStorage is disabled", () => {
  cy.disableLocalStorage();
  cy.reload();
  cy.get(COOKIES_BUTTON).should("be.visible");
});

it("should display warning if localStorage is disabled", () => {
  cy.disableLocalStorage();
  cy.reload();
  cy.get(LOCALSTORAGE_DISABLED_WARNING).should("be.visible");
});

it("should display localStorage error message", () => {
  cy.disableLocalStorage();
  cy.reload();
  cy.get(LOCALSTORAGE_ERROR).should("have.text", "Error");
});

// ...should not be visible after reloading
Enter fullscreen mode Exit fullscreen mode

Disabling localStorage in multiple tests

describe("when localStorage is disabled", () => {
  beforeEach(() => {
    cy.disableLocalStorage({
      withError: new Error("Disabled by cypress-localstorage-commands"),
    });
    cy.visit("/");
  });

  it("should display localStorage warning", () => {
    cy.get("#localstorage-disabled-warning").should("be.visible");
  });

  it("should display localStorage error message", () => {
    cy.get("#localstorage-error").should("have.text", "Disabled by cypress-localstorage-commands");
  });

  it("should display accept-cookies button disabled", () => {
    cy.get("#accept-cookies").should("be.disabled");
  });
});
Enter fullscreen mode Exit fullscreen mode

Discussion (5)

Collapse
gsaran profile image
gsaran • Edited on

Hello,

I am using this plugin to preserve local storage between tests but I am seeing different behavior when I execute e2e tests using cypress open or cypress run.

I have say 2 test files:

  • 1.test.ts which has describe block with multiple it tests & using above commands to preserve localStorage i.e. beforeEach calling cy.restoreLocalStorage() & afterEach calling cy.saveLocalStorage. In this file, I perform login & want to use local storage values in next test file.

  • 2.test.ts which also has describe block with multiple it tests & have beforeEach calling cy.restoreLocalStorage() & afterEach calling cy.saveLocalStorage()

When I run cypress open, I could see that localStorage set by 1.test.ts is restored for 2.test.ts in it's beforeEach.

But when I run cypress run or cypress run --browser chrome --headless, 2.test.ts beforeEach gets empty localStorage.

Are there any additional changes required to make it work?

Collapse
geabby profile image
geabby • Edited on

I have a similar requirement in order to prevent repeated logins across specs. The only solution I could think of was to write the token into a file and read it again next time we login. Didn't want to go down that path yet. Were you able to find a solution to this?

Collapse
gsaran profile image
gsaran

Hey!
No I couldn't find a way to accomplish that.

When I ran cypress run --browser chrome --headed, I observed that new browser (incognito) window was opening for each test file. Although I was doing cy.saveLocalStorage, local storage was cleared when the browser window was closed.

So I re-organized my tests so that I can test multiple related scenarios as part of one test file, logging in the start & logging out in the last test. Within each test file I do login & logout process & that's working fine.

Collapse
bendavies99 profile image
Ben Davies

Does this not break the principal that all tests should be isolated units?

Collapse
javierbrea profile image
Javier Brea Author

Yes, but in my experience that principal is not fully applicable to E2E tests. I fully agree with it when talking about unit tests, but E2E tests strongly depend on the application state left by previous tests, if you don't want to perform lots of repeated actions in every test, which may result in huge testing times, and low maintainability.
I usually follow that principle for each Cypress spec file, but not for every test inside it.