How to wait for rows to load with Protractor - angularjs

I'm trying to use protractor to select a row, count rows, etc. This doesn't seem to be working:
var GridTestUtils = require('./gridObjectTestUtils.spec.js');
var GridTestObj = new GridTestUtils('exampleGrid');
var Grid = GridTestObj.getGrid();
browser.wait(function(){
return Grid.isPresent();
}, 1000).then(function(){
GridTestObj.expectRowCount( 25 );
});
It seems as though it's trying to find the rows before they're loaded. The test keeps failing with 'Expected 0 to equal 25'.
I can get it to work if I use browser.sleep but there has to be a better solution than that.
How do I tell protractor to wait for Angular ui-grid to load completely?

I would do that with browser.wait() and a custom Expected Condition:
var rows = Grid.element( by.css('.ui-grid-render-container-body')).all( by.repeater('(rowRenderIndex, row) in rowContainer.renderedRows track by $index') );
browser.wait(function () {
return rows.count().then(function (countValue) {
return countValue > 0;
});
}, 5000);
In this case, protractor would execute the function passed into browser.wait() continuously until it evaluates to true or the timeout happens (in 5 seconds).

Related

E2E tests with multiple pages with information from browser

I am writing an E2E test with protractor. I had to fetch information from the browser and execute a step multiple times.
I am testing one screen which will start when a
User clicks 'Start'
lands on a new page
The workflow below is invoked with count being passed as argument
id the html id does not change. the value changes when queried again after submitting the current form.
for(i = 0 ; i < count ; i++){
console.log("counter is "+i);
element(by('id')).evaluate('value').then(function(v) {
// do some action on UI based on v
element(by('id1')).sendKeys(v+v);
// submit etc.,
// some angular code runs in the frontend.
}
// need to wait since webdriver jumps to the next one without this completing
}
Many blog posts/documentations suggests you cannot use it in a loop, but does not suggest any alternative way to do this.
Any suggestions appreciated.
Never use protractor element statements inside loop: The simple reason is that the webdriverJS (protractor) API is asynchronous. Element statements returns a promise and that promise is in unresolved state while the code below the statements continues to execute. This leads to unpredictable results. Hence, it is advisable to use recursive functions instead of loops.
source: http://engineering.wingify.com/posts/angularapp-e2e-testing-with-protractor/
Edit: updated question with details of workflow.
It is usually not recommended to use a loop when an iteration has an asynchronous call.
The reason is that the first asynchronous calls is executed after the last iteration of the loop when i is already equal to count.
Thus, it makes it difficult to break the loop and to keep track of the value of i.
On way to tackle the issue is to use a recursive function :
var count = 3;
var results = [];
function iterate(i, n) {
if(i < n) {
console.log(`counter is ${i}`);
browser.refresh();
return element(by.id('h-top-questions')).getText().then(function(text) {
results.push(`${i}:${text}`);
return iterate(i + 1, n);
});
}
}
iterate(0, count).then(function(){
console.log("done!", results);
});
But a better way would be to iterate with promise.map on an array sized to the number of iterations:
var count = 3;
protractor.promise.map(Array(count).fill(0), function(v, i) {
console.log(`counter is ${i}`);
browser.refresh();
return element(by.id('h-top-questions')).getText().then(function(text) {
return `${i}:${text}`;
});
}).then(function(results){
console.log("done!", results);
});
You could also keep using a loop. First you'll have to use the let statement to get the value of i in an asynchronous function (ES6).
Then call all the synchronous code with browser.call to synchronize the execution:
var count = 3;
var results = [];
for(let i = 0 ; i < count ; i++){
browser.call(function(){
console.log(`counter is ${i}`);
browser.refresh();
element(by.id('h-top-questions')).getText().then(function(text) {
results.push(`${i}:${text}`);
});
});
}
browser.call(function() {
console.log("done!", results);
});
Looping in protractor works like this
describe('Describe something', function() {
var testParams = [1,2,3,4,5,6,7,8,9,10];
beforeEach( function() {
// ...
});
for (var i = 0; i < testParams.length; i++) {
(function (testSpec) {
it('should do something', function() {
// inside loop
});
})(testParams[i]);
};
});
Edit : I might be mis-understanding your question, but it seems to me you want to complete all(dynamic count) actions on the page, before going to the next one ?
it('should clear old inspections', function() {
inspectieModuleInspectieFixture.getRemoveInspectionButton().count().then(function (value) {
if(value == 0){
console.log('--- no inspections to remove ---');
}
for(var i = 0; i < value; i++){
//global.waitForClickable(inspectieModuleInspectieFixture.getRemoveInspectionButtonList(i+1));
inspectieModuleInspectieFixture.getRemoveInspectionButtonList(i+1).click();
console.log('iteration '+i + 'count '+value )
};
});
global.wait(5000);
}); */
this counts elements on the page and then it performs an action for the ammount of elements it found
In the above example I use containers to hold my elements, so my code remains readable (i.e. inspectieModuleInspectieFixture.getRemoveInspectionButton() holds $(".elementSelectorExample")
There is also a 'global.waitForClickable' commented, that is reffering to a 'time module' I've created that extends the functionality of 'wait', in this case it waits till the element is vissible/clickable.
This is easily mirrored perhaps something like this :
waitForElementNoDisplay: function(element){
return browser.wait(function() {
return element.isDisplayed().then(function(present) {
return !present;
})
});
},
this will make protractor WAIT untill an element is no longer displayed.(Display:none)
If you need to perform some action on every element, it is true, that better to not use loops. Use .map() or .each() or .filter() instead
Still not quite sure what you what to do, but here is example how i am doing similar tasks, when you need to make number of actions depending on data from the page:
class SomePage {
typeValueForEachElement(elements) {
elements.each((elem, index)=> {
elem.getAttribute('value').then(value=> {
elem.sendKeys(value + value)
elem.submit()
})
})
}
}
new SomePage().typeValueForEachElement($$('your locator here'))
Here is api reference that might help
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.map
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.reduce
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.each
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.filter

Protractor - testing an Angular form with dynamically populated inputs

I am trying to write an e2e test for my angular application, and in particular a form that has 3 select inputs on. The test will need to involve picking random options from these selects. The first select is already populated with data, but the other 2 selects asynchronously populate when the one before has been selected, so they are dependant on each other.
The select inputs also use ng-disabled and only enable when there are options available as per their ng-repeat expressions.
I am using the page object approach with my tests, and so am trying to make some utility functions to achieve the random selection behaviour I need in my tests:
Page Object:
this.selectRandomCustomer = function() {
var option,
randomIndex;
this.customerSelect.click();
this.customerSelectOptions.count().then(function(count) {
randomIndex = Math.floor(Math.random() * count);
option = element(by.repeater('customer in vm.customers').row(randomIndex));
option.click();
});
};
this.selectRandomOrder = function() {
if(this.orderSelect.isEnabled()===true) {
var option,
randomIndex;
this.orderSelect.click();
this.orderSelectOptions.count().then(function(count) {
randomIndex = Math.floor(Math.random() * count);
option = element(by.repeater('order in vm.orders').row(randomIndex));
option.click();
});
}
};
Given that the orderSelect can only be selected once populated with options after choosing an option from the customerSelect, I wondered about hooking into the promise returned when calling this.customerSelectOptions.count(), so calling this.selectRandomOrder, but it seems this is undefined as I get an error from protractor saying it cannot find the selectRandomOrder function.
Right now I can only get the first select to pick an option since it's always populated on the initial page load.
Also, I'm unsure whether using isEnabled() is actually working for me, as I am certain this should be returning true for my second input, but if I console log this out, I see false. Does this not work with ng-disabled?
What are the best practices for dealing with inputs on a form that wont initially be populated with data and waiting for angular to fetch and populate any available options?
Thanks
UPDATE:
I have got this to working using a call to getAttribute() checking for the presence of the disabled property instead.
So from my spec file in an it block I can call
page.customerSelect.getAttribute('disabled').then(function(result){
if(!result) {
page.selectRandomCustomer();
}
});
page.orderSelect.getAttribute('disabled').then(function(result){
if(!result) {
page.selectRandomOrder();
}
});
Ideally what I'd like to be able to do is to call the selectRandomOrder after clicking the option in the selectRandomCustomer:
this.selectRandomCustomer = function() {
var option,
randomIndex;
this.customerSelect.click();
this.customerSelectOptions.count().then(function(count) {
randomIndex = Math.floor(Math.random() * count);
option = element(by.repeater('customer in vm.customer').row(randomIndex));
option.click();
//Like to be able to call selectRandomOrder but only after angular has finished performing AJAX request for data and received response
});
};
this.selectRandomOrder = function() {
var option,
randomIndex;
this.orderSelect.click();
this.orderSelectOptions.count().then(function(count) {
randomIndex = Math.floor(Math.random() * count);
option = element(by.repeater('order in vm.orders').row(randomIndex));
option.click();
});
};
I did try calling this.selectRandomOrder immediately after the option.click() but I get an error saying no such function, it seems this is not accessible from inside the returned promise function callback.
There is at least one major problem in the posted code:
if(this.orderSelect.isEnabled()===true) {
Here isEnabled() returns a promise. You have to resolve it to check it's value:
var self = this; // saving the page object reference
this.orderSelect.isEnabled().then(function (isEnabled) {
if (isEnabled) {
var option,
randomIndex;
self.orderSelect.click();
self.orderSelectOptions.count().then(function(count) {
randomIndex = Math.floor(Math.random() * count);
option = element(by.repeater('order in vm.orders').row(randomIndex));
option.click();
});
}
});

Protractor: Iterating over options works if debugging but doesn't if not

I have the following test (for the sake of brievity I've removed the page object):
element(by.model("elc.search.placeOfBirth")) //this is a select
element(by.model("elc.search.placeOfBirth")).all(by.tagName("option")).then(function(options) {
for(var i = 0; i < options.length; i++) {
options[i].getText().then(function(text) {
if(text !== "---") {
element(by.model("elc.search.placeOfBirth")).sendKeys(text);
var firstRow = element.all(by.repeater("employee in elc.filtered")).first();
firstRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
var lastRow = element.all(by.repeater("employee in elc.filtered")).last();
lastRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
}
});
}
});
Let me explain what's happening here. I have a table and a select box above it. The table's 5th column is related to the select combobox and the array I use in ng-repeat for the table is filtered by the value in the combobox. What I wanted to do here is to go over the values in the combobox, select a particular value and make sure the table has that value in the first and last row.
If I but browser.debugger() in the loop this works and the test passes, however if I don't debug the testing seems to go too fast and my table doesn't get updated quickly enough and the tests fail. I'm guessing this is due to the fact that a promise isn't resolved and the code keeps running, but I'm not sure which promise I'm waiting for, as I've also tried to put a .then(function() {...} right after I send the keys to the combobox.
I guess your for loop executes quickly and so the code inside it waiting for promises gets skipped. You can avoid it by executing a function inside the for loop. Update your code to do it -
var someFunction = function(options, i){
//Write your code that was inside your for loop
};
element(by.model("elc.search.placeOfBirth")).all(by.tagName("option")).then(function(options) {
for(var i = 0; i < options.length; i++) {
someFunction(options, i);
}
});
However there's a better solution for this problem. Use the inbuilt loops that protractor has .each() or .map() to get your job done. Here's how -
element(by.model("elc.search.placeOfBirth")).all(by.tagName("option")).each(function(option) {
option.getText().then(function(text) {
if(text !== "---") {
element(by.model("elc.search.placeOfBirth")).sendKeys(text);
var firstRow = element.all(by.repeater("employee in elc.filtered")).first();
firstRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
var lastRow = element.all(by.repeater("employee in elc.filtered")).last();
lastRow.all(by.tagName("td")).then(function(cells) {
expect(cells[4].getText()).toBe(text);
});
}
});
});
You may use wait's in between to make sure your DOM is updated before you perform any operation. Hope it solves your issue.

Not able to click the Matched Row button in the repeater in protractor Test Case?

This is my test code when I run my test it always click the last row button, not able to click the matched row button.
it('repeater element check',function(){
browser.get('http://test.worker.mondaz.com/#/Company/Select');
browser.sleep(1000);
var result = element.all(by.repeater('co in CoList'));
result.then(function(arr) {
for (var i = 0; i < arr.length; ++i) {
arr[i].element(by.binding('co.Nm')).getText().then(function(text) {
if(text=="Monday Ventures Private Limited") {
console.log(text);
console.log("Mathced");
console.log(i);//this is always giving my total row count
element(by.repeater('coCoList').row(i)).element(by.name('customRadio')).click();
}
});
}
});
}
I am a beginner for Angular Protractor test case.
Instead of using a for-loop over your array of promises returned by element.all, you should use element.all(locator).filter(filterFn) to filter out the "Monday" element.
If I understand your present code correctly, it will first loop through the array of promises without waiting for their completion. Because of this i will be equal to the total number of rows. Only then the actually matched row's function (text) { ... } is executed, but with the value of i you did not expect.
EDIT: Including the working code based on this answer, taken from #chandru-yadhav's comment:
var items = element.all(by.repeater('co in CoList')).filter(function(item) {
return item.element(by.binding('co.Nm')).getText().then(function(label) {
return label === 'Monday Ventures Private Limited';
});
});
items.get(0).element(by.name('customRadio')).click();

tell Protractor to wait for the page before executing expect

When I click the export button, it makes a REST call to our endpoint then few seconds after, I receive the response then I also render the table. Unfortunately, I read that every call is asynchronous which means my expect will be executed even if table hasn't been rendered yet. The expect I wrote checks if the string is on the table but it's failing since it's not there yet. What is the proper approach to this?
it('should generate global user report', function() {
element(by.css('button#exportButton')).click();
expect(element(by.css("th[name*=Date]")).getText()).
toEqual('Date');
})
The error on the console is
NoSuchElementError: No element found using locator: By.cssSelector("th[name*=Date]")
I noticed that the table hasn't been rendered yet that's why it's failing.
Protractor 1.7 introduced a feature called "Expected Conditions", that can be applied here.
Wait for element to become visible:
var EC = protractor.ExpectedConditions;
var elm = element(by.css("th[name*=Date]"));
browser.wait(EC.visibilityOf(elm), 5000);
expect(elm.getText()).toEqual('Date');
I had problem waiting for a dynamic element to appear. Have the driver wait for it to either be present or displayed. The number at the end is the timeout.
element(by.css('button#exportButton')).click();
var header = element(by.css("th[name*=Date]"));
browser.driver.wait(function() {
return header.isPresent();
}, 1000);
expect(header.getText()).toEqual('Date');
I had to wait until it was present AND displayed before the test was fully stable. You can do that like this:
var header = element(by.css("th[name*=Date]"));
browser.driver.wait(function() {
return header.isPresent().then(function(present) {
if (present) {
return header.isDisplayed().then(function(visible) {
return visible;
});
} else {
return false;
}
});
}, 1000);

Resources