Protractor nested for loops - javascript

I need some help with a nested for loop in protractor and converting/understanding promises correctly. In ‘test’ below the functionality works with all values, but as soon as I try to put in the nested for loops things go south. Any chance someone has a clean suggestion on this? I have tried the forEach which some indicate handle the promise issue inherently, but I seem to get the same results.
My Test data looks like:
objectPage.chartValues = {
[['chart','ChartID01'],['title','TitleText01'],['Name01','value01'],['Name02','Value02']],
[[‘chart','ChartID02'],['title','TitleText02'],['Name01','value01'],['Name02','Value02']],
[[‘chart','ChartID03'],['title','TitleText03'],['Name01','value01'], [‘Name02’,'Value02'],['Name03','Value03']]
}
it ('test', function (){
for (chartNumber = 0; chartNumber < objectPage.chartValues.length; chartNumber++) {
for (chartEntry = 1; chartEntry < ObjectPage.chartValues[chartNumber].length; chartEntry++) {
//for readability of next call pulled out here
chart = objectPage.chartValues[chartNumber][0][1];
name = objectPage.chartValues[chartNumber][chartEntry][0];
value = objectPage.chartValues[chartNumber][chartEntry][1];
pageObject.getbackgroundcolor(chart, name).then(function (color) {
expect(pageObject.getElementFromTable(chart, name, color).getText())
.toEqual(value);
});
}
}
});
//function calls in pageobject the call for get background is straight forward.
this.getbackgroundcolor = function (chartName, valueName) {
return element(by.id(chartName)).element(by.cssContainingText('.dxc-item', valueName)).element(by.tagName('rect')).getAttribute('fill');
//get element is very similar.
this.getElementFromTable = function(chartName, valueName, colorname) {
some searching stuff..
return element(by.css(‘tspan'));
My results seem to indicate the look executes, but not returning from the actual expect. Finally trying to find a value for an item with background color of null. I know this is not true as I have run all values individually and in sequence without issue. Hopefully I avoided cut and past/generalization errors.
Thank you.
Update:
it('Verify Charts on page ', function () {
myChartlength = objectPage.chartValues.length;
for (chartNumber = 0; chartNumber < myChartlength; chartNumber++) {
(function (chartNumber) {
myEntrylength = objectPage.chartValues[chartNumber].length;
chartValues = objectPage.chartValues[chartNumber];
for (chartEntry = 2; chartEntry < myEntrylength; chartEntry++) {
(function (chartEntry) {
//pulled these out for readablility of next call.
chart = chartValues[0][1];
name = chartValues[chartEntry][0];
value = chartValues[chartEntry][1];
console.log('chart: ' + chart + ', name: ' + name + ', value: ' + value);
page.getbackgroundcolor(chart, name).then(function (color) {
expect(objectPage.getElementFromTable(chart, name, color).getText()).toEqual(value);
});
})(chartEntry);
};
})(chartNumber);
};
});

Yeah, if I'm understanding your question correctly, your problem is async. It's firing through the loops before any promises are returned.
To loop tests, the best solution I've found is to use an IIFE (Instantly Invoked Function Expression). In which, you create your loop, create the iife, and pass in the index.
Here's a basic example:
describe('to loop tests', function() {
var data = ['1', '2', '3'];
for(var i = 0; i < data.length; i++) {
// create your iife
(function(i) {
it('pass in the index to an iife', function() {
console.log('i is: ' + i);
expect(data[i]).toBe(true);
});
})(i); // pass in index
}
});
This works great for data driving tests from say a data file, or whatever. And if you need multiple loops, like in your example code, you'll just make multiple iifes.

You shouldn't use for loops with protractor or you will have a bad time.
Due to the asynchronous nature of Protractor, if you need loops, I see async's map https://github.com/caolan/async as one good and clean solution.
Other option is to use ES5's map when you need loops in Protractor, such as:
[1,3,5,7].map(function(index,key){
expect(element.all(by.css('.pages-list')).get(index).isDisplayed()).toBeFalsy()
})
In your case, I see that you need a for loops to produce array, that you later can map over it.
You can have this array with function, that uses for loops inside and returns the needed array to a callback.
Simple example with one for loop
function returnIndexes(callback){
var exitArray = [];
for (var i = 0; i < someArray.length; i++) {
if(someArray[i].length > 12){
exitArray.push(someArray[i]);
}
if(i==someArray.length-1){
callback(exitArray);
}
}

Related

Cypress IO- Writing a For Loop

I have 15 buttons on a page. I need to test each button.
I tried a simple for loop, like
for (var i = 1; i < 15; i++) {
cy.get("[=buttonid=" + i + "]").click()
}
But Cypress didn't like this. How would I write for loops in Cypress?
To force an arbitrary loop, I create an array with the indices I want, and then call cy.wrap
var genArr = Array.from({length:15},(v,k)=>k+1)
cy.wrap(genArr).each((index) => {
cy.get("#button-" + index).click()
})
Lodash is bundled with Cypress and methods are used with Cypress._ prefix.
For this instance, you'll be using the _.times. So your code will look something like this:
Cypress._.times(15, (k) => {
cy.get("[=buttonid=" + k + "]").click()
})
You can achieve something similar to a "for loop" by using recursion.
I just posted a solution here: How to use a while loop in cypress? The control of is NOT entering the loop when running this spec file? The way I am polling the task is correct?
Add this to your custom commands:
Cypress.Commands.add('recursionLoop', {times: 'optional'}, function (fn, times) {
if (typeof times === 'undefined') {
times = 0;
}
cy.then(() => {
const result = fn(++times);
if (result !== false) {
cy.recursionLoop(fn, times);
}
});
});
Then you can use it by creating a function that returns false when you want to stop iterating.
cy.recursionLoop(times => {
cy.wait(1000);
console.log(`Iteration: ${times}`);
console.log('Here goes your code.');
return times < 5;
});
While cy.wrap().each() will work (one of the answers given for this question), I wanted to give an alternate way that worked for me. cy.wrap().each() will work, but regular while/for loops will not work with cypress because of the async nature of cypress. Cypress doesn't wait for everything to complete in the loop before starting the loop again. You can however do recursive functions instead and that waits for everything to complete before it hits the method/function again.
Here is a simple example to explain this. You could check to see if a button is visible, if it is visible you click it, then check again to see if it is still visible, and if it is visible you click it again, but if it isn't visible it won't click it. This will repeat, the button will continue to be clicked until the button is no longer visible. Basically the method/function is called over and over until the conditional is no longer met, which accomplishes the same thing as a for/while loop, but actually works with cypress.
clickVisibleButton = () => {
cy.get( 'body' ).then( $mainContainer => {
const isVisible = $mainContainer.find( '#idOfElement' ).is( ':visible' );
if ( isVisible ) {
cy.get( '#idOfElement' ).click();
this.clickVisibleButton();
}
} );
}
Then obviously call the this.clickVisibleButton() in your test. I'm using typescript and this method is setup in a class, but you could do this as a regular function as well.
// waits 2 seconds for each attempt
refreshQuote(attempts) {
let arry = []
for (let i = 0; i < attempts; i++) { arry.push(i) }
cy.wrap(arry).each(() => {
cy.get('.quote-wrapper').then(function($quoteBlock) {
if($quoteBlock.text().includes('Here is your quote')) {
}
else {
cy.get('#refreshQuoteButton').click()
cy.wait(2000)
}
})
})
}
Try template literals using backticks:
for(let i = 0; i < 3; i++){
cy.get(`ul li:nth-child(`${i}`)).click();
}

How to use then() within loops using closures

This question is very close to the question asked in Using protractor with loops but still have not resolved by me in case of tiny difference.
// This script should print button names and its current numbers
var buttons = element.all(by.css('button'));
buttons.count().then(function(cnt){
for(var i=0;i<cnt;i++) {
var func = function(i2){ var k=i2; return function(){console.log("#"+k+", name: "+button_name);}}(i);
buttons.get(i).getText().then(func);
}
});
The compiler said "ReferenceError: button_name is not defined" that is right.
How can I pass the button name inside then() function?
You are getting the function name as the argument to your then callback - but currently your func doesn't have a parameter. If you give it one, it'll work:
for (var i=0; i<cnt; i++) {
var func = function(k) {
return function(button_name) {
// ^^^^^^^^^^^
console.log("#"+k+", name: "+button_name);
};
}(i);
buttons.get(i).getText().then(func);
}
or maybe without returning from the IEFE, ther more common pattern might be:
for (var i=0; i<cnt; i++) (function(k) {
buttons.get(k).getText().then(function(button_name) {
// ^^^^^^^^^^^
console.log("#"+k+", name: "+button_name);
});
}(i));
Disclaimer: I'm not saying that this is the best way to use protactor, it's just how promises and closures work. I'd expect protractor to actually provide an iteration method - #finspin seems to have used it.
I'm not sure if I understand your intention correctly but if you want to print button name attribute and its index, this should do it:
$$('.button').forEach(function (button, index) {
button.getAttribute('name').then(function (btnName) {
console.log('#' + index + ', name: ' + btnName);
});
});

for loop in multiselect not called first time javascript/jquery

I have a multiselect dependency where I display cities and their areas respectively.
The problem is that, both for loops in the function getArea only get called upon the second select of the cities option element.
Note: In debugger it works fine. I think its a scope problem and I tried using the foreach function but to no avail.
I have indicated below the line where the issue occurs.
//Global Variables
var allAreas = new Array();
$(document).ready(function() {
getCities();
$("#sel-city").bind("change", getAreas);
});
//get Cities on reload
function getCities() {
$.getJSON("cities.json", function(json) {
var citySelect = $("#sel-city");
for (var i in json) {
$("#sel-city")
.append($('<option>', {value: json[i].id})
.text(json[i].name));
}
});
}
function getAreas() {
var parentID = $(this).val();
console.log(parentID);
if (allAreas.length == 0) {
$.getJSON("areas.json", function(json) {
for (var i in json) {
allAreas.push(json[i]);
}
});
}
$('option', $("#sel-area")).remove();
var areasByParentID = new Array();
//Loop here
for (var i in allAreas) {
if (allAreas[i].city_id == parentID) {
areasByParentID.push(allAreas[i]);
}
}
console.log(areasByParentID);
//Loop here
for (var k in areasByParentID) {
$("#sel-area")
.append($('<option>', {value: areasByParentID[k].id})
.text(areasByParentID[k].name));
}
}
With the information you gave us and without sample data everything looks fine. But you have some minor mistakes which could lead to bad results. I decided to have a look on it, make some corrections and get it workin correctly with some sample data.
What you should take care of?
use for loops instead of for-in for arrays
you should avoid the new Array() operation
you should cache jquery variables to reduce siteload
for the areas to be set correctly on siteload you should just define init functions
What here could cause an error?
var a = [];
a[5] = 5;
for (var x in a) {
// Shows only the explicitly set index of "5", and ignores 0-4
}
The use of the for-in statement is to enumerate over object properties and will even inherited properties. Depending on your data it could give wrong results as shown in my example.
here is a jsfiddle with sample data.

Javascript Closures and self-executing anonymous functions

I was asked the below question during an interview, and I still couldn't get my head around it, so I'd like to seek your advice.
Here's the question:
var countFunctions = [];
for(var i = 0; i < 3; i++){
countFunctions[i] = function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
}
//The below are executed in turns:
countFunctions[0]();
countFunctions[1]();
countFunctions[2]();
When asked what would be the output of the above, I said count0,count1 and count2 respectively. Apparently the answer was wrong, and that the output should all be count3, because of the concept of closures (which I wasn't aware of then). So I went through this article and realized that I should be using closure to make this work, like:
var countFunctions = [];
function setInner(i) {
return function(){
document.getElementById('someId').innerHTML = 'count' + i;
};
}
for(var i = 0; i < 3; i++){
countFunctions[i] = setInner(i);
}
//Now the output is what was intended:
countFunctions[0]();//count0
countFunctions[1]();//count1
countFunctions[2]();//count2
Now that's all well and good, but I remember the interviewer using something simpler, using a self-executing function like this:
var countFunctions = [];
for(var i = 0; i < 3; i++) {
countFunctions[i] = (function(){
document.getElementById('someId').innerHTML = 'count' + i;
})(i);
}
The way I understand the above code, we are skipping the declaration of a separate function and simply calling and executing the function within the for loop.
But when I ran the below:
countFunctions[0];
countFunctions[1];
countFunctions[2];
It didn't work, with all the output being stuck at count2.
So I tried to do the below instead:
for(var i = 0; i < 3; i++) {
countFunctions[i] = function(){
document.getElementById('someId').innerHTML = 'count' + i;
};
}
, and then running countFunctions[0](), countFunctions[1]() and countFunctions[2](), but it didn't work. The output is now being stuck at count3.
Now I really don't get it. I was simply using the same line of code as setInner(). So I don't see why this doesn't work. As a matter of fact, I could have just stick to the setInner kind of code structure, which does work, and is more comprehensive. But then I'd really like to know how the interviewer did it, so as to understand this topic a little better.
The relevant articles to read here are JavaScript closure inside loops – simple practical example and http://benalman.com/news/2010/11/immediately-invoked-function-expression/ (though you seem to have understood IEFEs quite well - as you say, they're "skipping the declaration of a separate function and simply calling and executing the function").
What you didn't notice is that setInner does, when called, return the closure function:
function setInner(i) {
return function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
}
// then do
var countFunction = setInner("N"); // get the function
countFunction(); // call it to assign the innerHTML
So if you translate it into an IEFE, you still need to create (and return) the function that will actually get assigned to countFunctions[i]:
var countFunctions = [];
for(var i = 0; i < 3; i++) {
countFunctions[i] = (function(i){
return function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
})(i);
}
Now, typeof countFunctions[0] will be "function", not "undefined" as in your code, and you can actually call them.
Take a look at these four functions:
var argument = 'G'; //global
function passArgument(argument){
alert(argument); //local
}
function noArguments(){
alert(argument); //global
}
function createClosure_1(argument){
return function (){
alert(argument); //local
};
}
function createClosure_2(argument){
var argument = argument; //local
return function (){
alert(argument); //local
};
}
passArgument('L'); //L
noArguments(); //G
createClosure_1('L') //L
createClosure_2('L') //L
alert(argument) //G
I think, first function is obvious.
In function noArguments you reference the global argument value;
The third and fourth functions do the same thing. They create a local argument variable that doesn't change inside them and return a function that references that local variable.
So, what was in the first and the last code snippet of your question is a creation of many functions like noArguments,
that reference global variable i.
In the second snippet your setInner works like createClosure_1. Within your loop you create three closures, three local variables inside them. And when you call functions inside countFunctions, they get the value of the local variable that was created inside the closure when they were created.
In the third one you assign the result of the execution of those functions to array elements, which is undefined because they don't return anything from that functions.

JavaScript variables giving the correct value when stepped through using the console, but not otherwise

I have a variable in a JavaScript constructor that appears to be set to the correct value when stepped through using breakpoints. However, when run without breakpoints, the variable (supposed to be an array that I give it), comes up as an empty array in the console. I don't know whether or not using the get/set property of prototype, as described here. Also-- I'm working in webkit, so if someone could help explain to me why it isn't working there, I'd appreciate it. Thanks!
function Box(inElement){
var self = this;
this.element = inElement;
this.boxes = (function () {
var boxes = [];
for (var i = 0; i < inElement.childNodes.length; ++i) {
if (3 !== inElement.childNodes[i].nodeType) {
boxes.push(inElement.childNodes[i]);
}
}
return boxes;
})();
this.rotation = [-40,-20,0,20,40];
}
Box.prototype =
{
get rotation(){
return this._rotation;
},
set rotation(rotArray){
console.log('rotArray');
console.log(rotArray);
var thisrot;
this._rotation = rotArray;
for(var i=0; i<this.boxes.length; i++){
thisrot = rotArray.shift();
this.boxes[i].style.webkitTransform = 'rotateY(' + thisrot + 'deg) translateZ(170px)';
}
}
}
function loaded()
{
new Box(document.getElementById('area'));
}
window.addEventListener('load',loaded, true);
So, after some fiddling, I discovered that boxes.push(inElement.childnodes[i] is the problematic line. When commented out, the value comes out as expected.
You are removing all elements from your array in the loop inside of set rotation using shift. Arrays are passed by reference in JavaScript, not by value. If you want to create a copy of your array, you will have to use Array.slice:
this._rotation = rotArray.slice();

Categories

Resources