How to break for loop inside Cypress's then() function? - javascript

I want to break for loop if .contacts element's style doen't equal display: none, but I can't find a way to do this inside then() fuction.
Use case: I want to click through table elements until contacts panel appears and then fill contacts. But if I don't stop the loop after panel appears it will disappear and I will get an error.
cy.get('#count').then($count => {
for (let i = 1; i <= $count - 1; i++) {
if (i == 1) {
cy.get(`:nth-child(${i}) > .room-type`).eq(1).click()
}
else {
cy.get(`:nth-child(${i}) > .room-type`).click()
}
cy.get('.spinner').should('not.exist')
cy.get('.contacts').then($contacts => {
if ($contacts.attr('style') != 'display: none;') {
//I want to break the loop here if condition is met
}
})
}
})

I believe the best way to go about about is the recursive approach. Using recursive approach your problem of synchronous and Asynchronous code will be resolved automatically.
So you can approach this problem with something like this below. The below code might not work as it is, you may have to do some changes accordingly. The main Idea is try going recursive approach if you want to avoid the sync-async code problem
cy.get("#count").then(($count) => {
test_recusive($count);
});
export const test_recusive = (count) => {
if (i == 1) {
cy.get(`:nth-child(${i}) > .room-type`).eq(1).click();
} else {
cy.get(`:nth-child(${i}) > .room-type`).click();
}
cy.get(".spinner").should("not.exist");
cy.get(".contacts").then(($contacts) => {
if ($contacts.attr("style") != "display: none;") {
//I want to break the loop here if condition is met
return;
} else {
return test_recusive(count - 1);
}
});
Now all the calls in your code will be synchronous ones.

Related

Break For Each Loop in a Custom Cypress Command

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
})

Check if object key exsist in object - javascript

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?

Is there a way to iterate and compare dom elements in nodejs?

