I have a custom Cypress command to scroll through a list until you reach the item passed into the command. In my command I have $.each so I can compare the name of the item to the item name passed into the function. If they match then I send a click command which is "ENTER" in my environment.
I am able to successful scroll through the list and find the the item I am looking for and click on it but the loop continues to execute. I added return false which is what Cypress says should break the loop but it is now working for me. Any ideas why that is the case?
listItems.getChildren('OneLineTextElement', 'type').each(($elm) => {
let labelAndValue = cy.wrap($elm).getChildren('LabelAndValue')
let label = labelAndValue.getChild('Label')
label.getProperty('texture-text').then(val => {
if (val == subject) {
cy.action('ENTER')
return false
}
else {
cy.action('DOWN')
}
})
})
You can try adding a control variable in the the scope of the .each() command.
listItems.getChildren('OneLineTextElement', 'type').each(($elm) => {
let done = false;
let labelAndValue = cy.wrap($elm).getChildren('LabelAndValue')
let label = labelAndValue.getChild('Label')
label.getProperty('texture-text').then(val => {
if (val == subject) {
cy.action('ENTER').then(() => { // is cy.action a custom command?
// Likely you may need to wait
done = true;
})
} else {
cy.action('DOWN')
}
})
if (done) {
return false;
}
})
However there are some method calls that look like custom commands inside .each() so you may not get the flow of execution you expect (Cypress commands and test javascript can run asynchronously).
It looks like the code may be refactored to avoid "scrolling through the list". The only thing this does not do is cy.action('DOWN') on the non-subject list items.
listItems.getChildren('OneLineTextElement', 'type')
.getChildren('LabelAndValue')
.getChild('Label')
.getProperty('texture-text')
.should(val => {
expect(val).to.equal(subject);
cy.action('ENTER');
})
You have to use promise after finding your match within the loop, and then apply the assertion. Below an example searching value '23' within the column 8 which corresponds to age field.
cy.get('.column-8')
.each(ele => {
if (ele.text() === '23') {
isValuePresent = true
if (isValuePresent) return false
}
}).then(() => {
expect(isValuePresent).to.be.true
})
Related
I need to verify if the sum of two elements is equal to the result shown on the UI.
Below is the html structure for the three elements and need to do the calculation on the h1 tags.
Below is the code I am trying to execute but nothing comes up in the test. The test runs and passes but nothing in the body.
it.only(' Verify Sum Of Active and Paused Jobs is Equal To Total Jobs Count ', () => {
function activejobnumber() {
return cy.get('[data-cy="jobSummaryActiveJobsStatTitle"]>h1')
.invoke('text').then(parseInt).as('actnum');
}
function pausedjobnumber() {
return cy.get('[data-cy="jobSummaryPausedJobsStatTitle"] > h1')
.invoke('text').then(parseInt).as('pasnum');
}
function add() {
const totaljobs = activejobnumber() + pausedjobnumber();
cy.log(totaljobs);
}
});
Also, if someone could point out why it isn't working, would really appreciate it.
Using functions to encapsulate each number is fine, but remember that commands inside are still asynchronous, and they are not returning numbers, they are returning Chainers.
If you did it without functions, this is what it would look like
cy.get('[data-cy="jobSummaryActiveJobsStatTitle"]>h1')
.invoke('text').then(parseInt).as('actnum')
.then(actnum => {
cy.get('[data-cy="jobSummaryPausedJobsStatTitle"] > h1')
.invoke('text').then(parseInt).as('pasnum')
then(pasnum => {
const totaljobs = actnum + pasnum
cy.log(totaljobs)
})
})
so with functions you need to repeat that pattern
function add() {
activejobnumber().then(actnum => {
pausedjobnumber().then(pasnum => {
const totaljobs = actnum + pasnum;
cy.log(totaljobs);
})
})
}
or take advantage of the alias for each number
function add() {
activejobnumber()
pausedjobnumber()
// wait for above async calls to finish
cy.then(function() {
const totaljobs = this.actnum + this.pasnum;
cy.log(totaljobs);
})
}
You can directly invoke text, convert them to numbers and add it like this:
cy.get('[data-cy="jobSummaryActiveJobsStatTitle"]>h1')
.invoke('text')
.then((actnum) => {
cy.get('[data-cy="jobSummaryPausedJobsStatTitle"] > h1')
.invoke('text')
.then((pasnum) => {
//Adding + converts string to number
const totaljobs = +actnum + +pasnum
cy.log(totaljobs)
})
})
I have code as below.
I need to break the loop when first match is found.
const [isCodeValid, setIsCodeValid] = useState(false);
for (let i = 0; i < properyIds.length; i++) {
if (isCodeValid) {
break; // this breaks it but had to click twice so state would update
}
if (!isCodeValid) {
firestore().collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
console.log("should break here")
// updating state like this wont take effect right away
// it shows true on second time click. so user need to click twice right now.
setIsCodeValid(true);
}
});
})
}
}
state won't update right away so if (!isCodeValid) only works on second click.
Once I find match I need to update state or variable so I can break the for loop.
I tried to use a variable but its value also not changing in final if condition, I wonder what is the reason? can anyone please explain ?
You should try and rewrite your code such that you will always call setIsCodeValid(value) once. In your case it could be called multiple times and it might not get called at all
const [isCodeValid, setIsCodeValid] = useState(false);
function checkForValidCode() {
// map to an array of promises for companies[]
const companiesPromises = properyIds.map(propertyId =>
firestore()
.collection(`properties`)
.doc(propertyId)
.collection('companies').get())
Promise.all(companiesPromises)
// flatten the 2d array to single array, re-create to JS array because of firestores internal types?
.then(companiesArray => [...companiesArray].flatMap(v => v))
// go through all companies to find a match
.then(companies =>
companies.find(
company => _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
))
.then(foundCompany => {
// code is valid if we found a matching company
setIsCodeValue(foundCompany !== undefined)
})
}
Try something like this:
import { useState } from 'react';
function YourComponent({ properyIds }) {
const [isCodeValid, setIsCodeValid] = useState(false);
async function handleSignupClick() {
if (isCodeValid) {
return;
}
for (let i = 0; i < properyIds.length; i++) {
const companies = await firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get();
for (const company of companies.docs) {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
setIsCodeValid(true);
return;
}
}
}
}
return (<button onClick={handleSignupClick}>Sign Up</button>);
}
If you await these checks, that will allow you to sequentially loop and break out with a simple return, something you can't do inside of a callback. Note that if this is doing database queries, you should probably show waiting feedback while this is taking place so the user knows that clicking did something.
Update:
You may want to do all these checks in parallel if feasible so the user doesn't have to wait. Depends on your situation. Here's how you'd do that.
async function handleSignupClick() {
if (isCodeValid) {
return;
}
const allCompanies = await Promise.all(
properyIds.map(id => firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get()
)
);
setIsCodeValid(
allCompanies.some(companiesSnapshot =>
companiesSnapshot.docs.some(company =>
_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
)
)
);
}
Can you not break it after setIsCodeValid(true);?
Use some:
companies.some(company => {
return _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase());
});
If some and forEach are not available then companies is not an array but an array-like object. To iterate through those, we can use for of loop:
for (const company of companies){
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// do something
break;
}
}
I tired below and it worked for me to break the loop.
I declared and tried to change this variable let codeValid and it was just not updating its value when match found. (not sure why)
But all of a sudden I tried and it just works.
I didnt change any actual code except for variable.
let codeValid = false;
let userInformation = []
for (let i = 0; i < properties.length; i++) {
console.log("called")
const companies = await firestore().collection(`properties`)
.doc(`${properties[i].id}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// a += 1;
codeValid = true;
userInformation.registrationCode = registrationCode.toUpperCase();
userInformation.companyName = company.data().companyName;
userInformation.propertyName = properties[i].propertyName;
}
});
})
if (codeValid) {
break;
}
}
I made a loop to check whether a key doesn't exsist in another object. As soon as this condition is true it should stop and redirect to a certain URL. I got the loop working but my issue is that as soon as the condition is met. It still continues to loop for the remaining items. Meaning it will never stop and creates some kind of infite loop. How can i make sure that if the condition (if) is met. the loop stops.
requiredResource:
resources: (first time it is empty)
Loop:
// For every requiredResource check if it exist in the resources. (right now it is one)
requiredResource.forEach((item: any) => {
// Check if there are resources, if not go get them
if(resources.length !== 0){
// Resources are filled with 2 examples
Object.keys(resources).forEach(value => {
//if the required is not in there, go get this resource and stop the loop
if(value !== item.name){
// Go get the specific resource that is missing
window.location.assign(`getspecificresource.com`);
} else {
// Key from resource is matching the required key. you can continue
//continue
}
});
} else {
// get resources
window.location.assign(`getalistwithresources.com`);
}
});
You can use some() array method for this like:
const found = requiredResource.some(({name}) => Object.keys(resources).indexOf(name) > -1)
if (!found) {
window.location.assign(`getspecificresource.com`);
} else {
// Key from resource is matching the required key. you can continue
//continue
}
EDIT:
Based on the discussion, you can updated your code like this to achieve the required behaviour as we can't break from a forEach loop:
requiredResource.some((item) => {
// Check if there are resources, if not go get them
if (resources.length !== 0) {
// Resources are filled with 2 examples
Object.keys(resources).some(value => {
//if the required is not in there, go get this resource and stop the loop
if (value !== item.name) {
// Go get the specific resource that is missing
window.location.assign(`getspecificresource.com`);
return true;
} else {
// Key from resource is matching the required key. you can continue
//continue
return false;
}
});
return false;
} else {
// get resources
window.location.assign(`getalistwithresources.com`);
return true;
}
});
Just using the some() with return true to break out of the loop here.
You could try something like this...
let obj = {
'a': 1,
'b': 2,
'c': 3
}
console.log(obj.a)
Object.keys(obj).some(x=>{
console.log(obj[x])
return obj[x]==2
})
And use the return statement to break the loop, does that help?
I have a label, which has onClick callback:
<label className="cursor-pointer" onClick={loadExisting}>
Click me
</label>
loadExisting function, fetch'es data from api, and passses it to parseData function.
const loadExisting = () => {
fetch("/api/v.1.0/events", { mode: "no-cors" })
.then(function(response) {
if (!response.ok) {
console.log("Something is wrong");
return;
}
return response.json();
})
.then(data => {
if (!data) {
return;
}
parseEvents(data);
});
};
In this function, I am trying to store only those events, which's titles are unique:
const parseEvents = data => {
if (data) {
for (let i = 0; i < data.length; i++) {
if (titlesArray.indexOf(data[i].title) < 0) {
setTitlesArray([...titlesArray, data[i].title]);
setEvents([...events, data[i]]);
}
}
}
};
Basically my idea is to set all the unique titles into titlesArray and if event's title is unique, I add it to both titlesArray and events.
Problem: This only works if I keep clicking on that label. With first click events.length is equal to 0, second click- equal to 1, third click- equal to 2, etc. Why it does not parse all events at once? So after 1 click I would have ~10 events that have unique titles.
setState is asynchronous and setTitlesArray and setEvents are likely to call setState.
So in your case, since you don't update titlesArray and events, the most likely is that you will only observe the last elem added
(data[data.length-1])
The fix is to simply call setState once.
data.forEach(d => {
if (titlesArray.includes(d.title) {
titlesArray.push(d.title)
events.push(d)
}
})
//I slice 0 to copy, but maybe it is not necessary if your function does it already
setTitlesArray(titlesArray.slice(0))
setEvents(events.slice(0))
nb: looking for existence in an array (titlesArray) is less efficient than using a Set. You should consider if you have a lot of titles
I am using Ionic3 with a rxjs/Observable. I have the following function, and for some reason, even though the function is only called once, the 3rd line gets fired twice.
findChats(): Observable<any[]> {
return Observable.create((observer) => {
this.chatSubscription2 = this.firebaseDataService.findChats().subscribe(firebaseItems => {
this.localDataService.findChats().then((localItems: any[]) => {
let mergedItems: any[] = [];
if (localItems && localItems != null && firebaseItems && firebaseItems != null) {
for (let i: number = 0; i < localItems.length; i++) {
if (localItems[i] === null) {
localItems.splice(i, 1);
}
}
mergedItems = this.arrayUnique(firebaseItems.concat(localItems), true);
} else if (firebaseItems && firebaseItems != null) {
mergedItems = firebaseItems;
} else if (localItems && localItems != null) {
mergedItems = localItems;
}
mergedItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
observer.next(mergedItems);
this.checkChats(firebaseItems, localItems);
});
});
});
}
Problem
This is causing a problem because this.chatSubscription2 is taking the value of the second subscription, and the first subscription gets lost, not allowing me to ever unsubscribe.
line 2 is executed once
line 3 is executed twice
Question
How do I create an Observable with only one subscription?
Thanks
UPDATE
I change the code to the following using share(), but the 3rd line still gets executed twice:
findChats(): Observable<any[]> {
return Observable.create((observer) => {
const obs = this.firebaseDataService.findChats().share();
this.chatSubscription2 = obs.subscribe(firebaseItems => {
....
As other users have suggested, while findChats is only called once, it would seem that the observable it returns is subscribed to multiple times. create returns a cold observable which will cause all the internal logic to be executed for each subscription. You can whack a share on the end of the whole thing (i.e. outside / after the create call) to test this, but I would suggest the solution would actually be simpler if you did not use create at all, and rather just mapped / flatMapped / switchMapped your original stream into your desired stream (to avoid manual subscription management).