Working with Array of Elements in Protractor
What is ElementArrayFinder?
ElementArrayFinder is an array of WebElements which is used to set up a chain of conditions that identify an array of elements. This allows you to perform actions (i.e. click, getText) on them as you would on an array of WebElements. The action will apply to every element identified by the ElementArrayFinder.
What is the issue working with ElementArrayFinder in Protractor?
While working with array of WebElements (ElementArrayFinder) in Protractor, I came across few exceptions while scripting e-to-e tests. One of them in “stale element reference exception”. This exception occurs because Protractor has lost the reference of the element that it is looking for or the element has been deleted entirely.
Being a Selenium (Java) guy I used similar techniques of handling lists of webelements what I normally do in Selenium Webdriver but sooner realize that I was on wrong track because I am working with JavaScript which is asynchronous in nature.
So let me explain you the case where I am wrong. As we all know that Java is synchronous so using for loop on arraylist of webelement is quit easy. Every instruction in program executed only when its previous one got finished. So I was trying same with Protractor but getting “stale element reference exception”. After debugging I found out that all this is happening because of Promises.
Lets take a look at one example taken from: here
All Promises will be executed only when their parent functions are finished with their execution. So, What happen is all Promises are scheduled in a queue and wait for their parent to finish which in turn does not interrupt the normal flow programs.
When you run something like this:
[java]for (var j = 0; j < 3; ++j) {
console.log(‘1) j is: ‘, j);
Prom().then(function() {
console.log(‘2) j is: ‘, j);
Array[j] // ‘j’ always takes the value of 3
})
}
console.log(‘finished looping. j is: ‘, j);[/java]
What happens is that the promise is resolved only after Prom().then(function() {…}) returns without executing then function. Loop keeps on running 3 times without entering then function because promise is not resloved yet and at last all promises are resolved.
The result on console would look something like this:
[java]1) j is: 0 // schedules first `Prom()`
1) j is: 1 // schedules second `Prom()`
1) j is: 2 // schedules third `Prom()`
finished looping. j is: 3
2) j is: 3
2) j is: 3
2) j is: 3 [/java]
What’s the solution?
So, the question is how do you loop across array of webelements?
Well there are few ways which you can use to solve this problem:
- By functions provided by Protractor such as maps, filters, each.
- By using closure
For now lets concentrate on solution as mention in point 1 i.e map function provided by Protractor:
[java]var list1= element.all(by.css(‘div#topmenu>ul>li:nth-of-type(2)>div>div>div’));
list1.map(function(links){
return links;
}).then(function(links){
for (var i = 0; i < links.length; i++) {
links[i].$(‘h3>a’).getText().then(function(text){
console.log(‘text of elem1 is: ‘+text);
if(text === ‘pink’){
links[i].$(‘h3>a’).click();
}
})
}
})[/java]
What happens in above example is, map iterate through each element within the ElementArrayFinder and store them in links array which inturn can be used with for loop to perform various actions on each element like in above code, getting text of each element and then based on comparison, performing clicking operation.
Hope the above solution works. Please let me know if you have any queries / additions in the comment box below.
How to write promise if we have to count the no of rows
Below is the Snippet of Code in which i’m facing the Classical Closure Loop problem , need help in fixing this issue
element.all(by.xpath(“//*/portal-current/div/div/div[3]/div[3]/div/div/div[@ng-repeat=’head in $ctrl.configuration.columns’]”)).count().then(function(rows){
// Here we are storing the 2 record sets in a variable
noofitems = rows;
console.log(noofitems);
browser.sleep(5000);
for(var i=2;i<=noofitems;i++)
{
//browser.sleep(5000);
console.log(i);
browser.sleep(5000);
element(by.xpath("//*/div[3]/div[3]/div/div/div["+ i +"]/span")).getText().then(function(text){
browser.sleep(5000);
products.push(text);
console.log(products);
});
console.log(products.length);
}
How we use the promise
when we have to count the no of rows below is the snippet of the Code
element.all(by.xpath(“//*/portal-current/div/div/div[3]/div[3]/div/div/div[@ng-repeat=’head in $ctrl.configuration.columns’]”)).count().then(function(rows){
// Here we are storing the 2 record sets in a variable
noofitems = rows;
// console.log(noofitems);
browser.sleep(5000);
for(var i=2;i<=noofitems;i++)
{
//browser.sleep(5000);
console.log(i);
browser.sleep(5000);
element(by.xpath("//*/div[3]/div[3]/div/div/div["+ i +"]/span")).getText().then(function(text){
products.push(text);
console.log(products);
console.log(i);
});
// console.log(products.length);
}
what is I have to run this code inside the it after the browser.get which is already in a for loop???
Some indents in the code example would be REALLY helpful.