Cypress with MeteorJS: user does not stay logged in

If you have to wait for a successful login then you are probably doing it wrong. When your app logs in, some part of the up should update to indicate that the user has logged in.

In cypress, you should be doing a cy.get() to await a successful login.

There should never really be a use case for cy.wait(). If you find yourself having to use it then either your ui is a bad design as it provide no user feedback or you should be cy.getting another object which will inherently wait for it to appear.

Our latest rock-solid discovery when it comes to cypress and meteor, is to set variables on the window object. It’s not magic, and it’s recommended by Cypress in their docs, but it really made things much less flaky.

For example, we have a top level React component that uses useTracker to get the current user and make it available through context. It looks something like this:

const CurrentUserProvider = ({ children }) => {
  const currentUser = useTracker(() => Meteor.user());
  window.currentuser = currentUser; // This is for cypress
  
  return <Provider value={currentUser}>{children}</Provider>
}

This way, in your custom cypress helper you can do this:

Cypress.Commands.add(
  'meteorLogin',
  (email = TEST_EMAIL, password = TEST_PASSWORD) => {
    cy.getMeteor().then(Meteor => {
      let promise;

      if (Meteor.userId()) {
        // Logout first if the user is already logged in
        promise = cy.meteorLogout();
      } else {
        promise = Promise.resolve();
      }

      return promise.then(
        () =>
          new Cypress.Promise((resolve, reject) => {
            Meteor.loginWithPassword(email, password, err => {
              if (err) {
                return reject(err);
              }
              resolve();
            });
          }),
      );
    });
    cy.window().should(win => {
      // Make sure currentUser is truthy
      expect(!!win.currentUser).to.equal(true);
    });
  },
);

That final line is really important and makes sure that your UI, Meteor and Cypress are in sync. And it’s just as easy to check if that value is undefined (loading) or null (logged out).

Another one we’re using, is in all before hooks of every suite, is to wait for meteor to be ready:

// Somewhere in your frontend code
Meteor.startup(() => {
  window.appIsReady = true;
})

And then in your before, call this command:

Cypress.Commands.add('startTest', (url = '/') => {
  cy.visit(url);
  cy.window().should('have.property', 'appReady', true);
  cy.checkConnection();
});
before(() => {
  cy.startTest('/first-page'); // Guarantee readiness of your app
  cy.meteorLogin('test@mail.com', '12345678'); // Guaranteed to succeed and be logged in
})
6 Likes

Regarding the topic, we’ve completely removed automatic clearing of local storage, and we now do it ourselves. This keeps us logged in for the entire test suite:

const clearLocalStorage = () => {
  localStorage.clear();
};
const doNotClearLocalStorage = () => {};

// By default Cypress clears local storage between every spec. We disable Cypress local storage clearing function, so that we can test local storage usage
// TODO after Cypress adds support for lifecycle events use them instead to do it: https://github.com/cypress-io/cypress/issues/686
Cypress.LocalStorage.clear = doNotClearLocalStorage;
// We need own version to manually clear local storage in tests, because above one disables also cy.clearLocalStorage
Cypress.Commands.add('clearLocalStorage', clearLocalStorage);

So in the tests in which you need to clear local storage, you can call it yourself with cy.clearLocalStorage();, call logout right after, and you’ll be guaranteed to have a clean test afterwards.

5 Likes

Any tips on how to setup 1. ?
Thanks for the rest of the solution btw.