Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to whitelist LocalStorage entries #461

Open
carlos-granados opened this issue Mar 15, 2017 · 36 comments
Open

Add option to whitelist LocalStorage entries #461

carlos-granados opened this issue Mar 15, 2017 · 36 comments

Comments

@carlos-granados
Copy link
Contributor

@carlos-granados carlos-granados commented Mar 15, 2017

Similar to the mechanism available to whitelist cookies so that these entries will not be cleared between test

@tjhillard
Copy link

@tjhillard tjhillard commented Apr 26, 2017

👍 Would a be nice feature!

@ashercoren
Copy link

@ashercoren ashercoren commented May 12, 2017

Yes, please!

@ZwaarContrast
Copy link

@ZwaarContrast ZwaarContrast commented Aug 21, 2017

Yes, that would really help us with our application which uses cognito by AWS as a authentication provider (which under the hood uses localstorage)

@brian-mann
Copy link
Member

@brian-mann brian-mann commented Aug 28, 2017

You can achieve this 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)
  }
}
@jurom
Copy link

@jurom jurom commented Sep 20, 2017

@brian-mann I had similar problem and this worked, thanks ! However, I assume that there's a typo and it should've been

Cypress.LocalStorage.clear = function (....
@jennifer-shehane
Copy link
Member

@jennifer-shehane jennifer-shehane commented Dec 4, 2017

@jurom Thanks, this has been fixed in brian's example.

@lacroixdavid1
Copy link

@lacroixdavid1 lacroixdavid1 commented Jan 11, 2018

I've tried your example @brian-mann but i get empty keys array on every call. Any ideas why ?

@brian-mann
Copy link
Member

@brian-mann brian-mann commented Jan 11, 2018

@lacroixdavid1 then it sounds like you don't have anything in localStorage. Are you sure you're not storing it in SessionStorage? You don't have to guess - just use Dev Tools Application Tab and it'll show you everything that's being stored.

@lacroixdavid1
Copy link

@lacroixdavid1 lacroixdavid1 commented Jan 11, 2018

@brian-mann it is really stored in localstorage with key jStorage

@lacroixdavid1
Copy link

@lacroixdavid1 lacroixdavid1 commented Jan 11, 2018

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

This doesn't clear my localStorage, but i need to filter keys.

@brian-mann
Copy link
Member

@brian-mann brian-mann commented Jan 11, 2018

Put the debugger outside of the if statement. Just look through the stack trace and you can walk back to what Cypress is doing. It's open source so you might have to do some digging. Cypress is just using standard JS API's - no magic here. It should all be transparent.

@lacroixdavid1
Copy link

@lacroixdavid1 lacroixdavid1 commented Jan 11, 2018

In /packages/driver/src/cypress/local_storage.coffee in method clear: (keys, local, remote) :

      .each (item) =>
        if keys.length
          @_ifItemMatchesAnyKey item, keys, (key) =>
            @_removeItem(storage, key)
        else
          @_removeItem(storage, item)

My localStorage gets fully cleared as key.length is 0 on every call.
So, as I can see, because no keys are sent to the methods, it clears whole storage.

Also, in packages/driver/src/cy/commands/local_storage.coffee in method clearLocalStorage = (state, keys), keys is always empty, but local and remote have values.

Why this ? because of the following :

  Cypress.prependListener "test:before:run", ->
    try
      ## this may fail if the current
      ## window is bound to another origin
      clearLocalStorage(state, [])
    catch
      null

you can clearly see that method clearLocalStorage called before each test doesn't get passed any keys.

@brian-mann am I missing something? is there anything broken?

@lacroixdavid1
Copy link

@lacroixdavid1 lacroixdavid1 commented Jan 15, 2018

@brian-mann any suggestions ?

@lacroixdavid1
Copy link

@lacroixdavid1 lacroixdavid1 commented Jan 26, 2018

Those misleading answers and that dead silence leave me disappointed about cypress.io.

@bahmutov
Copy link
Contributor

@bahmutov bahmutov commented Jan 26, 2018

Sorry to hear @lacroixdavid1 - but notice that this goes against our philosophy of making each test independent from others. Suggestion - maybe in afterEach grab all values from localStorage and then inside into localStorage in beforeEach callback? Remember, this is JUST JavaScript, so you can copy / clone / modify everything, just like you can from DevTools console.

@lacroixdavid1
Copy link

@lacroixdavid1 lacroixdavid1 commented Jan 26, 2018

@bahmutov I know it can be done this way, but I feel like Cypress.LocalStorage.clear should get localstorage keys like suggested in previous answers. Did code change since the first answer?

@pietmichal
Copy link

@pietmichal pietmichal commented May 25, 2018

I hope that in the future cypress will allow us to define the behaviour.

My current workaround is creating two helper commands which save and restore the local storage between tests.

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]);
  });
});

And then in one of my tests

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

