Cypress automatically clears all cookies before each test to prevent state from building up.
You can take advantage of Cypress.Cookies.preserveOnce() or even whitelist cookies by their name to preserve values across multiple tests. This enables you to preserve sessions through several tests.
So your auth cookies just get deleted by default. You either should execute login script in every test or just preserve auth cookies.
Cypress docs recommend creating an automated, non-UI based flow for logging in and not to use a persistent session.
This makes the tests isolated and help you pinpoint issues, rather than have a single bug make all following tests potentially fail (or not fail, when they should).
@arggh I read about this too, but I still don’t know how to get it.
My first thought was that stubbing Meteor’s userId and user functions could do the trick, but I’m still figuring out how to do that (I just discovered Cypress a couple of days ago and it truly looks awesome and fun).
Do you have any recommendations on how to get there?
@ fullhdpixel as @arggh here points out, the Cypress people recommend against using the UI to login and run your tests. In fact, it is the first of a list of “Anti patterns” they’ve defined:
Yet there are moments in which this could be useful and in fact, necessary.
Brian Mann, CEO and founder of Cypress comments in a talk that he does this for testing the login screen only (meaning having a separate login.spec.js file). Here you test the real login works well and you take that confidence up to 100%. The point he makes is that using real login in subsequent tests only make those test slower (by a factor of 4x or 5x in his example) and adds 0% more confidence on its test suite. Hence the recommendation. Here the specific section of the video:
Now we only need to find out how to do it programatically
Create a module that is loaded only when you are running integration tests
In this module, create a client side function createTestUser that calls Meteor’s Account.createUser with credentials you wish to use, so a fresh user is created for each test
Assign this function to window
Create a Cypress command login, something like this:
function login(win) {
win.createTestUser().then(() => win.appReady = true);
}
Cypress.Commands.add('login', () => {
cy.visit('/'); // load the app
cy.window().then(login); // create user
cy.window().should('have.property', 'appReady', true); // wait for the user to get created
});
Use this command in the beforeEach hooks of your Cypress tests
That’s an interesting way to do it. We just login with a user that are in our test fixtures and we reset the db to these with each spec. Here’s some commands you can grab - some of which we grabbed from various forum posts and github issues over the months we’ve been using cypress. Apologies if I can’t remember who to attribute all the various bits to but I think @mitar & @florianbienefelt helped
Cypress.Commands.add("resetDb", () => {
const mongoport = 3001;
cy.exec(
`mongorestore --host localhost:${mongoport} --drop --gzip --archive=./tests/testFixtures.agz`
);
});
Cypress.Commands.add("getMeteor", () =>
cy.window().then(({Meteor}) => {
if (!Meteor) {
// We visit the app so that we get the Window instance of the app
// from which we get the `Meteor` instance used in tests
cy.visit("/");
return cy.window().then(({Meteor: MeteorSecondTry}) => MeteorSecondTry);
}
return Meteor;
})
);
Cypress.Commands.add("callMethod", (method, ...params) => {
Cypress.log({
name: "Calling method",
consoleProps: () => ({name: method, params})
});
cy.getMeteor().then(
Meteor =>
new Cypress.Promise((resolve, reject) => {
Meteor.call(method, ...params, (err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
})
);
});
Cypress.Commands.add("login", () => {
Cypress.log({
name: "Logging in"
});
cy.getMeteor().then(
Meteor =>
new Cypress.Promise((resolve, reject) => {
Meteor.loginWithPassword("you@example.com", "password123", (err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
})
);
});
Cypress.Commands.add("logout", () => {
Cypress.log({
name: "Logging out"
});
cy.getMeteor().then(
Meteor =>
new Cypress.Promise((resolve, reject) => {
Meteor.logout((err, result) => {
if (err) {
reject(err);
}
resolve(result);
});
})
);
});
Cypress.Commands.add("allSubscriptionsReady", (options = {}) => {
const log = {
name: "allSubscriptionsReady"
};
const getValue = () => {
const DDP = cy.state("window").DDP;
if (DDP._allSubscriptionsReady()) {
return true;
} else {
return null;
}
};
const resolveValue = () => {
return Cypress.Promise.try(getValue).then(value => {
return cy.verifyUpcomingAssertions(value, options, {
onRetry: resolveValue
});
});
};
return resolveValue().then(value => {
Cypress.log(log);
return value;
});
});
Thank a lot @marklynch ! I’ve added the login/logout ones from your snippets :). I’m getting logged in programmatically now :).
I’d already grabbed some of those from this code:
Still wondering if there is some way to stub Meteor itself (I’ve been trying using cy.stub once I get the Meteor object with one of your commands, but still no luck).
I think that it should be theoretically the fastest mean to login in a test that does not intent to test the login functionality per-se.
I also thought that if I can achieve that, other tests might be eased out. For example, playing with Meteor.status to simulate the different states of the connection with the server and verify how the application responds to that.
Regards and thanks for the quick responses people!
The reason why cypress logs out the user is down to it clearing local storage at the start of each test which wipes out the meteor login token.
I made my cypress tests work by caching a valid meteor login token from local storage at the end of my login function which will login once via the ui and cache the login token in a cypress function. Use a onbefore block at the start of each cypress test to write the cached token back into local storage for each cypress test case and each test will be logged in.
You need to remember to clear the cache if your test changes user by logging out. You can preventing having to logout by switching login tokens on the fly to become another user
Works quite reliably for me and simulates normal user behaviour - login once and reuse the token.
[quote=“marklynch, post:7, topic:50093”]
allSubscriptionsReady
[/quote
Thank you for this very useful code, can you please explain what allSubscriptionsReady exactly do ?
Sorry to reopen an old thread, but I’m having a hard time getting Cypress to wait for the login. How are you doing the async login with a beforeEach. Would love to see what an example of a test with your Commands. Thank you for this comment overall a life saver.
Hey Scott, sorry, didn’t login to the forum for while. Wait’s are often needed (and I agree, it’s a bit worrying) but I don’t have any straight after login. Cypress is great overall but there are a few too many cases where things don’t seem to play out exactly the same on different setups or when you update the version. It’s improving though.
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.