$timeout.flush during protractor test [duplicate] - angularjs

I'm testing my angular application with Protractor.
Once the user is logged in to my app, I set a $timeout to do some job in one hour (so if the user was logged-in in 13:00, the $timeout will run at 14:00).
I keep getting these failures:
"Timed out waiting for Protractor to synchronize with the page after 20 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md. The following tasks were pending: - $timeout: function onTimeoutDone(){....."
I've read this timeouts page: https://github.com/angular/protractor/blob/master/docs/timeouts.md
so I understand Protractor waits till the page is fully loaded which means he's waiting for the $timeout to complete...
How can I make Protractor NOT wait for that $timeout?
I don't want to use:
browser.ignoreSynchronization = true;
Because then my tests will fail for other reasons (other angular components still needs the time to load...)

The solution will be to flush active timeouts (as #MBielski mentioned it in comments), but original flush method itself is available only in anuglar-mocks. To use angular-mocks directly you will have to include it on the page as a <script> tag and also you'll have to deal with all overrides it creates, it produces a lot of side effects. I was able to re-create flush without using angular-mocks by listening to any timeouts that get created and then reseting them on demand.
For example, if you have a timeout in your Angular app:
$timeout(function () {
alert('Hello World');
}, 10000); // say hello in 10 sec
The test will look like:
it('should reset timeouts', function () {
browser.addMockModule('e2eFlushTimeouts', function () {
angular
.module('e2eFlushTimeouts', [])
.run(function ($browser) {
// store all created timeouts
var timeouts = [];
// listen to all timeouts created by overriding
// a method responsible for that
var originalDefer = $browser.defer;
$browser.defer = function (fn, delay) {
// originally it returns timeout id
var timeoutId = originalDefer.apply($browser, arguments);
// store it to be able to remove it later
timeouts.push({ id: timeoutId, delay: delay });
// preserve original behavior
return timeoutId;
};
// compatibility with original method
$browser.defer.cancel = originalDefer.cancel;
// create a global method to flush timeouts greater than #delay
// call it using browser.executeScript()
window.e2eFlushTimeouts = function (delay) {
timeouts.forEach(function (timeout) {
if (timeout.delay >= delay) {
$browser.defer.cancel(timeout.id);
}
});
};
});
});
browser.get('example.com');
// do test stuff
browser.executeScript(function () {
// flush everything that has a delay more that 6 sec
window.e2eFlushTimeouts(6000);
});
expect(something).toBe(true);
});
It's kinda experimental, I am not sure if it will work for your case. This code can also be simplified by moving browser.addMockModule to a separate node.js module. Also there may be problems if you'd want to remove short timeouts (like 100ms), it can cancel currently running Angular processes, therefore the test will break.

The solution is to use interceptors and modify the http request which is getting timeout and set custom timeout to some milliseconds(your desired) to that http request so that after sometime long running http request will get closed(because of new timeout) and then you can test immediate response.
This is working well and promising.

Related

What are the methods we can use to wait for an angular site to be loaded in order to test it with protractor?

What are the methods we can use to wait for an Angular site to be loaded in order to test it with protractor in order to avoid this error caused by jasmine : A Jasmine spec timed out. Resetting the WebDriver Control Flow ?
I'm able to make the login and go to home page that test is passed, but from the second test i have problems of jasmine.
I have configured this problem by adding this function into my config file :
onPrepare: function() {
return browser.getProcessedConfig().then(function(config) {
var browserName = config.capabilities.browserName;
browser.manage().timeouts().setScriptTimeout(60000);
});
});
You can use the browser object of Protractor to wait for angular.
As soon as you load your page add the following :
browser.waitForAngular();
This error means that your test took too much time and exceeded the default Jasmine spec timeout interval which is 30 seconds by default (It looks like you've configured the timeout to be 60 seconds). It can be configured in the jasmineNodeOpts object in your Protractor config:
jasmineNodeOpts: {defaultTimeoutInterval: timeout_in_millis},
The solution is usually use-case specific and it usually indicates there is an error in the test code. In order to fully understand what is going, we would need to see the code itself.
In your particular case, for starters, you should try moving the "ignore synchronization" and the browser.get() part into the beforeEach. Also, since you are turning the sync off, you need to wait for the element to be present on the page before interacting with it:
describe("my app", function () {
beforeEach(function () {
browser.ignoreSynchronization = true;
browser.get("...");
});
it("should make the login test", function () {
// ...
var EC = protractor.ExpectedConditions;
var username = element(by.model("credentials.username"));
browser.wait(EC.presenceOf(username), 10000);
username.sendKeys("RET02");
// ...
});
});
And, I am not sure if you really need to turn the synchronization off since this is an AngularJS page you are working with.
Can you wait for a url?
Let's assume that when you click on the login button your page is redirected to another url. So you can wait for the expected url. Example:
browser.driver.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
// Dashboard is the loaded url after login (in this example)
return /Dashboard/.test(url);
});
}, 60000);
This code waits for the page browser.baseUrl/Dashboard to be loaded, for 60 seconds

$watch doesn't work and is driving me mad

So, what I basically want to have is visualisation of the socket connection for the user. Meaning an indication wheter the client is connected to the server over socket.io or not.
Therefor I have service which keeps track of the socket state:
[...]
.factory('DataConnection', function(CONN_EVENTS, socket, $rootScope) {
var dataConnection = {};
dataConnection.states={
// indicates if the user can interact with the ui
uiEnabled: false,
[...]
};
// socket IO connection states
$rootScope.$on(CONN_EVENTS.socketAuth, function() {
dataConnection.states.uiEnabled = true;
});
$rootScope.$on(CONN_EVENTS.socketUnAuth, function() {
dataConnection.states.uiEnabled = false;
});
$rootScope.$on(CONN_EVENTS.socketConnLost, function() {
dataConnection.states.uiEnabled = false;
});
}
which sets the states based on events. This events are emitted in a different service/factory which handles the socket events.
I tried many approaches to sync these values with my controller. It kinda worked with a manual $scope.$apply(), but sometimes it gave me the apply already in progress error and since its very bad practice, I decided to not use it.
I ended up with this solution in my controller:
.controller("MainCtrl", function($scope, DataConnection) {
$scope.$watch(function() {
return DataConnection.states.uiEnabled
}, function(uiEnabled) {
$scope.uiEnabled = uiEnabled;
}, true);
Which doesn't want to work somehow. When the socket get disconnected (the server gets shut down), the ui won't update. After interaction (e.g. trigger a popover), the ui will update as expected.
Is there anything I'm missing? Or something else I can try?
OK this is not a solution to get $watch working but instead just a workaround that "might" get your first approach using events to work without throwing a digest already in progress error. So instead of using $scope.$apply() enclose changes you make inside $timeout(function()) instead like this
$timeout(function(){
// do whatever changes you want here
}) // no timeout value
NOTE that there is no timeout actually added above. Why i think this might work is because a $timeout automatically applies whatever changes you made once script inside completes execution (ie, its like a $scope.$spply() that won't throw a digest cycle already in progress error). I am not sure about this but just give it a try.
I'd simply suggest you to use $scope.$applyAsync(), which means that if an apply in progress, it will do it on the next $digest only.
To me is it OK your approach, to watch a function that returns an object to be observed, I don't know what could be the problem, but I would use callbacks:
.factory('DataConnection', function(CONN_EVENTS, socket, $rootScope) {
var dataConnection = {};
dataConnection.states={
_uiEnabledBackingField: false,
// indicates if the user can interact with the ui
uiEnabled: function(val) {
if (typeof val !== 'undefined') {
this._uiEnabledBackingField = val;
this.callback(val);
return;
}
return this._uiEnabledBackingField;
},
onUiEnabled: function(cb){
if (cb) {
this.callback = cb;
}
}
};
});
And then in my controller:
.controller("MainCtrl", function($scope, DataConnection) {
dataConnection.states.onUiEnabled(function(uiEnabled){
$scope.uiEnabled = uiEnabled;
});
});
I did some research and this is my conclusion:
The $apply will trigger in three different cases (Exploring Angular 1.3: Go fast with $applyAsync) In my case (where I have to update the DOM after a socket event), the $apply has to be triggered manually.
This example is also found in the Angular Wiki:
If you're creating an AngularJS service (such as for sockets) it should have a $scope.$apply() anywhere it fires a callback.
I tried to trigger the $apply in the socket service already (instead of the controller), which worked way better and didn't give me the "$apply already in progress error".

Don't execute a $resource request in angular when there is already a request running

I use a interval of 10 seconds for sending a request to get the most recent data:
var pollInterval = 10000;
var poll;
poll= $interval(function()
{
getNewestData();//$resource factory to get server data
}, pollInterval );
This works fine voor 99% of the time, but if the internet speed is really slow(I have actually experienced this), It will send the next request before the current is finished. Is there a way to just skip the current interval request if the previous one is still busy? Obsiously I could just use booleans to keep the state of the request, but I wonder if there is a better(native to angular) way of doing this?
Use the $resolved property of the Resource object to check if the previous operation is done.
From the Docs:
The Resource instances and collections have these additional properties:
$promise: the promise of the original server interaction that created this instance or collection.
$resolved: true after first server interaction is completed (either with success or rejection), false before that. Knowing if the Resource has been resolved is useful in data-binding.
$cancelRequest: If there is a cancellable, pending request related to the instance or collection, calling this method will abort the request.
-- AngularJS ngResource $resource API Reference.
How about making the request, then waiting for that to complete and then wait 10 seconds before making the same request again? Something along this line:
var pollInterval = 10000;
var getNewestData = function () {
// returns data in promise using $http, $resource or some other way
};
var getNewestDataContinuously = function () {
getNewestData().then(function (data) {
// do something with the data
$timeout(function () {
getNewestDataContinuously();
}, pollInterval);
});
};
getNewestData is the function that actually makes the request and returns the data in a promise.
And once data is fetched, a $timeout is started with timer as 10 seconds which then repeats the process.

How to use multiple $timeout.flush() and $httpBackend.flush() calls in a Jasmine spec for AngularJS (with PhantomJS) without getting $digest error

I am trying to write a unit test for a AngularJS service with Karma and Jasmine using the PhantomJS browser. The service that I want to test uses nested promises (for reading a IndexedDB database, getting responses from the server and writing the response back to the database).
The same has to be done when writing to the database (write local db, post/put to server, update local db with server id). The tests I am writing all work except for the tests for handling a loss of connection to the server.
When that happens the sync() method sets the online status to false and creates a $timeout that repeatedly checks for a connection by doing a recursive call. If the connection is up again, all unsynced data will be synced (and the promises of the corresponding sync() calls will be resolved).
describe('Harmonized', function() {
var ... // setting all 'global' variables
[...]
beforeEach(function(){
// Address that the service sends a ping to check if online again
pingGet = backend.when('GET', 'http://localhost:2403');
pingGet.respond(0);
testTablePut121 = backend.when('PUT', 'http://localhost:2403/test_table/121');
testTablePut121.respond(function(method, url, data, headers) { ... } // returns 200
[...]
}
[...]
it('should try to sync a SAVED element but should fail because of missing server connectivity', function() {
var list;
var timeoutCounter = 0;
_checkOnline = harmonized._checkOnline; // harmonized is the service that has to be testet
harmonized._checkOnline = function () {
// only call the original function two times
if (timeoutCounter++ < 2) {
_checkOnline();
// flush the $timeout and the $httpBackend
timeout.flush(2000);
backend.flush(1);
// this also gives me an error because of the $digest already in progress
console.log('after tick');
}
};
all.getList().then(function(entries) {
list = entries;
testTablePut121.respond(0);
pingGet = backend.expect('GET', 'http://localhost:2403');
pingGet.respond(0);
spyOn(list[0], 'sync').and.callThrough();
list[0].save().then(
// resolve is called when the server call was successfull
function() {
expect(list[0]._synchronized).toBeTruthy();
expect(list[0].sync.calls.count()).toBe(2);
console.log('done');
},
// reject is called when the server response an error other than 0 (which is not the case in this test spec)
null,
// notify is called when the local db save is made
function(){
expect(list[0].sync.calls.count()).toBe(1);
expect(list[0]._synchronized).toBeFalsy();
});
backend.flush(1);
});
backend.flush(1);
});
When the backend.flush() function is called a second time (to flush the http request made in the save() function, I get an error telling me that the $digest is already in progress. Omitting the line, doesn't flush the request (and a also get an error because of that).
Is there a way to flush everything at the right time without getting the $digest error?

Fixing waits in Protractor 2.0 with ExpectedCondition

Due to Protractor 2.0's breaking changes, I have to refactor my at() function. I use this on non-angular pages to both wait for the page to load, and return true, so it's expectable (if that's a word).
Because 2.0 no longer returns a promise from a locator, it's breaking my wait (wait fires immediately). Thus, I've turned to the new ExpectedConditions... which works fine. My question is, am I using this correctly, or is there a better solution?
Page object locator:
this.pageLoaded = $('div#splash[style="display: none;"]');
Works in Protractor 1.8.0 (breaks in 2.0.0):
this.at = function() {
var that = this;
return browser.wait(function() {
return browser.isElementPresent(that.pageLoaded);
});
};
My working solution for Protractor 2.0.0:
this.at = function() {
var that = this;
return browser.wait(function() {
return protractor.ExpectedConditions.presenceOf(that.pageLoaded);
});
};
And for example, I would call this like so:
expect(mainPage.at()).toBeTruthy();
It looks like there was another breaking change in the Protractor 2.0 changelog, referring to a problem in WebDriver 2.45 that makes the wait parameter required:
Due to changes in WebDriverJS, wait without a timeout will now default to waiting for 0 ms instead of waiting indefinitely.
Before:
browser.wait(fn); // would wait indefinitely
After:
browser.wait(fn, 8000) // to fix, add an explicit timeout
This will be reverted in the next version of WebDriverJS.
In our Protractor tests, we always refer to a common helper class var helper = require("..\helpers.scenario.js"). To fix this problem, we just wrapped browser.wait in the helper class and passed in a timeout.
wait: function(f) {
return browser.wait(f, 30000);
},
We then used find-replace to change from browser.wait to helper.wait.

Resources