Protractor: 'wait' doesn't work with "element.all" - angularjs

I write Protractor automation tests and faced an issue. Wait command doesn't actually wait for one of the array elements. See the example below: I try to wait for the first element after navigating to webpage.
var category = element.all(by.repeater('category in listCtrl.categories'));
var category2 = $$('.category-name.custom-tooltip-link.ng-binding');
var EC = protractor.ExpectedConditions;
describe('wait for the first category', function() {
it('wait', function() {
browser.get('http://www.deep.mg/');
browser.wait(EC.visibilityOf(category.get(0)), 20000);
browser.wait(EC.visibilityOf(category2.get(0)), 20000);
});
});
But test fails with the following error: Failed: Index out of bound. Trying to access element at index: 0, but there are only 0 elements that match locator by.repeater("category in listCtrl.categories").
Error doesn't depend on locator type, because appears for both: "by.repeater" and "by.css".
The selectors are ok, test passes after adding 'sleep' command:
var category = element.all(by.repeater('category in listCtrl.categories'));
var category2 = $$('.category-name.custom-tooltip-link.ng-binding');
var EC = protractor.ExpectedConditions;
describe('wait for the first category', function() {
it('wait', function() {
browser.get('http://www.deep.mg/');
browser.sleep(15000);
browser.wait(EC.visibilityOf(category.get(0)), 20000);
browser.wait(EC.visibilityOf(category2.get(0)), 20000);
category.count().then(function(count1) {
console.log(count1); //count returns 5, which means there are actually elements in array
});
category2.count().then(function(count2) {
console.log(count2);
});
});
});
Also timeout parameter doesn't help, it just ignores it and fails immediately.
So the question is how to wait for a certain element of an array? Am I missing something? Thanks.

Make a custom Expected Condition to wait for count of elements in an array to be more than 0:
function presenceOfAll(elementArrayFinder) {
return function () {
return elementArrayFinder.count(function (count) {
return count > 0;
});
};
}
Usage:
browser.wait(presenceOfAll(category), 10000);
browser.wait(presenceOfAll(category2), 10000);
Works for me.