I'm trying to get two values from a site I'm automating. I iterate through an array of elements and try to compare the value of the dropdown to what is on the page to make sure they equal one another. One of the values can only be accessed by .getValue(). The other is accessed by .getText(). I would like to save the result.value of these callback functions and compare the results.
I've tried to console.log both of these values and I get them back, but I can't return anything from this callback function. I also can't seem to save it's value in a variable and return that either. I've tried looking to do it in plain javascript with document.getElementById() but that works for client-side javascript, not serverside like nodejs. Just trying to compare two values together
for (let i = 1; i <= 20; i++) {
browser
.element('css selector', `mat-nav-list > a:nth-child(${i})`,
function(result) {
if (result.value && result.value.ELEMENT) {
browser.isVisible(`mat-nav-list > a:nth-child(${i})`,
function(result) {
if (result.value === true) {
browser.click(`mat-nav-list > a:nth-child(${i})`)
let chunkView = '#mat-input-0';
let sideBar = `body > gps-app-root > div > div.sidebar-desktop > gps-app-sidebar-menu > div > div.product-list-wrap > mat-nav-list > a:nth-child(${i}) > div`
browser.getValue(chunkView, function(result) {
chunkView = result.value
console.log(chunkView)
})
browser.getText(sideBar, function(result) {
console.log(result.value);
})
}
})
}
})
//.pause(2000)
//.pause(10000)
}
When I loop through I would expect to get the two values sideBar result.value to equal chunkView result.value. Current output can only log the two separate values.
I haven't used Nightwatch.js before, so I'm basing my answer on the assumption that browser.click, browser.getValue, and browser.getText run asynchronously, as that's fairly common with UI and UI test frameworks, and, if they did run synchronously, there would be no point in using callbacks.
You're probably going to want to get used to working with the JavaScript Promise. Since JavaScript engines are single-threaded, there is no way to spinlock/sleep while another thread handles some change (such as updating the UI after a click event). A Promise allows you to get around this by working with callbacks and handling events behind the scenes.
You can then chain promises using promise.then() which pass the returned value to the next callback.
In your case, though, I would wrap the two functions that retrieve values in promises and then use Promise.all(). This allows them to complete in any order which could improve performance.
browser.isVisible(`mat-nav-list > a:nth-child(${i})`,
function(result) {
if (result.value === true) {
browser.click(`mat-nav-list > a:nth-child(${i})`);
let chunkView = '#mat-input-0';
let sideBar = `body > gps-app-root > div > div.sidebar-desktop > gps-app-sidebar-menu > div > div.product-list-wrap > mat-nav-list > a:nth-child(${i}) > div`;
let valPromise = new Promise(resolve => {
browser.getValue(chunkView, resolve);
});
let textPromise = new Promise(resolve => {
browser.getText(sideBar, resolve);
});
Promise.all([valPromise, textPromise]).then(([valueResult, textResult]) => {
browser.assert.strictEqual(valueResult.value, textResult.value,
`Server-side value '${value.result}' does not match client-side value '${text.result}'`);
});
}
});
Use the perform method so that callbacks are ensured to have completed before the next command. (see https://github.com/nightwatchjs/nightwatch/wiki/Understanding-the-Command-Queue#the-perform-command)
Something like this
browser.getValue(chunkView, function(result) {
chunkView = result.value
console.log(chunkView)
}).perform(function() {
// here you have access to chunkView so you can compare it
browser.getText(sideBar, function(result) {
console.log(result.value);
if (chunkView === result.value) {
console.log('They are the same!');
}
})
});
or you could chain perform commands so that you can do the comparison at the end regarding the number of intermediate steps.
let chunkView = '#mat-input-0',
chunkViewResult;
let sideBar = `body > gps-app-root > div > div.sidebar-desktop > gps-app-sidebar-menu > div > div.product-list-wrap > mat-nav-list > a:nth-child(${i}) > div`,
sideBarResult;
browser.getValue(chunkView, function(result) {
chunkViewResult = result.value
console.log(chunkView)
}).getText(sideBar, function(result) {
sideBarResult = result.value
console.log(sideBarResult);
}).perform(function() {
if (chunkViewResult === sideBarResult) {
console.log('They are the same!')
}
})
I found if I nested .getValue() and .getText and assigned variables I was able to compare the two.
You can always use .execute() from nightwatch and run any javascript you want in the page.
client.execute(
function (data) {
// this is runned in browser
},
[what_you_pass_to_the_function_above_as_data], // multiple parameters = multiple elements in array
function (result) {
// result.status == -1 if error happend
// here you cand compare what you want... result.value is the returned things from the browser executed function.
}
);

IMacro JavaScript; Loop on IF else statement

for(var i=0;i<1;i++)
{
iimPlay(macro_var)
var extract=iimGetLastExtract();
if(extract.toLowerCase()=="incorrect security code entered!")
{
iimDisplay("wrong")
}
else
{
iimDisplay("right")
}
}
That's my imacro code (Javascript File)
If i get wrong how i do get it to try again until it reaches right?
Supposing you don't want to wait between executions, there are a couple of ways you could do this. The quickest one would be:
while(true) {
iimPlay(macro_var);
var extract = iimGetLastExtract();
if (extract.toLowerCase() == "incorrect security code entered!") {
iimDisplay("wrong");
continue;
} else {
iimDisplay("right");
break;
}
}
In this way, when the extracted value is wrong you restart the loop. If instead it's right, you break out of the loop.

How To Handle 'No Results Found' in a Search Feature

I am trying to display "No Results Found" in the case where someone uses my search feature to search for something that returns no results from the database. The problem I'm running into is that "No Results Found" prints to the screen right away - then disappear when a search query is actually being evaluated - and then reappears if no results were found. In other words, it's working as it should EXCEPT that it should wait till a query is actually triggered and evaluated before printing "No Results Found" to the screen. Conceptually what's the best way to do this? On the one hand it occurs to me that I could just use a timeout. But that's not really solving the problem directly. So what would be the best way to approach this? This is the code I have for the function:
public get noResultsFound(): boolean
{
if (this.query && !this._isSearching && !this.hasResults) {
return true;
}
}
Here's my view code:
<div *ngIf="inputHasFocus && noResultsFound" class="no-results-found">No Results Found</div>
Promises sound like what you need :D
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Something along the lines of this.
search = (paramObj) : Promise<SomeDataClass> => {
// Some search logic
}
requestSearch() {
this.search(null).then((result) => {
if (result.length === 0)
{
//logic for showing 0 results here
}
else{
// Show result?
}
})
}
A more complete example would look a bit like this.
class example {
public someBool = false;
public search = (paramObj): Promise<Array<string>> => {
this.someBool = !this.someBool;
return new Promise<Array<string>>((resolver) => {
// This just simulates something taking time.
setTimeout(() => {
if (this.someBool) {
resolver([]);
} else {
resolver(["hi","there"]);
}
}, 1000)
});
}
public requestSearch() {
this.search(null).then((result) => {
if (result.length === 0) {
console.log("empty")
}
else {
console.log(result)
}
})
}
}
So, in the end I was able to resolve this not by using some kind of debounce timeout, but by changing what the function was paying attention to - so to speak. By handling it this way the "No Results Found" never triggers before it's supposed to. This is what I ended up using:
public get noResultsFound(): boolean
{
if (!Object.isNullOrUndefined(this._results) && this._results.length < 1) {
return true;
}
}

Categories

Resources