afterEach(() => {
  cy.saveLocalStorage();
});
@danatemple
Copy link

@danatemple danatemple commented Aug 26, 2018

I have the same issue, and I think a slightly neater workaround using aliases rather than a variable to store the data from LocalStorage:

cy.contains('Welcome to our site').then(() => {
     cy.wrap( localStorage.getItem('currentUser') ).as('currentUser');
     cy.wrap( localStorage.getItem('token') ).as('token');
});

and then restoring them in a beforeEach()

describe('User login', function() {
    beforeEach(function() {
        if (this.currentUser) {
            localStorage.setItem('currentUser', this.currentUser);
            localStorage.setItem('token', this.token);
        }
    });

But I agree whitelisting the specific entries would be a better way to maintain authentication within a describe() context.

@Aaronius
Copy link

@Aaronius Aaronius commented Sep 6, 2018

Regarding @unrealprogrammer's approach, I have something like this:

describe()
  it()
  it()

describe()
 before()
   it()

Inside the before(), I'm calling cy.visit(). The problem is that the call to cy.visit() comes after the global afterEach() that stores off the local storage item, but before the global beforeEach() that restores the local storage item. Therefore, when Cypress tries to visit the URL, our app says it can't find the thing that's supposed to be in local storage and fails (in our case, we're storing an auth token).

The only solution I have for now is to move the cy.visit() call into a beforeEach() or into each it(), both of which require that the browser do more work than is necessary. I would love to hear if there's a better way of handling this. I've tried @brian-mann's approach, but can't get it to work for the same reasons @lacroixdavid1 has stated.

@Aaronius
Copy link

@Aaronius Aaronius commented Sep 6, 2018

I managed to come up with something that seems to be working for our needs. Here it is in case someone else could use it:

let cachedLocalStorageAuth;

function restoreLocalStorageAuth() {
  if (cachedLocalStorageAuth) {
    localStorage.setItem('auth-token', cachedLocalStorageAuth);
  }
}

function cacheLocalStorageAuth() {
  cachedLocalStorageAuth = localStorage.getItem('auth-token');
}

Cypress.on('window:before:load', restoreLocalStorageAuth);
beforeEach(restoreLocalStorageAuth);
afterEach(cacheLocalStorageAuth);
@lacroixdavid1

This comment was marked as off-topic.

@carlos-granados

This comment was marked as off-topic.

@coffenbacher
Copy link

@coffenbacher coffenbacher commented Sep 12, 2018

"There are multiple reasons" is not very compelling.

Back on topic, I think the workarounds in here are pretty helpful. Borderline good enough where I don't think Cypress should prioritize doing anything here.

@EvHaus
Copy link

@EvHaus EvHaus commented Jan 9, 2019

I would honestly just offer an option to "Don't reset localStorage between tests" as well. I would say, in the majority of cases (for us) we want to preserve it, and clearing it manually is literally a one liner we can add in after() where appropriate.

battistone added a commit to battistone/cypress-documentation that referenced this issue Feb 20, 2019
I found an excellent solution, made by pietmichal, on persisting local storage across a spec here: cypress-io/cypress#461

There are valid reasons for persisting local storage across a battery of tests within a spec.js file. I use local storage as the storage location for a JWT token, so my integration tests break if I don't use this code.
@AbdealiJK
Copy link

@AbdealiJK AbdealiJK commented Apr 30, 2019

For the earlier issues on the keys always being empty [].
As per the code i debugged today, it looks like the [] means - delete all keys.

Reference:

So, the code which should work for whitelist would be as:

const clear = Cypress.LocalStorage.clear

Cypress.LocalStorage.clear = function (keys, ls, rs) {
  if (keys && keys.length == 0) {
    keys = Object.keys(localStorage);
  }
  whitelistKeys = [];
  keys = keys.filter(function(i) {return whitelistKeys.indexOf(i) < 0;});
  return clear.apply(this, arguments)
}
@jennifer-shehane
Copy link
Member

@jennifer-shehane jennifer-shehane commented Jul 1, 2019

It's on our roadmap to make handling localStorage and other things that are automatically cleared between tests more customizable, including turning off clearing altogether. You can follow that larger issue here: #686

@pavelsmajda
Copy link

@pavelsmajda pavelsmajda commented Aug 9, 2019

Hi everyone,
our new web application is using Silent sign in method (oidc authentication) and after all tutorials, and hints I'm still unable to keep localStorage and Cookies saved for whole bunch of tests. My goal is to keep user logged in all the time during the tests but I always finish on "/not-authorized" page and with completely cleared session. I'm out of ideas.
Test structure looks like this:

describe('testA', () => {
    it('request url if available' () {
        cy.a
        })
describe('login, function() {
    before(function () {
        cy.login()
    })
    it('go to page A and test something() {
        cy.a
        })
    it('go to page B and test something () {
        cy.a
        })
describe('logout', () => {
    it('Logout', function () {
        cy.a
    })

Can you help me with this issue please? Thanks a lot! :-)

@danatemple
Copy link

@danatemple danatemple commented Aug 10, 2019

I have a similar model to you, 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.

The solution that works robustly for us is to initially prevent any localStorage clearing happening:

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

We did try to implement the "whitelist" solution above by replacing this with a function to process the list of keys, but this did not work reliably.

Then, at the start of each describe() i.e. before the login:
localStorage.clear();

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

@pavelsmajda
Copy link

@pavelsmajda pavelsmajda commented Aug 19, 2019

@danatemple thanks for help, but your solution works for me only partially. I still receive "not-authorized" page after some time tests are running. Thank you anyway.

I'm also trying workaround from @pietmichal but I got the same result.

Silent sign on is called in Cypress very often and requests are often returned without token. Some of the requests contain token, but they're sent late and user is logged out. So I got test (it) where I'm logged out because of "not-authorized" but meanwhile is token sent so next test (it) is passing.

I'm in dead end and it seems I need to wait to this issue fix. In Selenium is this working without any problem. "authority" is called once and returned with token which keep user signed into application.

@Tenmak
Copy link

@Tenmak Tenmak commented Feb 20, 2020

You can achieve this 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)
  }
}