element.all(by.repeater('category in listCtrl.categories')).get(0) will ALWAYS throw an error if there are no elements to 'get' (source: element.js ElementArrayFinder.prototype.get)
You can do:
browser.wait(function() {
return category.count().then(function(catCount) {
if (catCount > 0) {
return EC.visibilityOf(category.get(0));
}
}
}, 20000);
Or you could probably just wait until all the elements are visible, and it would do what you are asking it to do (because it will wait for the 'all' promise to resolve completely anyway, not just break out when it gets the first one):
browser.wait(EC.visibilityOf(category), 20000);

Related

Using _.each and $q promise to iterate widgets

I have a pretty straight-forward problem where I'm :
Iterating through a series of dashboard "widgets" using _.each().
Calling a function to refresh the current widget, and returning a $q promise.
Now, my issue is that I would like each iteration to WAIT prior to continuing to the next iteration.
My first version was this, until I realized that I need to wait for updateWidget() to complete:
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
updateWidget(wid, parentWidgetData);
}
});
My second version is this one, which returns a promise. But of course, I still have the problem where the iteration continues without waiting :
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
updateWidget(wid, parentWidgetData).then(function(data){
var i = 1;
});
}
});
and the called function which returns a deferred.promise object (then makes a service call for widget data) :
function updateWidget(widget, parWidData) {
var deferred = $q.defer();
// SAVE THIS WIDGET TO BE REFRESHED FOR THE then() SECTION BELOW
$rootScope.refreshingWidget = widget;
// .. SOME OTHER VAR INITIALIZATION HERE...
var url = gadgetDataService.prepareAggregationRequest(cubeVectors, aggrFunc, typeName, orderBy, numOrderBy, top, filterExpr, having, drillDown);
return gadgetDataService.sendAggGetRequest(url).then(function (data) {
var data = data.data[0];
var widget = {};
if ($rootScope.refreshingWidget) {
widget = $rootScope.refreshingWidget;
}
// BUILD KENDO CHART OPTIONS
var chartOptions = chartsOptionsService.buildKendoChartOptions(data, widget);
// create neOptions object, then use jquery extend()
var newOptions = {};
$.extend(newOptions, widget.dataModelOptions, chartOptions);
widget.dataModelOptions = newOptions;
deferred.resolve(data);
});
return deferred.promise;
}
I would appreciate your ideas on how to "pause" on each iteration, and continue once the called function has completed.
thank you,
Bob
******* UPDATED ************
My latest version of the iteration code include $q.all() as follows :
// CREATE ARRAY OF PROMISES !!
var promises = [];
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
promises.push(updateWidget(wid, parentWidgetData));
}
});
$q.all(promises)
.then(function () {
$timeout(function () {
// without a brief timeout, not all Kendo charts will properly refresh.
$rootScope.$broadcast('childWidgetsRefreshed');
}, 100);
});
By chaining promises
The easiest is the following:
var queue = $q.when();
_.each(widgets, function (wid) {
queue = queue.then(function() {
if (wid.dataModelOptions.linkedParentWidget) {
return updateWidget(wid, parentWidgetData);
}
});
});
queue.then(function() {
// all completed sequentially
});
Note: at the end, queue will resolve with the return value of the last iteration
If you write a lot of async functions like this, it might be useful to wrap it into a utility function:
function eachAsync(collection, cbAsync) {
var queue = $q.when();
_.each(collection, function(item, index) {
queue = queue.then(function() {
return cbAsync(item, index);
});
});
return queue;
}
// ...
eachAsync(widgets, function(wid) {
if (wid.dataModelOptions.linkedParentWidget) {
return updateWidget(wid, parentWidgetData);
}
}).then(function() {
// all widgets updated sequentially
// still resolved with the last iteration
});
These functions build a chain of promises in the "preprocessing" phase, so your callback is invoked sequentially. There are other ways to do it, some of them are more efficient and use less memory, but this solution is the simplest.
By delayed iteration
This method will hide the return value even of the last iteration, and will not build the full promise chain beforehands. The drawback is that, it can be only used on array like objects.
function eachAsync(array, cbAsync) {
var index = 0;
function next() {
var current = index++;
if (current < array.length) {
return $q.when(cbAsync(array[current], current), next);
}
// else return undefined
}
// This will delay the first iteration as well, and will transform
// thrown synchronous errors of the first iteration to rejection.
return $q.when(null, next);
}
This will iterate over any iterable:
function eachAsync(iterable, cbAsync) {
var iterator = iterable[Symbol.iterator]();
function next() {
var iteration = iterator.next();
if (!iteration.done) {
// we do not know the index!
return $q.when(cbAsync(iteration.value), next);
} else {
// the .value of the last iteration treated as final
// return value
return iteration.value;
}
}
// This will delay the first iteration as well, and will transform
// thrown synchronous errors of the first iteration to rejection.
return $q.when(null, next);
}
Keep in mind that these methods will behave differently when the collection changes during iteration. The promise chaining methods basically build a snapshot of the collection at the moment it starts iteration (the individual values are stored in the closures of the chained callback functions), while the latter does not.
Instead of trying to resolve each promise in your _.each(), I would build out an array of promises in your _.each to get an array like:
promises = [gadgetDataService.sendAggGetRequest(url1), gadgetDataService.sendAggGetRequest(url2)....]
Then resolve them all at once, iterate through the results and set your models:
$q.all(promises).then(function(results){ // iterate through results here })

How to execute promises "sync" and not in async way

I calling getBubblesUserAccess that returns json objects that are orderd in a special way. This results i wanna run a foreach and get other messages but there i wanna return them in "order". I know that it will run these async but there must be a way that i can force it to "sequential" execution. (above code is my last attempt to add a defer...)
Example
pseudo code - get my groups
{
"id":"016cd1fc-89a3-4e4a-9e6e-a102df1b03d9",
"parent":"53750396-7d26-41f3-913d-1b93276b9e09",
"name":"XX",
"createdBy":"c9c63080-2c5b-4e8e-a093-2cfcd628a9d0",
"hasWriteAccess":true,
"hasCreateAccess":false,
"hasDeleteAccess":false,
"hasAdminAccess":false,
"settingsBubbleId":"00000000-0000-0000-0000-000000000000"
},
{
"id":"016cd1fc-89a3-4e4a-9e6e-a102df1b03d9",
"parent":"53750396-7d26-41f3-913d-1b93276b9e09",
"name":"XX",
"createdBy":"c9c63080-2c5b-4e8e-a093-2cfcd628a9d0",
"hasWriteAccess":true,
"hasCreateAccess":false,
"hasDeleteAccess":false,
"hasAdminAccess":false,
"settingsBubbleId":"00000000-0000-0000-0000-000000000000"
}
From this result i wanna iterate over those parent id strings and call another service that respond with this.
pseudo code
for each group above call another service with parent id and get result. This result will be added to a new JSON object.
"messages":[
{
"id":"f1d1aeda-d4e2-4563-85d5-d954c335b31c",
"text":"asd",
"sent":"2015-09-10T22:31:09.897+00:00",
"sender":"6b9e404b-ef37-4d07-9267-3e7b2579003b",
"senderName":"XXX XXXX"
},
{
"id":"a7ac0432-e945-440e-91ce-185170cbf3de",
"text":"asd",
"sent":"2015-09-10T22:28:24.383+00:00",
"sender":"c9c63080-2c5b-4e8e-a093-2cfcd628a9d0",
"senderName":"ZZZZZ ZZZZ"
},
My problem is that my second foreach are running async (as it should) and i want it to resolve back in same order as first json object...
My code::
var loadBubblesAccess = function () {
if (vm.running && angular.isDefined(vm.running)) { return; }
vm.running = true;
vm.bubblesWithMessages = null;
return BubbleFactory.getBubblesUserAccess().then(function (bubblesAccessTo) {
return bubblesAccessTo;
});
},
loadSubBubbles = function (bubblesAccessTo) {
/**
* Result from chain method with all bubbles user has access to.
*/
var promiseArray = [];
//var promiseArrayError = [];
var i = 0;
/**
* Creates a defer object so that we will not resolve before for each loop has been gone thru.. async problems.
*/
var deferred = $q.defer();
angular.forEach(bubblesAccessTo, function (bubble) {
$log.error(JSON.stringify(bubblesAccessTo));
/**
* Get 20 because default thats default and cache and e-tags are done to that number..
*/
BubbleFactory.getBubbleMessages(bubble.id, 0, 20, false).then(function (data) {
i++;
if (data.messages.length > 0) {
promiseArray.push({ bubbleSortOrder: i, bubbleId: bubble.parent, bubbleName: bubble.name, bubbleMessagesId: bubble.id, bubbleMessages: smartTrim(data.messages[0].text, 400, ' ', ' ...'), bubbleMessagesSent: data.messages[0].sent });
}
else {
// console.log("YYYY::: " + bubble.parent);
promiseArray.push({ bubbleSortOrder:i, bubbleId: bubble.parent, bubbleName: bubble.name, bubbleMessagesId: bubble.id, bubbleMessages: 'Inget meddelande än..', bubbleMessagesSent: '' });
}
});
/**
* Check if we have gone thru all bubbles - when finished we resolve defer object.
*/
if(i===bubblesAccessTo.length)
{
deferred.resolve(promiseArray);
}
});
//$log.debug.log(promiseArray);
vm.bubblesWithMessages = promiseArray;
promiseArray.length = 0;
vm.running = false;
};
loadBubblesAccess().then(loadSubBubbles);
The $q service in AngularJS is described as "lightweight" because it only implements the functions 90% of people need. That keeps its code size small - at the expense of not being able to address requests like yours very easily.
If you have the option, try an alternative such as bluebird. Bluebird provides a reduce() function that can execute an array of promises serially, and return their results in the order they were requested. It makes this task straightforward because your result array will match your data array and you can match up the results very easily.
If you do NOT have that option, there is a standard (if not-exactly-simple) technique with promises where you build an array of the elements you want to promise, then call the processing function (that returns a Promise) on the first value (popped from the array). In the .finally() handler, call the processing function recursively with the next value until it is empty (or an error occurs).
Pseudo-code for this:
var valuesToProcess = [1, 2, 3],
results = [];
function processValue(val) {
myProcessingFunction(val).then(function(result) {
results.push(result);
}).catch(function(e) {
console.log('FAIL!', e);
}).finally(function() {
if (valuesToProcess.length > 0) {
processValue(valuesToProcess.shift());
} else {
// All done - do something with results here
}
});
}
// Note: No error checking done, assumes we have work to do...
processValue(valuesToProcess.shift());
You'll need to adapt this to your use-case but it's a simple technique that guarantees serial operation and result-handling.

Protractor expected condition for element containing any text

Is there a way how to check whether an element has any text in it? I already found textToBePresentInElement but this function checks for specified value and does not return a proper error if it fails.
I'm population the element via API and it's loaded bit later, so I want the browser to wait till any information appears in the element and then check for correct value.
Alternatively it would be also very helpful to manage to get a specific error message when EC fails:
browser.wait(EC.textToBePresentInElement(element(by.binding('myvar')), "expected"), 5000);
The third argument to browser.wait() is a custom error message:
browser.wait(EC.textToBePresentInElement(element(by.binding('myvar')), "expected"), 5000, "Text is not something I've expected");
See also:
Custom message on wait timeout error
To wait for an element to contain any text, you can write a custom expected condition:
var EC = protractor.ExpectedConditions;
var anyTextToBePresentInElement = function(elementFinder) {
var hasText = function() {
return elementFinder.getText().then(function(actualText) {
return actualText;
});
};
return EC.and(EC.presenceOf(elementFinder), hasText);
};
And here is the usage:
browser.wait(anyTextToBePresentInElement(element(by.binding('myvar'))), 5000);
The previous code snippet works form but with a small update: return actualText; should be boolean. So the whole code will be:
var anyTextToBePresentInElement = function(elementFinder) {
var EC = protractor.ExpectedConditions;
var hasText = function() {
return elementFinder.getText().then(function(actualText) {
return !!actualText;
});
};
return EC.and(EC.presenceOf(elementFinder), hasText);
};
Usage example:
var el = element(by.binding('myvar'));
browser.wait(anyTextToBePresentInElement(el, 5000, 'Element still has no text');
You can check this one
export function titleType(textReference, expectedText)
{
textReference.then(function(name)
{
if (name == expectedText)
{
expect(textReference).toContain(expectedText);
expect(name).toEqual(expectedText);
}
else
{
throw new TypeError("Wrong " + expectedText + " name " + name);
}
});
}

Has anyone found a way to add list numbering to Protractor Describe blocks?

I have a very large number of Describe blocks in my Protractor tests. I get pages and pages of test output all correctly indented but it's difficult to see which test is which and how far the tests have progressed.
Has anyone tried to add a list numbering to the Describe. Something like this:
1. Main Page test
1.1 Test xxx
1.2 Test yyy
1.2.1 Describe in describe in describe test
2. XXX Page test
2.1 Test abc
Note that here the first and maybe second number after dots would be a result of describes with describes.
You can use jasmine spec reporter (>= 1.1.0) with the displaySuiteNumber option to display the output of your protractor tests.
Output example:
1 first suite
✗ should failed
- Expected true to be false.
✓ should be ok
2 second suite
✗ should failed
- Expected true to be false.
✓ should be ok
2.1 first child suite
2.1.1 first grandchild suite
✗ should failed
- Expected true to be false.
- Expected true to be false.
✗ should failed
- Expected true to be false.
✓ should be ok
You can write a (not so) simple "plugin" that adds this functionality.
I'd avoid replacing the original describe and it functions. The only downside of my approach is that you'll have to do a search and replace from describe and it to lp.describe and lp.it respectively.
(Yes, you could just overwrite the original describe and it if you are sure it won't affect anything else - and it should not, but just to be on the safe side, don't :) )
My approach, updated to take into account the fact that you can have a describe inside another describe:
list-plugin.js
(function(protractorDescribe, protractorIt) {
var level = -1;
var ids = [1];
function levelLabel() {
var label = ids.join('.');
if(ids.length === 1) {
label += '.';
}
return label;
}
function startDescribe() {
startIt();
level += 1;
}
function endDescribe() {
ids[level] += 1;
ids.pop();
level -= 1;
}
function startIt() {
if(!ids[level + 1]) {
ids[level + 1] = 1;
}
}
function endIt() {
ids[level + 1] += 1;
}
function describe(name, body) {
var protractorResult;
startDescribe();
protractorResult = protractorDescribe(levelLabel() + ' ' + name, body);
endDescribe();
return protractorResult;
}
function it(name, body) {
var protractorResult;
startIt();
protractorResult = protractorIt(levelLabel() + ' ' + name, body);
endIt();
return protractorResult;
}
exports.describe = describe;
exports.it = it;
})(describe, it);
spec.js
var lp = require('./list-plugin.js');
lp.describe('Main Page test', function() {
lp.it('Test xxx', function() {
expect('a').toEqual('a');
});
lp.describe('Test yyy', function() {
lp.it('Describe in describe test', function() {
expect('a').toEqual('a');
});
});
});
lp.describe('XXX Page test', function() {
lp.it('Test abc', function() {
expect('a').toEqual('a');
});
});
conf.js
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
jasmineNodeOpts: {
isVerbose: true
},
specs: [
'spec.js'
]
};
Probably the most elegant solution would be to change protractor's code. But it might be problematic if needed to upgrade the library.
I came up with a working solution by decorating protractor's describe instead. The only caveat is that it requires the spec code to be indented correctly. Actually this limitation may be considered as a feature, as for sure it is a good practice to have the code indented correctly and with current IDEs it is just a 2-sec-task. You can reset the counter (e.g. at the beginning of each spec) by calling require('./protractor-decorator').resetCounter();.
UPDATE
If you want to decorate the it just call it = require('./protractor-decorator.js').decorateUsingErrorStack(it); or refactor it into a single method.
protractor-decorator.js module:
var stack = [];
var lastIndentColumn = 1;
function decorateUsingErrorStack(origDescribe){
function describe(){
var callerIndent, args;
if(stack.length === 0){
stack.push(0);
}
// from current stack we get the information about indentation of the code
callerIndent = new Error().stack.split('\n')[2].split(':');
callerIndent = parseInt(callerIndent[callerIndent.length-1]);
if(callerIndent == lastIndentColumn){
stack[stack.length-1] += 1;
}
else {
if(callerIndent < lastIndentColumn){
stack.pop();
stack[stack.length-1] += 1;
}
else {
stack.push(1);
}
}
lastIndentColumn = callerIndent;
args = Array.prototype.slice.call(arguments, 0);
origDescribe.call(null, stack.join('.') + '. ' + args[0], args[1]);
}
return describe;
}
module.exports = {
decorateUsingErrorStack : decorateUsingErrorStack,
resetCounter : function(){
// this function should be called to start counting from 1.
stack = [];
lastIndentColumn = 1;
}
}
spec.js file:
describe = require('./protractor-decorator.js').decorateUsingErrorStack(describe);
describe(' should be 1.', function(){
describe('should be 1.1.', function(){
it('xxx', function(){
});
describe('should be 1.1.1.', function(){
it('xxx', function(){
});
describe('should be 1.1.1.1', function(){
it('xxx', function(){
});
});
describe('should be 1.1.1.2', function(){
it('xxx', function(){
});
});
});
describe('should be 1.1.2.', function(){
it('xxx', function(){
});
});
});
describe('should be 1.2.', function(){
it('xxx', function(){
});
});
describe('should be 1.3.', function(){
it('xxx', function(){
});
});
});
// same as above but all starts with 2.
describe(' should be 2.', function(){...});

Testing Angular Filter That Returns An Array with Jasmine

So, I'm having issues testing an angular filter that takes an array that has previously been sorted by a group property. It uses a flag property to indicate that the item is the first observation of that group, and then false for subsequent observations.
I'm doing this to have a category header in the UI with an ng-repeat directive.
When I test the filter, the output does not return the array with the flags unless I create new objects for the return array. This is a problem, because it causes an infinite loop when running in a webpage. The code works in the webpage when it just adds a flag property to the input object.
Is there some additional step I should be taking to simulate how angular handles filters so that it outputs the proper array?
This is what my test looks like right now.
describe('IsDifferentGroup', function() {
var list, itemOne, itemTwo, itemThree;
beforeEach(module("App.Filters"));
beforeEach(function () {
list = [];
itemOne = new ListItem();
itemTwo = new ListItem();
itemThree = new ListItem();
itemOne.group = "A";
itemTwo.group = "B";
itemThree.group = "C";
list.push(itemOne);
list.push(itemOne);
list.push(itemOne);
list.push(itemOne);
list.push(itemTwo);
list.push(itemThree);
list.push(itemThree);
list.push(itemThree);
list.push(itemThree);
list.push(itemThree);
});
it('should flag the items true that appear first on the list.', (inject(function (isDifferentGroupFilter) {
expect(list.length).toBe(10);
var result = isDifferentGroupFilter(list);
expect(result[0].isDifferentGroup).toBeTruthy();
expect(result[1].isDifferentGroup).toBeFalsy();
expect(result[4].isDifferentGroup).toBeTruthy();
expect(result[5].isDifferentGroup).toBeTruthy();
expect(result[6].isDifferentGroup).toBeFalsy();
expect(result[9].isDifferentGroup).toBeFalsy();
})));
});
And here is something like the code with the filter:
var IsDifferentGroup = (function () {
function IsDifferentGroup() {
return (function (list) {
var arrayToReturn = [];
var lastGroup = null;
for (var i = 0; i < list.length; i++) {
if (list[i].group != lastGroup) {
list[i].isDifferentGroup = true;
lastAisle = list[i].group;
} else {
list[i].isDifferentGroup = false;
}
arrayToReturn.push(list[i]);
}
return arrayToReturn;
});
}
return IsDifferentGroup;
})();
Thanks!
I figured out my issue.
When I was passing the items into the list, I just pushed a pointer to an item multiple times. I was not passing in unique objects so the flag was being overridden by the following flag in the array(I think). So, I just newed up 10 unique objects using a loop, pushed them into the array and ran it through the filter. And it worked.
I'm not entirely sure my analysis is correct about the override, because itemTwo was not being flagged as unique when it was the only itemTwo in the array. But the test is working as I would expect now so I'm going to stop investigating the issue.

Resources