JS Cypress: unable to use alias for array - cypress

I am quite new to Cypress and I have some before() calling commands that create bunch of things via API calls and return the IDs of created which I use in the after() for removing them, but somehow it works perfectly if I only return one ID and store in the alias but will fail if I store an array of IDs in alias, is this intended or I did something wrong.
in my code:
before(() => {
cy.setupEnv()
.as('access_token')
.then((token) => cy.setupFlow(token).as('data_id'))
})
after(function () {
console.log(this.access_token)
console.log(this.data_id)
})
console.log(this.data_id) shows fine if setupFlow returns only one ID but becomes undefined if I try to return [id1,id2,id3]and store the array using .as("data_id")

You've struck a strange issue, worth raising with Cypress.
It only seems to happen if you have more than one test.
For example, if I run the following it logs the array.
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
return [1,2,3]
}).as('data_id')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // [1,2,3]
})
it('test1', () => {
console.log('test1')
expect(true).to.eq(true)
})
If I add a test it logs undefined!
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
return [1,2,3]
}).as('data_id')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // undefined
})
it('test1', () => {
console.log('test1')
expect(true).to.eq(true)
})
it('test2', () => {
console.log('test2')
expect(true).to.eq(true)
})
One way around this is to use Cypress.env() instead
before(() => {
cy.wrap(1).as('access_token')
cy.then(() => {
Cypress.env('data_id', [1,2,3])
return [1,2,3]
}).as('data_id')
console.log('before')
})
after(function () {
console.log(this.access_token) // 1
console.log(this.data_id) // undefined
console.log(Cypress.env('data_id')) // [1,2,3]
})
beforeEach(function() {
console.log(cy.state())
console.log(this.data_id)
cy.wrap(this.data_id).as('data_id')
})
it('test1', () => {
expect(true).to.eq(true)
console.log('test1')
})
it('test2', () => {
console.log('test2')
expect(true).to.eq(true)
})

Assuming that cy.setupFlow(token) generates an array of values something like [id1, id2, id3]. This will work even when there is one value in the array. You after each should look this:
after(function () {
cy.get('#data_id').then((data_id) => {
//Get individual values
cy.log(data_id[0])
cy.log(data_id[1])
cy.log(data_id[2])
//Get all values using forEach
data_id.forEach((id) => {
cy.log(id) //get all values one by one
})
})
})
I created a small POC for this and it is working as expected.Below are the results.
Code:
describe('SO Ques', () => {
before(function () {
cy.wrap([1, 2, 3]).as('array')
})
it('SO Ques', function () {
cy.log('Hello')
})
after(function () {
cy.get('#array').then((array) => {
cy.log(array[0])
cy.log(array[1])
cy.log(array[2])
})
})
})
Result:

Related

How to get the return value from a custom command in Cypress?

