How to test applications that use long polling with Protractor - angularjs

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.

Related

Stuck with infinitely repeating digest loops

I have an Angular SPA I'm running with Angular Loading Bar and AngularJS v1.4.9.
For some time now, it has been so happening that after the app gets loaded, the bar has been getting stuck after a while, indicating that not all requests are done with. Additionally, one of the console.log()s I have in our code have been firing continuously, around 1-2 times every second. The bar completes and the console.log works normally when the user reloads the page(but doesn't stop on its own).
The console.log() is set inside a function attached to a ng-disabled directive, so I know it's an indicator of a digest cycle in progress.
I use Chrome as my browser and I recently did a profiling run.
Here's some screenshots of what I see:
This is a broad view. As is shown here, it's first happening at 100ms, then at 400, then at 600, and so on(I did a 3s run).
This is the very first vertical strip. Not all of them look exactly the same as this one, but the completeOutstandingRequest, timeout and Browser.self.defer methods are always there. The searchDisable and log methods are ours, the log is the one I'm talking about above.
Here's another one for comparison, but this is slightly different - it has another Browser method: self.url. I'm not sure what it does.
Here are some issues I found which could be related:
Timeout callback can occur in the middle of a digest cycle in Firefox
$browser.defer ends up triggering changeDetection in zonejs through settimeout
P.S. I think this issue first started when we added some interceptors to our code to do some automatic redirects - e.g. when the session has timed out and the user clicks on something, he's automatically returned to the login page to relogin.
This is the interceptor:
interceptor.$inject = ['$rootScope', '$q'];
function interceptor($rootScope, $q) {
return {
responseError: function (rejection) {
var config = rejection.config || {};
if (!config.ignoreAuthModule) {
switch (rejection.status) {
case 401:
var deferred = $q.defer();
$rootScope.$broadcast('event:auth-loginRequired', rejection);
return deferred.promise;
case 403:
$rootScope.$broadcast('event:auth-forbidden', rejection);
break;
}
}
return $q.reject(rejection);
}
};
}
$httpProvider.interceptors.push(interceptor);
This issue has been solved.
It turns out that in case of any Unauthorized requests, the promise that is returned on line 11 above was remaining pending forever, because there was no call to .resolve() or .reject() in the deferred object.
As a result, the loading bar's interceptor was blocked.
I ended up removing the custom promise entirely and that solved the problem.

Protractor timeouts

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();
});
},

Angular, refresh source

I'm having a trouble looking for an answer for this situation.
Our system server has logs that keeps updating.
The view of the logs is terrible ! and very messy.
So I want to build a web application that will show the logs and will offer filtering options to make our life easier :).
So this is what I've planned:
1) I'll load up to 50 log lines (so it wont be blank when opened);
2) I'll keep pulling information for every line the console write.
Well, How do I approach step 2? how can I be synced with the server logs?
furthermore, When I load the 50 lines maybe a new line alrdy had been put out in the console, which lower the integrity of data provided in the log viewer.
BTW I use PuTTY to see the log console.
Excuse me for my bad English. Thanks from advance :)
Ok, so if you are planning on doing this entirely client side with Angular, you're going to need to:
Create an API, some endpoint/url that you can fetch your logs from. Something like http://www.example.com/my-console-feed/. You might already have this.
Poll the server with angular's $interval at the interval of your choosing.
If you were operating with web sockets, perhaps some continuous feed could be done.
So you could make a factory that looks something like this:
(function() {
'use strict';
angular
.module('myApp')
.factory('pollingFactory', pollingFactory);
function pollingFactory($http, $interval) {
return function() {
// 60000 is 1 minute
var duration = 60000;
// log API endpoint
var myLogAPIEndpoint = 'http://www.example.com/my-console-feed/';
// this will hit the API every minute
$interval(function() {
// poll the api for new logs
$http({
url: myLogAPIEndpoint,
method: 'GET'
})
.success(function(response) {
// do something with the response
})
}, duration);
}
}
})();
If you were going to go this way, I'd use $q as a promise library and in your controller, when the promise resolves, update the model in your scope with the new logs. If you're using ng-repeat, I'd also recommend using track by for performance if the logs have unique IDs.
Here is a fiddle polling an API every 3 seconds.

angularjs http.get only works first time

I have a function in my controller that calls an api to retrieve some values:
$scope.Refresh= function(){
$http.get('/get/value')
.success(function(data) {
//some actions
})
.error(function(data) {
//some actions
});
} ;
I want to refresh the values occasionally, so I've done:
setInterval($scope.Refresh, 100000);
I will do in a better way, but now I want to solve this.
but there is a problem:
If, in the controller, I say: $scope.Refresh (to execute the function first time), the controller does nothing.
If I write the same function + setInterval (to test and run it) it works first time (outside the function), but never refresh next times (code function inside), to explain, that execute the function but neither .success nor .error is called.
I have seen the headers with a 304 status (not modified) but the values are modified!!
I tried to disable cache but that did not fix the problem.
I tried to give a random value to the route like: /get/value/(randomNuber) but I get nothing
Where is the problem?
Just running:
$scope.Refresh();
should definitely run the function at least once. If it doesn't something is wrong with your code or with your server route. But you should be getting a console error if that's the case.
For setInterval, you should be using the $interval service that ensures your code is run within the angular loop.
Also, per the documentation, you should explicitly cancel this interval when your controller is destroyed.
var httpInterval = $interval($scope.Refresh, 100000);
$scope.$on('$destroy', function() {
$interval.cancel(httpInterval);
});
I've only had intermittent luck with .success and .error, and I'd like to think that part of it was caching the request. I have very consistent, successful results using .then, as shown:
$scope.Refresh= function(){
var myGet = $http.get('/get/value');
myGet.then(function(data){
//do success things here
}, function(data){
//do error things here
});
};
Other than that, follow the advice that #theJoeBiz gave regarding $interval and you should be fine.

AngularJS - When does a service retrieve data?

When does a Service / Factory retrieve data from an HTTP request?
When a factory is created in a service, I am curious as to when the HTTP request is sent, and how it is processed after the app is running for some time.
I'm writing my code using Ionic Framework. Once I initialize my app, and it stays open for a day or two, will the JSON data be refreshed at any interval? Or does it only refresh the data once the app is closed, and opened once again?
My requirement for the HTTP request, is that it is updated every day at the 00:01 AM.
I suppose my general question is: how does an HTTP request fetch data? And How does a service work in AngularJS.
Here is my code to retrieve a JSON package:
angular.module('starter.services', [])
.factory('menuJSON', function ($http) {
return {
all : function() {
return $http({
url: 'http://middmenuapi.herokuapp.com/',
method: 'GET'
})
}
}
});
Calls to $http() (or any of the aliases, such as $http.get() etc) invoke a web request immediately (barring any manipulation by inspectors or third-party components). It is analogous to issuing an XmlHttpRequest or JSONP request in other frameworks, like jQuery.
Services are created as singletons, so they are created once when they are first requested, and the same instance is injected from that point on. What the service does is entirely up to you, as Angular only deals with instantiating it, resolving any of its dependencies, and injecting it anywhere it's requested.
If you have an application that is running for long periods of time and needs to be updated with data, you'll need to architect it appropriately. Obviously I don't know the full requirements or particulars of your spec, but I can give you some hints to maybe get you going.
High level, it sounds like you need a function to operate on a timer (using the $timeout service), and if you meet or exceed a time window, invoke an $http request to retrieve the latest data and route it along to various components that need it. Then, it should mark the time frame it should next operate, then set a timeout again so it can wake further down the road and see if it's time to do work again.
The first thing to think about is where should this functionality live? If you only need it to happen in a certain controller, then you can do it all there in that controller using $timeout and $http. On the other hand, if you need to reuse this data in multiple places, you'll want to use a service. If you use a service, which is likely, then you need to figure out the best way to get those changes to the various parts of your app that need it.
My recommendation is to use Angular events on the $rootScope to $broadcast from your service when your $http request has updated data. Then, your various controllers, services, and directives that consume this data can subscribe to this event using $scope.$on and react appropriately. This keeps the service decoupled from the things that use it and allow them to react to changes easily.
All the service does is set a timeout, when it lapses check for data, if it has data, broadcast the data in an event on $rootScope, and set another timeout. The clients just listens and updates its local scope with the new data when it receives the event from the service.
This plunk contains a silly example. You'd want to change it to schedule work at a time of day or whatever you see fit, and then also have it make an $http request rather than send the current date.
angular.module("demo", [])
.service('myService', ['$rootScope', '$timeout', '$http', function($rootScope, $timeout, $http) {
var state = { timeout: null, next: null };
function work() {
var now = Date.now();
if (now >= state.next) { // you can replace with your own logic to schedule when it should occur (like a specific time of day). in this example, we poll every second, but do work every 5.
// this is where your $http service can do work, something like $http.get(...).success(function(data) { $rootScope.$broadcast('myService.data', data); });
$rootScope.$broadcast('myService.data', new Date());
state.next = now + 5000; // do work every five seconds
}
state.timeout = $timeout(work, 1000); // poll every second
}
return {
start: function() {
if (state.timeout) $timeout.cancel(state.timeout); // cancel pending timeout
work(); // first time will just schedule work to be done in the future
},
stop: function() {
if (state.timeout) $timeout.cancel(state.timeout); // cancel pending timeout
}
};
}])
.controller('DemoCtrl', ['$scope', function($scope) {
$scope.title = "Hello, World";
// here, the controller subscribes to the event, and when it occurs, it copies the event data to a local scope item
$scope.$on('myService.data', function(evt, data) {
$scope.$apply(function() {
$scope.myServiceData = data;
});
});
}])
.run(['myService', function(myService) {
myService.start(); // starts the service when the app runs
}]);

Resources