cypress: cy.request + Callback Queue Race Conditions prevent LocalStorage from being cleared prior to each `it`

Current behavior:

When i have a complex set of conditions (100% reproduced in the Test repository below ๐Ÿ‘‡ ), localStorage is not cleared prior to each it (we do a cy.visit in beforeEach)

One weird condition - cy.request MUST be called before visit and it MUST take some time to respond (Iโ€™ve found as little as 1s can cause failures, but in this example i set my mock api to 2s)

Another weird condition - the application code has to be updating the localStorage in a weird way. In my example, I use redux with redux-localstorage and a setTimeout which increases on each iteration.

We assert localStorage item redux_test is null and it fails:

image

it is 100% reproducible.

Desired behavior:

If we take out the cy.request in the beforeEach, every time it is run, the localStorage is cleared correctly even with all the wacky setTimeout redux stuff done in app code (no other app code changes)

Desired behaviour is, Even when doing cy.request in beforeEach, the output should be all passing:

image

Steps to reproduce:

You can clone this Repository https://github.com/egucciar/cypress-support/tree/local-storage

On the local-storage branch (linked above)

Run:

git checkout local-storage
yarn
yarn start

In another terminal run:

yarn cypress:open

Observe errors. Also comment out the cy.request to observe passing.

Versions

Cypress v3.1.0, Chrome 70, MacOS 10.13.1

Notes

My mocklab API account will have its free trial expired within the next 2 weeks i think, so please take a look ASAP~~

This has been plaguing us forever and i FINALLY Have a standalone reproducer that is 100% reliable ๐ŸŽ‰ ๐Ÿ˜ญ

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 2
  • Comments: 28 (23 by maintainers)

Most upvoted comments

No work has been done on this issue.

K, then this is a proper reproducible example. You can add expectations in the beforeEach. This does pass when commenting out the cy.request() and fails on the 2nd testโ€™s beforeEach when the cy.request() is present.

Sorry for the back and forth. I was just trying to get the lowest reproducible example.

describe('Local Storage Reproducer', () => {
  beforeEach(() => {
    // comment out below for tests to now be passing!
    cy.request({
      method: 'POST',
      url: 'http://nlv4d.mocklab.io/json2',
    }).should((resp) => {
      expect(resp.status).to.eq(200)
    })
    cy.visit('/', {
      onBeforeLoad: (win) => {
        expect(win.localStorage.getItem('redux_test')).to.be.null;
      }
    });
    cy.get('#test');
  })
  context('testing', () => {
    it('1', () => {
      cy.window().then((win) => {
        expect(win.localStorage.getItem('redux_test')).to.be.object;
      })
    })
    it('2', () => {
      cy.window().then((win) => {
        expect(win.localStorage.getItem('redux_test')).to.be.object;
      })
    })
  })
});

I believe this may be related to this issue -> of an asynchronous function from a previous test setting localStorage after the test is done. We will fix this issue in a later release.

Workaround

Manually clear localStorage in the onBeforeLoad in cy.visit(). The tests pass when below is added.

cy.visit('/', {
  onBeforeLoad: (win) => {
    win.localStorage.clear()
    expect(win.localStorage.getItem('redux_test')).to.be.null;
  }
});

After a closer look, it seems a race condition indeed.

The local storage cleanup happens in the test:before:run listener: https://github.com/cypress-io/cypress/blob/1690d41d763704d18f6ab638f9a19a910cd70266/packages/driver/src/cy/commands/local_storage.js#L25-L33

A bit down the line, the window object gets updated during the window:before:load event: https://github.com/cypress-io/cypress/blob/1690d41d763704d18f6ab638f9a19a910cd70266/packages/driver/src/cypress/cy.js#L1194-L1198

Which is in turn triggered from the proxy inject code: https://github.com/cypress-io/cypress/blob/1690d41d763704d18f6ab638f9a19a910cd70266/packages/proxy/lib/http/util/inject.ts#L13-L22

So, If there was a cross origin request, it sets both events (test:before:run and window:before:load) apart in time, which lets us reliably reproduce the bug. At this point, cleanup happens earlier than it should, only cleaning the window object of the previous test (are they different objects though?). In fact, removing the cy.request code and running the tests with an open console somehow gives a similar effect.

It looks like the reproducer tests pass only when window:before:load is triggered early enough to update the reference to window before the local storage cleanup. However, I couldnโ€™t confirm this with a console.log.

This comment describes something similar: https://github.com/cypress-io/cypress/blob/develop/packages/driver/src/cypress/cy.js#L919-L926

Its been few months after this issue had been found. Is there any progress on fixing this issue? @jennifer-shehane

Thanks you @egucciar for this bug report. I was facing the same issue in my project (with basically the same setup, just with vue + vuex + vuex-persistedstate) and i have spent ages trying to figure out was is wrong in my specs. +1