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

Cypress should clear indexeddb state between test runs #1208

Open
andreiashu opened this issue Jan 19, 2018 · 23 comments
Open

Cypress should clear indexeddb state between test runs #1208

andreiashu opened this issue Jan 19, 2018 · 23 comments
Labels
stage: needs investigating Someone from Cypress needs to look at this type: feature New feature that does not currently exist

Comments

@andreiashu
Copy link

andreiashu commented Jan 19, 2018

Current behavior:

Cypress does not clean the indexeddb state between test runs

Desired behavior:

Cypress should clean the indexeddb state between tests

How to reproduce:

I've created a spec that reproduces this behaviour in a fork of cypress-test-tiny. Just clone, run the spec here and open the console: https://github.com/andreiashu/cypress-test-tiny

  • Operating System: any
  • Cypress Version: 1.4.1
  • Browser Version: any
@jennifer-shehane jennifer-shehane added the stage: needs investigating Someone from Cypress needs to look at this label Jan 19, 2018
@bahmutov
Copy link
Contributor

Big thanks for making a nice demo. Cleaning everything between the tests is important, and we will invest more time, track #686 for the overall theme. For now, can you clear the DB in beforeEach hook?

@andreiashu
Copy link
Author

andreiashu commented Jan 22, 2018

hi @bahmutov yes we currently use the below workaround in our tests. had to strip out some of our project specific code from it but hopefully it works for other people facing this issue.

ps: thanks for cypress, it's a breath of fresh air from the days of selenium testing.

// ./cypress/integration/common/index.js

// loads an external script
export function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.onload = () => resolve();
    script.onerror = err => reject(err);
    script.src = src;
    document.head.appendChild(script);
  });
}

// clears IndexedDb storage
export function clearIndexedDb() {
  return new Promise(resolve => {
    // we clear the indexeddb state ourselves for now
    // @see https://github.com/cypress-io/cypress/issues/1208
    it(`load localforage script`, () =>
      loadScript('https://unpkg.com/localforage@1.5.5/dist/localforage.js').then(() => {
        localforage.config({
          driver: [localforage.INDEXEDDB],
          name: 'MyDb',
          storeName: 'my_store',
          version: '1.0',
        });
      }));

    it(`clear INDEXEDDB storage`, () => {
      cy.visit('/');
      localforage.clear();
    });
  });
}
// ./cypress/integration/mytest.js
import { clearIndexedDb } from './common';

describe('Create Group Chat', () => {
  before(() => {
    clearIndexedDb();
  });

  it('does something on a clean indexeddb state', () => {
    //
  });
});

@brian-mann
Copy link
Member

@andreiashu why couldn't you clear this yourself as per this comment: #1383 (comment)

@andreiashu
Copy link
Author

That's what we actually do in our tests and it works alright.

@jennifer-shehane jennifer-shehane added the type: feature New feature that does not currently exist label Mar 6, 2018
@joelclimbsthings
Copy link

Firebase is now storing their auth data in indexedDB, so this will likely cause some people a few headaches. Clearing in support.index.js provides a good workaround.

@abumalick
Copy link

abumalick commented Jul 26, 2018

found this at https://gist.github.com/innerdaze/2fe0c6a23ed4aee0f94a0302f9de7e6c

indexedDB.deleteDatabase('localforage')

It does the job too

@embiem
Copy link

embiem commented Oct 11, 2018

Thanks @abumalick!

As we use redux-persist with a localForage adapter, we need to clear this between tests.

I used this code inside support/index.js to run it on every new page load to start with a clean redux state:

Cypress.on("window:before:load", win => {
  win.indexedDB.deleteDatabase("localforage");
});

@itsthesteve
Copy link

Thanks @abumalick!

As we use redux-persist with a localForage adapter, we need to clear this between tests.

I used this code inside support/index.js to run it on every new page load to start with a clean redux state:

Cypress.on("window:before:load", win => {
  win.indexedDB.deleteDatabase("localforage");
});

This also works great for clearing the local Firebase storage (firebaseLocalStorageDb as of this comment).

@ChaseBig
Copy link

ChaseBig commented May 6, 2019

I believe I was also able to find a workaround for this issue by just disabling local storage completely. This may not be a viable solution for everyone, though. This config was added to the cypress/plugins/index.js file.

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  on('before:browser:launch', (browser = {}, args) => {

    if(browser.name === 'chrome') {
      args.push('--disable-local-storage')

      //
      return args
    }
  })
}

