wait for Cypress each() function to finish - javascript

I am calling a function populateArray which picks elements on the page using children() function. I want to store the attribute values into an using each() function.
Here is what I am doing
static populateArray(){
let arr = []
cy.xpath(NODE_PREVIEW_PANEL).children(NODE_TYPE)
.each((element, index, list) => arr.push(cy.wrap(element).invoke('attr', 'data-testid')))
}
The problem is when I call this function by assigning it to a variable
actualArray = ArticlePage.populateArray()
its not waiting for underlying each() function to complete fully. It just pick partial values and proceeds. I want actualArray to have values only after populateArray is fully resolved.

How about using Cypress.Promise? From the docs
Cypress is promise aware so if you return a promise from inside of commands like .then(), Cypress will not continue until those promises resolve
const populateArray = () => {
return new Cypress.Promise((resolve, reject) => {
let arr = []
cy.xpath('//ul')
.children('li')
.each(element => arr.push(element.attr('data-testid'))) // NOTE different push
.then(() => {
return resolve(arr)
})
})
}
Call with await (test must be async),
it('gets the array', async () => {
const actualArray = await ArticlePage.populateArray();
expect(actualArray).to.deep.equal(['1', '2', '3']); // whatever is in 'data-testid'
})

Since .each() can be chained - you can chain a .then and just type the code you need to execute after the .each() process is done:
static populateArray(){
let arr = []
cy.xpath(NODE_PREVIEW_PANEL).children(NODE_TYPE)
.each((element, index, list) => arr.push(arr.push(element.attr('data-testid')))
.then(list => {
cy.wrap(arr).as('actualArray')
})
}
And in test code
populateArray()
cy.get('#actualArray')
.then(actualArray => {
//code that needs the array data
})

Another answer using custom commands
Cypress.Commands.add("populateArray", (parentSelector, childSelector, arrayAllias) => {
let arr = []
cy.xpath(parentSelector).children(childSelector)
.each((element, index, list) => arr.push(element.attr('data-testid')))
.then(()=>{
cy.wrap(arr).as(arrayAllias)
})
})
And in test code
cy.populateArray(NODE_PREVIEW_PANEL, NODE_TYPE, 'actualArray')
cy.get('#actualArray')
.then(actualArray => {
//code that needs the array data
})

Related

How can I wait for multiple Promise.all()

I'm working with some old code and I'm trying to return results for two arrays of promises.
So basically body contains first and second which are arrays of ids. I know that the below code works for a small array of of 1 to 5 ids, but if I were to do a few hundred, would the resolve fire before I get the hundred of results from both promise arrays?
const doSomething = (body) => {
return new Promise((resolve, reject) => {
const promisesOne = body.first.map((id) =>
doSomethingOne(id)
);
const promisesTwo = body.second.map((id) =>
doSomethingTwo(id)
);
let response = {
first: {}
second: {}
headers: 'mock headers'
};
Promise.all(promisesOne).then((results) => {
response.first = results;
});
Promise.all(promisesTwo).then((results) => {
response.second = results;
});
resolve(response);
});
};
Also I won't be able to refactor this to async/await as this codebase does not use it.
would the resolve fire before I get the ... results from both promise arrays?
Yes, absolutely because you aren't waiting for them to resolve.
Simply wrap the two Promise.all() promises in another Promise.all() to wait for everything then create the final object you want.
const doSomething = (body) => {
return Promise.all([
Promise.all(body.first.map(doSomethingOne)),
Promise.all(body.second.map(doSomethingTwo)),
]).then(([first, second]) => ({
first,
second,
headers: "mock headers",
}));
};
You could make this generic by creating a map of body property names to their corresponding mapping function. For example
const mappers = {
first: doSomethingOne,
second: doSomethingTwo,
};
const doSomething = (body) => {
return Promise.all(
Object.entries(mappers).map(([key, mapper]) =>
Promise.all(body[key].map(mapper)).then((data) => ({ [key]: data }))
)
)
.then((results) => Object.assign({ headers: "mock headers" }, ...results));
};
Your code will not work at the moment - the resolve(response) will run before either of the Promise.alls are done.
First, refactor your .first and .second (etc) properties so that they're arrays, rather than unique string properties. Then, it'll be easy to .map them with two Promise.alls - one to wait for an individual array to fulfill, and an outer one to wait for all arrays to fulfill.
The same thing should be done for the doSomethingOne etc functions - to make the code easy to work with, use an array of functions instead of having many standalone identifiers.
Also, there's no need for the explicit Promise construction antipattern.
const doSomethings = [
doSomethingOne, // define these functions here
doSomethingTwo,
];
const doSomething = (body) => Promise.all(
body.arrayOfArraysOfIds.map(
(arr, i) => Promise.all(
arr.map(id => doSomethings[i](id))
)
)
)
.then((results) => ({
headers: 'mock headers',
results,
});

Complete one promise array first, then complete another promise array JavaScript

I have a problem with promises and have no idea how resolve this:
My idea is to have two methods that are a "dispatcher". In testFetchFaceCharacter() call all promises that I need to resolved first. Need all data from state: {Body:{}, TopA:{}, eyes:{}} When testFetchCharacter() finished, immediately start testFetchTopCharacter(), only if all previous promises executed successfully.
However, at this point (with this code) have a errors. The promises aren't executed "Synchronously". still retrieved "asynchronously". Which "should not happen". Since it "reduce" (from what I read in several articles) avoided that behavior.
const buildCharacter = (state) => {
try {
testFetchFaceCharacter(state);
testFetchTopCharacter(state);
} catch (e) {
console.error(e + "En buildCharacter");
}
const testFetchCharacter = (state) => {
const promises = [
fetchCustom(state.Body.id, state.Body.file),
fetchCustom(state.TopA.id, state.TopA.file),
fetchCustom(state.eyes.id, state.eyes.file),
fetchCustom(state.mouth.id, state.mouth.file),
fetchCustom(state.nose.id, state.nose.file),
fetchCustom(state.eyebrow.id, state.eyebrow.file),
fetchCustom(state.Clothing.id, state.Clothing.file),
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
const testFetchTopCharacter = (state) => {
const promises = [
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
Y try this:
Execute -> Body
Execute -> TopA
Execute -> [eyes, mouth, nose, Clothing, eyebrow] //No matter the order
then
Execute [beard, hat, hair, glass] //not matter the order
First of all, there is a mistake in your code. You need to understand that as soon as you called a function, you triggered a logic that does something, even if you don't listen to the promise right away, the logic is executing.
So what happened, is that you launched all actions in "parallel" when you are doing function calls in the promises array.
Solution A
You need to "postpone" the actual call of a function until the previous function was successful, you can either do it manually, e.g.
const testFetchTopCharacter = async (state) => {
await fetchCustom(state.beard.id, state.beard.file),
await fetchCustom(state.hat.id, state.hat.file),
await fetchCustom(state.hair.id, state.hair.file),
await fetchCustom(state.glass.id, state.glass.file)
}
Solution B
If you want to use reducer you need to use callback in that array, so that when promise is completed you call the next callback in the chain.
const testFetchTopCharacter = (state) => {
const promises = [
() => fetchCustom(state.beard.id, state.beard.file),
() => fetchCustom(state.hat.id, state.hat.file),
() => fetchCustom(state.hair.id, state.hair.file),
() => fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce((promise, callback) => promise.then(callback), Promise.resolve());
}
Solution C
If an order doesn't matter to you just do Promise.all
const testFetchTopCharacter = (state) => {
return Promise.all([
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
]);
}

Using a forEach loop to overwrite values

Alright so I have a code like so
giveaways.forEach( x => {
if (x.extraData) {
const guild = client.guilds.cache.get(x.extraData.server)
const channel = guild.channels.cache
.filter((channel) => channel.type === 'text')
.first()
channel.createInvite().then( inv => { // returns promise
embed.addField(`Join Requirement Giveaway:`, `**[This Server](${inv})**`)
})
} else {
embed.addField(`Normal Giveaway:`,`**[${x.prize}](https://discord.com/channels/${x.guildID}/${x.channelID}/${x.messageID})**`)
}
})
Where the commented part returns a promise , I am using a forEach loop and I am aware that we are unable to overwrite values in an asynchronus one therefore my embed returns the portion of the loop without the promise, I am looking for a possible solution to this.
You can use Promise.all with map
Like this
Promise.all(giveaways.map((x) => new Promise((resolve, reject) => {
// ~~ doing something
somePromises.then((inv) => {
embed.addField(/*~~~*/)
resolve();
})
})))
.then(() => {
// next things..
});

Array.filter() with async arrow function

I am trying to filter my array using Array.filter() function, however I came across this issue.
I need to call some other function inside the filter function asynchronously. However the array is not changing its value based on the conditions that I define in the function.
const filterWithEmail = data.filter(async (doc) =>
{
const findUser = await UserService.findUser(doc.id).catch(err => {});
if (findUser)
{
const { email } = findUser;
return regexFilter ? regexFilter.test(email.normalize("NFKC")) : false;
}
});
This code doesn't affect the data array at all for some reason.
Any suggestions what I'm doing wrong?
Thank you in advance.
filter expects the return value of the callback to be a boolean but async functions always return a promise.
You don't know if you want to return true or false in time to tell filter which it is.
What you possibly want to do is:
map the data from data to { keep: true, original_data: data } (using an async callback)
Pass the resulting array of promises to Promise.all
await the return value of Promise.all
filter that array with: .filter(data => data.keep)
Get the original objects back with .map(data => data.original_data)
Something along these lines (untested):
const filterWithEmail = (
await Promise.all(
data.map(async (data) => {
const findUser = await UserService.findUser(doc.id).catch((err) => {});
let keep = false;
if (findUser && regexFilter)
keep = regexFilter.test(email.normalize("NFKC"));
return { data, keep };
})
)
)
.filter((data) => data.keep)
.map((data) => data.data);

Сollect promises results in foreach loop and only after performing setState

I am new to React and Promises.
My purpose is - Collect array of objects from several Search Services and then transfer whole array to the State.
PNP.SearchResults is a Promise
In code below this.setState is performing earlier then array is ready.
How to fix it?
private getSearchResults() {
const allSearchResults = []
this.state.resultSources.forEach(rSource =>{
this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, rSource.sourceGuid).then((results: PNP.SearchResults) => {
allSearchResults.push({Results: results, sourceGuid: rSource.sourceGuid});
console.log("push");
}).catch(() => {});
})
this.setState({PrimaryResults2: allSearchResults} as any);
console.log("state updated");
}
Now console.log("state updated") fires earlier then console.log("push").
But I need vice versa
Because of this.props.searchService.search is async function.
You should await result to make sure data return.
private async getSearchResults() {
const allSearchResults = []
for (const rSource of this.state.resultSources) {
await this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, rSource.sourceGuid).then((results: PNP.SearchResults) => {
allSearchResults.push({Results: results, sourceGuid: rSource.sourceGuid});
console.log("push");
}).catch(() => {});
}
this.setState({PrimaryResults2: allSearchResults} as any);
console.log("state updated");
}
If i understand correctly, you need to first push the search results into allSearchResults array and then setState. Why not use async await and a basic for loop instead of .then. When you use .then, only the code in the .then callback will execute after the promise is resolved but the other code outside it like this.setState, console.log won't wait till you push all the search results.
async func() {
const allSearchResults = [];
for(let i=0; i<this.state.resultSources.length; i+=1){
const item = await this.props.searchService.search(this.props.AdditionalQuery, this.state.activePage, this.props.PageSize, this.state.resultSources[i].sourceGuid)
allSearchResults.push({Results: item, sourceGuid: this.state.resultSources[i].sourceGuid})
}
this.setState({PrimaryResults2: allSearchResults} as any, () => {console.log("state updated")})
}

Categories