I've been developing automated tests in Protractor for quite some time and like many of you, I've run into gaps which can only be crossed with the browser.sleep()-bridge. I'm not a fan of hard coding things such as this but if it's a necessity I will.
The tests I've developed have brought me to a point where every browser.sleep(1000) has a major impact on my runtime. The tests are currently testing permissions for different accounts (128 exactly) and this involves logging in and out whilst checking what every account has and has not received access to.
The website I'm testing is a pure AngularJS application which, in my eyes, should make browser.sleep() a deprecated method since there is a browser.waitForAngular() method that accurately waits until the page is fully loaded compared to browser.sleep() which waits a set amount of time and if your website isn't loaded within that time (it happens), you'll have an inconsistent test (nobody likes inconsistency).
Research has led me to believe that browser.waitForAngular() does not take into account animations and related time-consuming features since they're not AngularJS related yet this is not implemented in our website. Also waitForAngular() basically waits for $digest, $http, and $timeout.
What I'm asking is wether this is something which is regarded as an acceptable loss since Protractor is great in general or is there something I'm overlooking here?
TL;DR:
Are there solutions out there to allow us not to settle for browser.sleep()?
Sources: Protractor Timeout Docs,
Timeout-spec.js (protractor docs),
Issue909, Issue279, Issue92, StackQuestion1
If you can devise some sort of test to determine if whatever you're waiting for has completed, you can use browser.wait. Taking ideas from from http://docsplendid.com/archives/209, you can pass a function that returns a promise that resolves to true or false, such as one that uses isPresent
browser.wait(function() {
return element(by.id('some-element')).isPresent();
}, 1000);
or if you have some more complicated condition you can use promise chaining:
browser.wait(function() {
return element(by.id('some-element')).isPresent().then(function(isPresent) {
return !isPresent;
});
}, 1000);
and the command flow will wait, repeatedly calling the function passed to wait, until the promise it returns resolves to true.
This is the way if you want to perform any action when element present or want to wait until it present on page.
element(by.id).isPresent().then(function(result) {
if (result) {
nextButton.click();
}
else{
browser.wait(function () {
return browser.isElementPresent(element(by.id));
},50000);
}
}).then(function () {
nextButton.click();
});
},
Related
I am building an Angular app that uses long polling to receive fast updates whenever something changes on the server. I use $resource like this to fetch the actual data:
appServices.factory('Data', ['$resource',
function(){
return $resource('', {}, {
query: {"url": …, isArray: false}
});
}]);
Then I have a service that takes care of the long polling: Wait that the data are loaded; store them somewhere; after one second, start the next long-polling cycle:
app.factory(„DataLoader“, [„Data“, "$timeout", function(Data, $timeout) {
return {
loadData: function() {
var parent = this;
var data = Data.query({},
function(result) {
/* do something to the data,
* then start waiting for an update from the server again
*/
$timeout(function() {
parent.loadData();
}, 1000);
}
);
}
};
});
It works like a charm so far.
However, I am now trying to write Protractor tests for this. The problem is: The server times out the long polling requests after 30 seconds only if there are no changes to the data. As I am waiting for new data inside $timeout, Protractor times out before any results arrive.
I have googled the last hour, but there doesn't seem to be a solution except for using $interval instead of $timeout. This works in a good old polling setup (poll every 3 seconds, get empty results from the server if there's nothing new). However, to avoid exactly that, I implemented long polling. $timeout is just the much more sensible option for me.
Can you give me any tips how to get Protractor running successfully in this environment?
I would suggest taking a look at Protractor's documentation on timeouts. You will probably need to increase your allScriptsTimeout in your configuration file since the default wait for page synchronization is 11 seconds.
Use $interval angular api and $fetch for polling data continuously in your app
If you dont want to use $interval instead of $timeout; which is not a good practice,
then you have to turn off the browser synchronization with angular browser.waitForAngularEnabled(false) and use either browser.sleep() or browser.wait() to achieve synchronization between the elements that you interact with on the page.
I am using the ngIdle library and as the documentation states there are certain methods that you can call to check for user inactivity, and I have put these on the root scope.
app.run(function($rootScope) {
$rootScope.$on('IdleTimeout', function() {
alert('you will be logged out');
});
$rootScope.$on('IdleStart', function () {
alert('test');
});
});
These functions are never being called and I think that it might be a problem more to do with $rootscope rather than the ngidle library.
There are not any errors in console, and the ngidle library is included correctly. Any Ideas?
For me ng-idle works fine using the $rootScope. But I wasted some time waiting for the events to fire until I recognized, that the configuration expects seconds instead of milliseconds ;)
app.config(function(IdleProvider) {
// configure Idle settings
IdleProvider.idle(5); // in seconds!
IdleProvider.timeout(5); // in seconds!
});
I spent some time struggling with this too; none of the events were firing at all. Turns out that you need to call Idle.watch() so that the timing starts.
This isn't well documented; documentation is patchy and none of the examples I saw had this. All looked just like the code you have so I couldn't figure out what's wrong until I saw it in the Getting Started section on the library's GitHub page. It's not explicitly mentioned though, you have to look closely.
.run(function(Idle){
// start watching when the app runs. also starts the Keepalive service by default.
Idle.watch();
});
Then the second catch: once you get auto-logged out once, it will no longer time your session if you login again without reloading the app. You need to call Idle.watch() again from your Login method to reset the timers and start again.
I'm trying to write what I think is a fairly simple test in protractor, but it would seem that the minute you try to do anything synchronously, Protractor makes life hard for you! Normally, dealing with locator functions (that return a promise) are not an issue, since any expect statement will automatically resolve any promise statement passed to it before testing the assertion. However, what I'm trying to do involves resolving these locator promises before the expect statement so that I can conditionally execute some test logic. Consider (pseudocode):
// Imagine I have a number of possible elements on the page
// and I wish to know which are on the page before continuing with a test.
forEach(elementImLookingFor){
if (elementImLookingFor.isPresent) {
// record the fact that the element is (or isnt) present
}
}
// Now do something for the elements that were not found
However, in my above example, the 'isPresent' call returns a promise, so can't actually be called in that way. Calling it as a promise (i.e. with a then) means that my forEach block exits before I've recorded if the element is present on the page or not.
I'm drawing a blank about how to go about this, has anyone encountered something similar?
I've used bluebird to do the following;
it('element should be present', function(done)
Promise.cast(elementImLookingFor.isPresent)
.then(function(present){
expect(present).toBeTruthy();
})
.nodeify(done);
});
If you have a few elements that you want to check the isPresent on you should be able to do the following;
it('check all elements are present', function(done){
var promises = [element1, element2].map(function(elm){
return elm.isPresent();
});
// wait until all promises resolve
Promise.all(promises)
.then(function(presentValues){
// check that all resolved values is true
expect(presentValues.every(function(present){
return present;
})).toBeTruthy();
})
.nodeify(done);
});
Hope this helps
So elementImLookingFor is a promise returned by element.all, I presume? Or, as stated in the Protractor documentation, an ElementArrayFinder. You can call the method .each() on it and pass it a function that expects things.
In our very large and quite complex AngularJS application, I noticed (by accident!) that a line like this in my main module setup...
application.run(function($rootScope) {
window.setInterval( () => { $rootScope.$digest(); }, 1000);
});
...has significant positive impact, in the activation time of our $http.post requests.
A small part of our code that deterministically reproduces the behaviour is this:
// In the partial
<button ... ng-click="buttonHandler" ...>
// In the controller
$scope.buttonHandler = function() {
$http.post(....).success( function() { console.log("Activated"); })
}
We associate a button's ng-click handler with invocation of one of our web services.
The web service itself responds within 30ms (according to Chrome developer tools).
However, the code inside the .success handler is executed after 1.75 - 2.3 seconds (and only then is the message "Activated" displayed in the console).
However, when we put the Eternal rootScope $digest Hack (TM) in place, the activation occurs in less than a second! :-)
I can only guess that the rootScope $digest somehow 'triggers' the actual invocation of the .success handler, and since the webservice itself responds in 30ms, the activation time depends on when the button press happens to fall in the 1 second period of the hack's setInterval.
I am not saying that this is a recommended practise - but i was very surprised to see it happen. Note that there are no console errors logged or any other mischief reported anywhere in my assertive checks - the code works fine with or without the hack, but with significantly improved performance when the hack is in place.
Any idea what is going on?
Promises in Angular queue up a callback with $rootScope.$evalAsync(callback).
The callback checks to see whether the promise has been resolved/rejected.
$evalAsync schedules the task to run after the next digest cycle is complete.
Therefore, if you're preemptively asking for a digest cycle, your promise may be resolved quicker.
Source: https://github.com/angular/angular.js/blob/master/src/ng/q.js#L175
In my AngularJS application, for any page to load, there are two things which are loading
First the content of the page and secondly some back-end resources.
While back-end resources are loading, a spinner comes in the front and user is not able to do anything on the page contents.
Now while I am writing the automation test suites of the application using Protractor, I am not able to find a technique, to wait for the spinner to disappear from the screen before starting the test.
Please help me in this.
IsDisplayed as you mentioned in Andres D's comment should be the right one to use for your situation. However, it returns a promise so you cant just use
return !$('.spinner').isDisplayed()
since that will just always return false.
Try the below and see if it works.
browser.wait(function() {
return $('.spinner').isDisplayed().then(function(result){return !result});
}, 20000);
If you are waiting for something to happen you can use browsser.wait()
For example, if the spinner has the class name "spinner"
browser.wait(function() {
// return a boolean here. Wait for spinner to be gone.
return !browser.isElementPresent(by.css(".spinner"));
}, 20000);
The 20000 is the timeout in milliseconds.
Your test will wait until the condition is met.
For those dealing with non-angular apps:
browser.wait(
EC.invisibilityOf(element(by.css(selector))),
5000,
`Timed out waiting for ${selector} to not be visible.`
)
https://www.protractortest.org/#/api?view=ProtractorExpectedConditions.prototype.invisibilityOf