@RoyalSix
Copy link

RoyalSix commented May 7, 2019

If anyone has tried the above solutions and it's still not working this is what I came up with after a lot of failed attempts. This implementation so far is the most consistent with CI.

//commands.js
let firebase = require('firebase');
Cypress.Commands.add("clearAuth", () => {
  return new Cypress.Promise(async (resolve) => {
    const config = {
      apiKey: Cypress.env('FIREBASE_API_KEY'),
      authDomain: Cypress.env('FIREBASE_AUTH_DOMAIN'),
      databaseURL: Cypress.env('FIREBASE_DATABASE_URL'),
      projectId: Cypress.env('FIREBASE_PROJECT_ID'),
      storageBucket: Cypress.env('FIREBASE_STORAGE_BUCKET'),
      messagingSenderId: Cypress.env('FIREBASE_MESSAGING_SENDER_ID')
    };
    await firebase.auth().signOut();
    await firebase.app().delete();
    firebase.initializeApp(config);
    resolve();
  });
});
//test.js
  describe('Login', function() {
    before(() => {
      cy.visit('/login', {timeout: 100000});
    });
    beforeEach(() => {
      cy.clearAuth();
      cy.reload(true);
    });
    it(`should login`, function() {
      //login code here
    });

@sarink
Copy link

sarink commented Jul 20, 2019

Re: firebase, indexedDB.deleteDatabase('firebaseLocalStorageDb'); worked for me and seems a lot simpler

@RoyalSix
Copy link

RoyalSix commented Feb 29, 2020

Going to make one more suggestion on here if you do not have access to the firebase web api locally

//commands.js
Cypress.Commands.add(
  'clearFirebaseAuth',
  () =>
    new Cypress.Promise(async resolve => {
      const req = indexedDB.deleteDatabase('firebaseLocalStorageDb');
      req.onsuccess = function() {          
        resolve();
      };
    })
)
//test.spec.js
  beforeEach(() => {
    cy.clearFirebaseAuth();
  });

The promise ensures the delete happens before the cypress chain continues

@quantuminformation
Copy link

hehe good to see firebase mentioned above, as thats why im here.

@quantuminformation
Copy link

quantuminformation commented Oct 13, 2020

Turns out

Cypress.on("window:before:load", win => {
    win.indexedDB.deleteDatabase("firebaseLocalStorageDb");
})

is no good if I want to visit another page in the same test, but I like it because I don't have to use

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

everywhere
window:before:load would be good if I could turn it off for a particular test

@quantuminformation
Copy link

This always throws an error

Cypress.Commands.add(
    "clearFirebaseAuth",
    () =>
        new Cypress.Promise(async (resolve) => {
            const req = indexedDB.deleteDatabase("firebaseLocalStorageDb")
            req.onsuccess = function () {
                resolve()
            }
        })
)

image

@Dabolus
Copy link

Dabolus commented Nov 15, 2020

I'm currently using Firebase Auth (which, as pointed out above, now uses indexedDB to store its auth data), but I didn't want to depend on their implementation (I don't want my tests to break if Firebase decides to change their indexedDB name), so I went for a more generic solution that clears all the available indexedDB databases:

Cypress.Commands.add('clearIndexedDB', async () => {
  const databases = await window.indexedDB.databases();

  await Promise.all(
    databases.map(
      ({ name }) =>
        new Promise((resolve, reject) => {
          const request = window.indexedDB.deleteDatabase(name);

          request.addEventListener('success', resolve);
          // Note: we need to also listen to the "blocked" event
          // (and resolve the promise) due to https://stackoverflow.com/a/35141818
          request.addEventListener('blocked', resolve);
          request.addEventListener('error', reject);
        }),
    ),
  );
});

So now in my tests I just do:

beforeEach(() => cy.clearIndexedDB());

@quantuminformation
Copy link

Have you tried the new auth emulator?

@clintharris
Copy link

clintharris commented Jan 17, 2021

For anyone else who is running Jest tests in Cypress (i.e., so that your unit tests can run against a real IndexedDB implementation), Cypress.on("window:before:load", ...) might not be an option since there isn't a site being loaded.

I found that the main issue with deleting the database before each test was making sure the database connection is closed first.

const TODOS_DB = 'todos-db';
const TODOS_STORE = 'todos-store';
let dbPromise: Promise<IDBDatabase> | null = null;

context('IndexedDB test', () => {
  beforeEach(async () => {
    if (dbPromise) {
      // indexedDB.deleteDatabase() will be blocked unless we ensure the db conn is closed first.
      (await dbPromise).close(); // 👈👈👈
      dbPromise = null;
    }

    return new Promise((resolve, reject) => {
      const deleteDbReq = indexedDB.deleteDatabase(TODOS_DB);
      deleteDbReq.onsuccess = () => resolve();
      deleteDbReq.onerror = reject;
      deleteDbReq.onblocked = reject;
    });
  });

  it(`onupgradeneeded() creates expected object stores.`, async () => {
    const db = await getDb();
    expect(db.name).to.equal(TODOS_DB);
    expect(db.objectStoreNames.contains(TODOS_STORE)).equals(true);
  });

  it(`test something else...`, async () => {
    const db = await getDb();
    const txReq = db.transaction(TODOS_STORE, 'readonly');
    const store = txReq.objectStore(TODOS_STORE);
    const addReq = store.add({ id: 123, name: 'Buy more cookies'});
    // ...
  });
});

async function getDb(): Promise<IDBDatabase> {
  if (!dbPromise) {
    dbPromise = new Promise((resolve, reject) => {
      const openreq = indexedDB.open(TODOS_DB, 1);
      openreq.onblocked = reject;
      openreq.onerror = reject;
      openreq.onsuccess = () => resolve(openreq.result);
      openreq.onupgradeneeded = (event) => {
        const db = openreq.result;
        db.createObjectStore(TODOS_STORE, { keyPath: 'id' });
      };
    });
  }
  return dbPromise;
}

@matthewbal
Copy link

matthewbal commented May 21, 2021

I'm currently using Firebase Auth (which, as pointed out above, now uses indexedDB to store its auth data), but I didn't want to depend on their implementation (I don't want my tests to break if Firebase decides to change their indexedDB name), so I went for a more generic solution that clears all the available indexedDB databases:

Cypress.Commands.add('clearIndexedDB', async () => {
  const databases = await window.indexedDB.databases();

  await Promise.all(
    databases.map(
      ({ name }) =>
        new Promise((resolve, reject) => {
          const request = window.indexedDB.deleteDatabase(name);

          request.addEventListener('success', resolve);
          // Note: we need to also listen to the "blocked" event
          // (and resolve the promise) due to https://stackoverflow.com/a/35141818
          request.addEventListener('blocked', resolve);
          request.addEventListener('error', reject);
        }),
    ),
  );
});

So now in my tests I just do:

beforeEach(() => cy.clearIndexedDB());

This solved most of my problems too.
Unfortunately Firefox doesn't seem to support this - only works on Chrome + electron for me. I get the following error when using Firefox 88: window.indexedDB.databases is not a function

There is a discussion here about support in Firefox & Safari
https://gist.github.com/rmehner/b9a41d9f659c9b1c3340

@EtienneBruines
Copy link

EtienneBruines commented Sep 29, 2021

This would fit well with the new session() command, because indexedDB is untouched at the moment, and especially important because all workarounds currently have to wait on active connections getting closed (which may not happen due to a cy.visit() before closing). There is no clean solution at the moment; workaround create flakiness.

This means that if indexedDB contains anything regarding login-state, the session() command is unusable.

@antonyfuentes
Copy link

I second what @EtienneBruines is saying above, the application that I'm testing uses an IndexedDB to store the access token. However since cy.session() doesn't cache/clear the data from there, I ended up implementing my own custom solution with localForage.

@burtonator
Copy link

New to Cypress and was really surprised this wasn't a standard part of the framework. Seems like a pretty obvious win to implement. This is sort of a deal breaker and we can't get this to work as a command.

@Revadike
Copy link

Revadike commented Jul 24, 2023

So, the current status of this issue is that we're waiting for a way to clear indexedDB (without knowing the db name) on Firefox?

In the meantime, I don't see why we can't already implement this already while waiting for Firefox to catch up.

Simply:

if (window.indexedDB.databases) {
    const dbs = await window.indexedDB.databases();
    dbs.forEach(db => { window.indexedDB.deleteDatabase(db.name) });
}

Source: https://gist.github.com/rmehner/b9a41d9f659c9b1c3340
Related Firefox Issue: https://bugzilla.mozilla.org/show_bug.cgi?id=934640

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stage: needs investigating Someone from Cypress needs to look at this type: feature New feature that does not currently exist
Projects
None yet
Development

No branches or pull requests