I am writing a long test so I added the most reusable part to a Command Folder, however, I need access to a certain return value. How would I get the return value from the command?
Instead of directly returning the salesContractNumber, wrap it and then return it like this:
Your custom command:
Cypress.Commands.add('addStandardGrainSalesContract', () => {
//Rest of the code
return cy.wrap(salesContractNumber)
})
In your test you can do this:
cy.addStandardGrainSalesContract().then((salesContractNumber) => {
cy.get(FixingsAddPageSelectors.ContractNumberField).type(salesContractNumber)
})
Generally speaking, you need to return the value from the last .then().
Cypress puts the results of the commands onto the queue for you, and trailing .then() sections can modify the results.
Cypress.Commands.add('addStandardGrainSalesContract', () => {
let salesContractNumber;
cy.get('SalesContractsAddSelectors.SalesContractNumber').should($h2 => {
...
salesContractNumber = ...
})
.then(() => {
...
return salesContractNumber
})
})
cy.addStandardGrainSalesContract().then(salesContractumber => {
...
Or this should work also
Cypress.Commands.add('addStandardGrainSalesContract', () => {
cy.get('SalesContractsAddSelectors.SalesContractNumber').should($h2 => {
...
const salesContractNumber = ...
return salesContractNumber; // pass into .then()
})
.then(salesContractNumber => {
...
return salesContractNumber // returns to outer code
})
})
cy.addStandardGrainSalesContract().then(salesContractumber => {
...
Extra notes:
const salesContractHeader = $h2.text() // don't need Cypress.$()
const salesContractNumber = salesContractHeader.split(' ').pop() // take last item in array

Cannot read properties of undefined (reading 'data')

This is my code
describe('Demo', () => {
before(() => {
cy.fixture('example.json').then(function (data) {
this.data = data
})
})
it('Fixture demo', () => {
cy.visit('https://admin-demo.nopcommerce.com/login')
cy.get('input[name=Email]').clear().type(this.data.email)
cy.get('input[name=Password]').clear().type(this.data.password)
cy.get('.login-button').click()
})
})
Error is showing in this line
cy.get('input[name=Email]').clear().type(this.data.email)
^
To fix please change arrow functions () => { to non-arrow functions function() {.
If using this you need to use function().
before(function() {
cy.fixture('example.json').then(function(data){
this.data=data
})
})
it('Fixture demo', function() {
cy.visit('https://admin-demo.nopcommerce.com/login')
cy.get('input[name=Email]').clear().type(this.data.email)
...
})
You are getting confused with component class approach, Cypress test file is not a class file, you shouldn't use this keyword here
To fix this you can declare a global variable for your data and use it later in your tests
describe('Demo', () => {
let exampleData; //Create variable here
before(() => {
cy.fixture('example.json').then(function (data) {
exampleData = data //Assign data here
})
})
it('Fixture demo', () => {
cy.visit('https://admin-demo.nopcommerce.com/login')
cy.get('input[name=Email]').clear().type(exampleData.email) //Use it like this
cy.get('input[name=Password]').clear().type(exampleData.password) //Use it like this
cy.get('.login-button').click()
})
})

Jest assert promise resolved with object containing

Say I want to test a module that returns a Promise:
function myFunc () {
return Promise.resolve({
anArray: [1,2,3,4,5,6]
})
}
Using Jest, how can I assert the length of the array contained in the object the promise resolves to?
describe('myFunc', () => {
it('returns array of length 6', () => {
expect.assertions(1)
return expect(myFunc()).resolves // ... something here
})
})
If it were synchronous, I would do something like:
let result = myFunc()
expect(result.anArray.length).toBe(6)
How does this work with Promises?
There are two ways either return the promise from the test and make the assertion in the then or make your test using async/await
describe('myFunc', () => {
it('returns array of length 6', () => {
expect.assertions(1)
return expect(myFunc())
.then(result => expect(result).toEqual([1,2,3,4,5,6]);)
})
})
describe('myFunc',() => {
it('returns array of length 6', async() => {
const result = await expect(myFunc())
expect(result).toEqual([1,2,3,4,5,6]);)
})
})
The docs on this topic
The easiest approach is to use .resolves like you were starting to do in your sample.
You just need to chain .toMatchObject to the result:
function myFunc () {
return Promise.resolve({
anArray: [1,2,3,4,5,6]
})
}
describe('myFunc', () => {
it('returns array of length 6', () => {
expect(myFunc()).resolves.toMatchObject({ anArray: [1,2,3,4,5,6] }); // Success!
})
})
This will assert that the object has at least the anArray property set to [1,2,3,4,5,6] (it can have other properties as well).
Note that PR 5364 makes it so resolves validates its arguments synchronously so you don't even have to return, await, or use done if you are using Jest >= v22.2.0.
Update
Sounds like the goal is to only assert on the length of the array.
For that you would need to get the result of the Promise (as has been described in the previous answers), then use .toHaveLength to assert the length of the anArray property:
describe('myFunc', () => {
it('returns array of length 6', async () => {
const result = await myFunc();
expect(result.anArray).toHaveLength(6); // Success!
})
})
A way to do this is to pass a done callback, to mark your test as asynchronous and force jest to wait until you call done():
describe('myFunc', () => {
it('returns array of length 6', (done) => {
expect.assertions(1)
myFunc().then((values) => {
expect(values).toEqual([1,2,3...]);
done();
});
})
})
You can just return a Promise as well, without the need for done:
describe('myFunc', () => {
it('returns array of length 6', () => {
expect.assertions(1)
return myFunc().then((values) => {
expect(values).toEqual([1,2,3...]);
});
})
})
You can read more about this here.

Mocha not registering 'it' blocks inside promise list

I'm trying to write a test that will run a GET over all items. To do this, I get that list in the before block, then I want to have an it block for each item. I am trying to do this by putting the it block inside itemList.forEach. However, I suspect that the problem here is that the blocks never get registered for the test. How can I run this test as desired?
let token;
let itemList;
describe('GET items/:itemId with Admin', async () => {
before(async () => {
// NOTE: item.find({}) returns a promise of a list of all items
itemList = await item.find({});
console.log(item[0]._id) // this logs correctly!
const res = await userLogin(admin);
token = res.body.accessToken.toString();
});
it('registers initial it test', () => {
// This test passes and logs the statement
console.log('first test registered')
console.log(itemList.length) // successfully logs non-zero value
})
await itemList.forEach(async (item) => {
it('respond with json with a item', () => {
const itemId = item._id;
return getItem(itemId, token)
.then((response) => {
assert.property(response.body, '_id');
});
});
});
});
Afaik the before setup runs before every it test. It doesn't run immediately, and definitely does not wait for anything until you try to iterate your itemList. I think you will need to do either
describe('GET items/:itemId with Admin', async () => {
let token;
before(async() => {
const res = await userLogin(admin);
token = res.body.accessToken.toString();
});
// a list of all items for which tests should be created
const itemList = await item.find({});
console.log(itemList.length) // successfully logs non-zero value
for (const item of itemList) {
it('responds with json for item '+item, () => {
const itemId = item._id;
return getItem(itemId, token).then((response) => {
assert.property(response.body, '_id');
});
}
});
or
describe('GET items/:itemId with Admin', () => {
let itemList;
let token;
before(async() => {
[itemList, token] = await Promise.all([
item.find({}),
userLogin(admin).then(res => res.body.accessToken.toString())
]);
});
it('responds with json for every item', () => {
return Promise.all(itemList.map(item => {
const itemId = item._id;
return getItem(itemId, token)
.then((response) => {
assert.property(response.body, '_id');
});
});
}));
});
});
This is the solution I ended up with. I ended up putting a new describe block in the before block. The before block results the promise that gives the list of items. There is an it block in the top level so that mocha registers the test in the first place.
describe('GET items/:itemId with Admin', async () => {
before((done) => {
Item.find({}).then(async (itemList) => {
// create the admin user to get the items with
await createUsers([admin]);
const res = await userLogin(admin);
const token = res.body.accessToken.toString();
itemList.forEach((item, index) => {
const itemId = item._id;
describe(`get item number ${index}: _id: ${itemId}`, () => {
it('responds with item id', () =>
getItem(item, token)
.expect(200)
.then((response) => {
assert.notProperty(response.body, 'error');
assert.property(response.body, '_id');
assert.equal(response.body._id, itemId);
}));
});
});
done();
});
});
// If there is no it block here, it will not run the before block!
it(`register the initial it`, () => {
assert.equal('regression test!', 'regression test!');
});
});

How can I return an array of Promises from a then clause

I saw a similar question here which doesnt solve my problem. I am trying to run a cron job every 10 hours that lets me get the categories first and then based on the categories, i find the information for each category. How can I simplify the below Promise. I am NOT using Bluebird or Q, this is the native JS promise. Honestly, the code below looks like the same callback hell Promises were supposed to avoid, any suggestions
flipkart.getAllOffers = function () {
interval(43200, () => {
flipkart.findAllCategories()
.then((categories) => {
flipkart.save('flipkart_categories.json', categories)
if (categories) {
for (let item of categories) {
flipkart.findAllForCategory(item.category, item.top)
.then((items) => {
flipkart.save('flipkart_top_' + item.category + '.json', items)
}).catch((error) => {
console.log(error)
})
}
}
})
.catch((error) => {
console.log(error)
})
})
}
function interval(seconds, callback) {
callback();
return setInterval(callback, seconds * 1000);
}
If you stop using an extra level of indent just for .then(), then you have a pretty simple structure.
One .then() handler that contains
an if() statement
that contains a for loop
that contains another async operation
In this modified version, half your indent comes from your if and for which has nothing to do with promises. The rest seems very logical to me and doesn't at all look like callback hell. It's what is required to implement the logic you show.
flipkart.getAllOffers = function () {
interval(43200, () => {
flipkart.findAllCategories().then((categories) => {
flipkart.save('flipkart_categories.json', categories)
if (categories) {
for (let item of categories) {
flipkart.findAllForCategory(item.category, item.top).then((items) => {
flipkart.save('flipkart_top_' + item.category + '.json', items)
}).catch((error) => {
console.log(error)
throw error; // don't eat error, rethrow it after logging
});
}
}
}).catch((error) => {
console.log(error)
})
})
}
If flipkart.save() is also async and returns a promise, then you probably want to hook those into the promise chain too.
You can always create a helper function that may improve the look also like this:
flipkart.getAllOffers = function () {
interval(43200, () => {
flipkart.findAllCategories().then(iterateCategories).catch((error) => {
console.log(error);
})
})
}
function iterateCategories(categories) {
flipkart.save('flipkart_categories.json', categories);
if (categories) {
for (let item of categories) {
flipkart.findAllForCategory(item.category, item.top).then((items) => {
flipkart.save('flipkart_top_' + item.category + '.json', items);
}).catch((error) => {
console.log(error);
});
}
}
}
If you're trying to collect all the results (something your title implies, but your question doesn't actually mention), then you can do this:
flipkart.getAllOffers = function () {
interval(43200, () => {
flipkart.findAllCategories().then(iterateCategories).then((results) => {
// all results here
}).catch((error) => {
console.log(error);
});
})
}
function iterateCategories(categories) {
flipkart.save('flipkart_categories.json', categories);
let promises = [];
if (categories) {
for (let item of categories) {
let p = flipkart.findAllForCategory(item.category, item.top).then((items) => {
flipkart.save('flipkart_top_' + item.category + '.json', items);
}).catch((error) => {
console.log(error);
});
promises.push(p);
}
}
// return promise here that collects all the other promises
return Promise.all(promises);
}

Resources