Thanks @brian-mann , I've been inspiring myself from this example to create my own in TypeScript. I'm setting the list of keys to ignore in my Local Storage but still manually clear it once at when Cypress start to ensure at least one authentication case.

Works great for my needs.

import { isArray } from 'util';

// Manually clear the localStorage before each session to ensure authentication at least once
window.localStorage.clear();

Cypress.LocalStorage.clear = (keys?: string[]) => {
  const KEYS_IGNORED = [
    ...
  ];

  // Specific clear
  if (keys !== undefined && isArray(keys) && keys.length > 0) {
    keys.forEach(key => window.localStorage.removeItem(key));
  } else {
    // Full clear
    if (window.localStorage.length > 0) {
      Object.keys(window.localStorage)
        .filter(key => !KEYS_IGNORED.includes(key))
        .forEach(key => window.localStorage.removeItem(key));
    }
  }
};

I've set this code in a custom .ts file that I import un the support folder of Cypress.

@kriegster108
Copy link

@kriegster108 kriegster108 commented Mar 6, 2020

This needs to get implemented soon as possible. super frustrating issue

@MikhailForte

This comment was marked as off-topic.

@Cubo25
Copy link

@Cubo25 Cubo25 commented Apr 9, 2020

Same problem for me 😞 strange think is that it was working like charm for 3 weeks but somehow this issue popped up like 3 days ago ...

@evba1
Copy link

@evba1 evba1 commented May 7, 2020

For the earlier issues on the keys always being empty [].
As per the code i debugged today, it looks like the [] means - delete all keys.

Reference:

So, the code which should work for whitelist would be as:

const clear = Cypress.LocalStorage.clear

Cypress.LocalStorage.clear = function (keys, ls, rs) {
  if (keys && keys.length == 0) {
    keys = Object.keys(localStorage);
  }
  whitelistKeys = [];
  keys = keys.filter(function(i) {return whitelistKeys.indexOf(i) < 0;});
  return clear.apply(this, arguments)
}

This isn't working as expected! In fact it doesn't remove any local storage item at all.
I did a test like this. Set new item in local storage with name 'test'. Then in the whitelistKeys add 'test2'. It will not remove the 'test' item and any other item that exists in local storage.

@jennifer-shehane
Copy link
Member

@jennifer-shehane jennifer-shehane commented Aug 18, 2020

Hey everyone, we've outlined some work to be done to address some of the concerns of 'local storage' in this issue: #8301

It outlines a proposal for a 'session' API, to quickly summarize:

With cy.session, you'll be able to perform UI interactions before taking a "snapshot" of all the session related data and "restore" it before each test.

This session related data would include localStorage.

I recommend reading the entire proposal in #8301 and following there for any updates. Please feel free to express any concerns or questions in that issue concerning the API and add a 👍 for general support.

@whenmoon
Copy link

@whenmoon whenmoon commented Sep 16, 2020

I cannot believe there isn't a proper solution for this. There I was trying out Cypress and thinking how easy it was to use and then realising I couldn't actually use it because it logs me out of the app I'm testing every time I try and run a test. Maybe they think authentication isn't really a thing?

Why not just give an option not to close the browser after stopping tests or allow users to choose not to have some data removed?

Update: the only workaround which I've found is to use this, which is of course incredibly dangerous:

cy.get('#login-form-username').type(`${username}`)
cy.get('#login-form-password').type(`${password}{enter}`) 

But even then I couldn't get a test to run as it timed out because cypress wouldn't wait long enough to load the page / desired url after logging in.

I've just noticed that there are 1200 + issues on this repo which is the highest I've ever seen (React has 400 - 500). Maybe something's going on at Cypress?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet