Cypress fixtures - Cannot read properties of undefined (reading 'data') - cypress

I'm trying to use fixtures to hold data for different tests, specifically user credentials. This is an example of the code. When it gets to the second test I'm getting 'Cannot read properties of undefined (reading 'data')'.
Any ideas why and how I can get around that? Is that wrong?
before(function () {
cy.fixture('credentials').then(function (data) {
this.data = data;
})
})
it('Login correct', () => {
cy.visit('/')
loginPage.signIn(this.data.admin.username,this.data.admin.password)
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
})
And here is my credentials.json file:
{
"admin": {
"username": "*****",
"password": "*****"
}
}

Try using closure variables to assign fixture data.
describe('Some Test', () => {
let data; //closure variable
before(() => {
cy.fixture('credentials').then((fData) => {
data = fData;
});
});
it('Login correct', () => {
cy.visit('/')
loginPage.signIn(data.admin.username, data.admin.password) //usage of closure variable to get the values from the fixtures
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
});
});
Gleb Bahmutov also recommends using closure variables.
I strongly recommend using closure variables instead of this properties. The closure variables are clearly visible and do not depend on function vs () => {} syntax.

As per the cypress docs:
If you store and access the fixture data using this test context
object, make sure to use function () { ... } callbacks. Otherwise, the
test engine will NOT have this pointing at the test context.
So, your it block should also use function:
before(function () {
cy.fixture('credentials').then(function (data) {
this.data = data
})
})
it('Login correct', function () {
cy.visit('/')
loginPage.signIn(this.data.admin.username, this.data.admin.password)
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
})

The above answes are correct. One more way to do the above operation is use cypress.json instead other json files.
In your cypress.json u can add the credentials :
{
"Env": {
"username": "*****",
"password": "*****"
}
}
and refere json directy in your it fucntion.
it('Login correct', function () {
cy.visit('/')
loginPage.signIn(Cypress.env('username'), Cypress.env('password'))
cy.wait(5000)
// assertion
cy.contains('Dashboard').should('be.visible')
})

Related

Cypress: Using same fixture file in multiple test

I need to pass the url and other variable in multiple tests[it-function]. For 1st test code run successfully but for 2nd test it showing error. Is there any workaround or solution? My code is as follows
describe('Document Upload', function()
{
before(function () {
cy.fixture('Credential').then(function (testdata) {
this.testdata = testdata
})
})
//1st test
it('Login as manager',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
login.getPassword().type(this.testdata.passwordDocumentM)
login.getLoginButton().click()
//Logout
login.getUser().click()
login.getLogout().click()
})
//2nd test
it('Create Documents',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
})
})
The error is
I have tried with above and also using before function again but same error
before(function () {
cy.fixture('Credential').then(function (testdata) {
this.testdata = testdata
})
})
//2nd test
it('Create Documents',function()
{
const login = new loginPage()
cy.visit(this.testdata.baseUrl);
login.getUserName().type(this.testdata.userDocumentM)
})
Starting with Cypress version 12 Test Isolation was introduced. This now means the Mocha context (aka this) is completely cleaned between tests.
Mocha context
It used to be (undocumented) that the Mocha context could be used to preserve variables across tests, for example
before(function () {
cy.fixture("example.json").as('testdata') // preserved on "this"
})
it("check testdata 1", function () {
expect(this.testdata).to.not.be.undefined // passes
})
it("check testdata 2", function () {
expect(this.testdata).to.not.be.undefined // fails in Cypress v12
})
but now that does not work.
The use of Mocha context is a bit arbitrary anyway, and requires explicit use of function-style functions which is easy to forget, particularly in places like array method callbacks Array.forEach(() => {}).
You can still use the Cypress context to store data
before(function () {
cy.fixture("example").then(function (testdata) {
Cypress.testdata = testdata;
})
})
it("check testdata 1", function () {
expect(Cypress.testdata).to.not.be.undefined // passes
})
it("check testdata 2", function () {
expect(Cypress.testdata).to.not.be.undefined // passes
})
Note this is also undocumented and may change in the future.
Caching methods
Technically, the way to do this is to set the alias with beforeEach().
The cy.fixture() command caches it's value, so you do not get the read overhead for each test (see Fixture returns outdated/false data #4716)
There is also cy.session() for more complicated scenarios, which would be officially supported.
beforeEach(() => {
cy.session('testdata', () => {
cy.fixture("example").then(testdata => {
sessionStorage.setItem("testdata", testdata)
})
})
})
it("check testdata 1", function () {
expect(sessionStorage.getItem("testdata")).to.not.be.undefined
})
it("check testdata 2", function () {
expect(sessionStorage.getItem("testdata")).to.not.be.undefined
})
Lastly, cypress-data-session which fills a gap
From the docs
Feature
cy.session
cy.dataSession
Command is
official ✅
community 🎁
Can cache
the browser session state
anything
Stability
experimental !!! not in v12
production
Cache across specs
yes
yes
Access to the cached data
no ???
yes
Custom validation
no
yes
Custom restore
no
yes
Dependent caching
no
yes
Static utility methods
limited
all
GUI integration
yes
no
Should you use it?
maybe
yes
Cypress version support
newer versions
all
Cypress.env()
This is another way that is officially supported,
before(() => {
cy.fixture("example").then(testdata => {
Cypress.env("testdata", testdata)
})
})
it("log my fixture 1", function () {
expect(Cypress.env("testdata")).to.not.be.undefined // passes
})
it("log my fixture 2", function () {
expect(Cypress.env("testdata")).to.not.be.undefined // passes
})
but there are still certain tests scenarios that reset the browser completely where this may not work.

Looking for a way tu use Cypress fixtures for all my custom commands outside an it block

I'm building some custom commands and trying to use my fixtures data for all my commands. Right now I'm forced to define it inside an it block.
Looks similar to this:
it("Commands", () => {
cy.fixture("fixtureFile").as("data");
cy.get("#data").then((data) => {
Cypress.Commands.add('login', () => {
cy.visit("/login");
cy.get('#login-email').type(data.userEmail);
cy.get('#login-pass').type(data.userPass, {log: false});
cy.get('.btn').debug().click();
})
Cypress.Commands.add('createTagMedia', () => {
cy.get(".close").click();
cy.get("#form-field-name").type(data.releaseVersion);
cy.get(".form-group-adTag > .CodeMirror > .CodeMirror-scroll").type(data.mediaTag);
cy.get("#media-save-btn").click();
})
})
})
This it block is being count as a test case, Is there a better way to pass this for more than one command at the same time?
The workaround I found was to put everything inside a before block, for example:
before(() => {
cy.fixture("fixtureFile").as("data");
cy.get("#data").then((data) => {
Cypress.Commands.add('login', () => {
cy.visit("/login");
cy.get('#login-email').type(data.userEmail);
cy.get('#login-pass').type(data.userPass, {log: false});
cy.get('.btn').debug().click();
})
Cypress.Commands.add('createTagMedia', () => {
cy.get(".close").click();
cy.get("#form-field-name").type(data.releaseVersion);
cy.get(".form-group-adTag > .CodeMirror > .CodeMirror-scroll").type(data.mediaTag);
cy.get("#media-save-btn").click();
})
})
})
Is there a reason why you won't use the following:
import {data} from '../fixtures/FixtureFile'
Considering you have the following JSON file:
{
"data": {
"userEmail": "blah",
"userPass": "blah",
"releaseVersion": "1"
}
}
You can include this on your tests, commands (Cypress.custom.commands), etc.
before(() => {
const data = cy.fixture("fixtureFile");
cy.login(data);
cy.createTagMedia(data);
})
You could literally do something like the above. With your Cypress.Commands in your command.ts or js whichever you're using.
And make the commands take in a parameter. Then the above before hook would just be in your tests.

Cypress sharing variables/alias between Hooks?

So I have a pretty `before` and `beforeEach` function that runs before all tests. It looks something like this:
describe("JWT Authentication", function() {
before(function() {
// custom command runs once to get token for JWT auth
// alias token as 'user' for further use
cy.get_auth_token().as('user')
})
beforeEach(function() {
// before each page load, set the JWT to the aliased 'user' token
cy.visit("/", {
onBeforeLoad(win) {
// set the user object in local storage
win.localStorage.setItem("token", this.user.token);
}
})
})
it("a single test...", function() {
//do stuff
});
The custom command is also pretty simple:
Cypress.Commands.add("get_auth_token", () => {
cy.request("POST", Cypress.env("auth_url"), {
username: Cypress.env("auth_username"),
password: Cypress.env("auth_password")
})
.its("body")
.then(res => {
return res;
});
})
The custom command itself works and retrieves the token as expected. However when it comes to the beforeEach it has no idea what this.user.token is. Specifically not knowing what user is.
One option is of course calling the command in every beforeEach which is what the JWT Cypress recipe/example spec does. However this feels excessive because in my case I do not NEED to grab the token every test. I only need to grab it once for this set of tests.
So how can I share the token to the beforeEach hook with a Cypress custom command.
I ran a few tests, all the bits seem to work!
The following does not give you an answer, but may help you debug.
Passing token between before() and beforeEach()
Assume we have a user in before(), does it get to the onBeforeLoad() callback?
describe("JWT Authentication", function() {
before(function() {
const mockUser = { token: 'xyz' };
cy.wrap(mockUser).as('user');
})
beforeEach(function() {
cy.visit("http://example.com", {
onBeforeLoad(win) {
console.log(this.user.title); // prints 'xyz'
}
})
})
it("a single test...", function() {
//do stuff
})
});
Is the custom command working
I can't find a generic mock for an Auth check, but any cy.request() that gets an object should be equivalent.
I'm hitting typicode.com and looking for the title property
describe("JWT Authentication", function() {
Cypress.Commands.add("get_auth_token", () => {
cy.request("GET", 'https://jsonplaceholder.typicode.com/todos/1')
.its("body")
.then(body => {
console.log('body', body); // prints {userId: 1, id: 1, title: "delectus aut autem", completed: false}
return body;
});
})
before(function() {
cy.get_auth_token()
.then(user => console.log('user', user)) // prints {userId: 1, id: 1, title: "delectus aut autem", completed: false}
.as('user')
})
beforeEach(function() {
cy.visit("http://example.com", {
onBeforeLoad(win) {
console.log(this.user.title); // prints 'delectus aut autem'
}
})
})
it("a single test...", function() {
//do stuff
})
});
Custom command
This shorter version also seems to work
Cypress.Commands.add("get_auth_token", () => {
cy.request("GET", 'https://jsonplaceholder.typicode.com/todos/1')
.its("body");
})

Mocha .then(done) doesn't work as expected

This question is not about a problem which I can't solve, it is just a curiosity. I'm not very experienced with Mocha, but there's something interesting I've stumbled upon already.
What I want is to use done() to tell Mocha the promise has been resolved.
The following code DOESN'T work:
beforeEach((done) => {
user = new User({ name: 'Dummy' })
user.save()
.then(done)
})
I know I'm passing the result of the user.save() promise to done, but I think it shouldn't be a problem.
Instead this other code works:
beforeEach((done) => {
user = new User({ name: 'Dummy' })
user.save()
.then(() => done())
})
It seems to me that Mocha done() has some kind of control flow which leads to: Error: done() invoked with non-Error: {"_id":"5b65b9d2669f7b2ec0a3d503","name":"Dummy","__v":0}
Is it because done() wants strictly an error as its argument?
Why done() does even care about what I pass to it?
Can you make some example showing why done() argument to be an Error is useful?
Thanks in advance ;)
It is because done() in Mocha only accepts Error argument. In your case, your save() method returns json object not an Error ie new Error('failed save').
If we take a look at mocha test file, we can see that it won't accept other type of arguments.
// https://github.com/mochajs/mocha/blob/master/test/unit/runnable.spec.js#L358
describe('when done() is invoked with a string', function () {
it('should invoke the callback', function (done) {
var test = new Runnable('foo', function (done) {
done('Test error'); // specify done with string/text argument
});
test.run(function (err) {
assert(err.message === 'done() invoked with non-Error: Test error');
done();
});
});
});
But if we see the test when the argument is Error, it works
// https://github.com/mochajs/mocha/blob/master/test/unit/runnable.spec.js#L345
describe('when an error is passed', function () {
it('should invoke the callback', function (done) {
var test = new Runnable('foo', function (done) {
done(new Error('fail'));
});
test.run(function (err) {
assert(err.message === 'fail');
done();
});
});
});
Btw, I suggest that you avoid using done since mocha supports promise by specifying return statement. So, we change the code into
beforeEach(() => {
user = new User({ name: 'Dummy' })
return user.save().then(user => {
// antyhing todo with user
});
});
Hope it helps.

mocha tests Is it possible to reuse the same before() and after() hooks in all my tests?

I use the same before() and after() root level hooks in all my tests to setup and clear my test-database ...
Is there any way to move them into a file and export/import that file ?
Yes. I was able to achieve this behavior by taking advantage of mocha's this context, discussed in their Wiki article about shared behaviors. I am using ts-mocha which accounts for the async/await functionality.
So I wrote functions to login and logout using supertest-session and it looks something like this:
export function authHooks()
{
const email = UsersSeed[0].email;
const password = UsersSeed[0].password;
before(`login ${email}`, async function()
{
await this.session.post('/api/account/login')
.send({ email, password })
.expect(201);
});
after(`logout ${email}`, async function()
{
await this.session.get('/api/account/logout')
.expect(200);
});
}
Inside of my describe I set up the this.session and in my it-statements I can reuse it. It looks a little like this:
import { expect } from 'chai';
import * as Session from 'supertest-session';
import { authHooks } from './authHooks';
describe('Some Suite', () =>
{
before(function()
{
this.session = new Session(SomeApp);
});
after(function()
{
this.session.destroy();
});
describe('when not authenticated', () =>
{
it('returns 403', async function()
{
await this.session.get('/api/jobs')
.expect(403)
.expect({ statusCode: 403, message: 'Forbidden resource' });
});
});
describe('when authenticated', () =>
{
authHooks();
describe('when finding jobs', () =>
{
it('returns all jobs', async function()
{
await this.session.get('/api/jobs')
.expect(200)
.then((response) =>
{
expect(response.body).to.deep.eq(SomeThing);
});
});
});
});
});
I'm not sure if this is the best way to achieve it (I'm not a fan of using function over () => {} personally), but I have confirmed it works.
The above code will not run mainly because I'm protecting code specifics, but hopefully this will provide at least one option for you and maybe someone else can show a better way to do this.

Resources