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
I want to test my scheduleJob from the node-schedule package. With sinon useFakeTimers() i can skip the time. Unfortunately my scheduler doesn't seem to 'believe' the fake time. When i set the scheduler to 1 minute it works perfectly, so i know that it works. I also tried to set the fake time just a minute before the call, also doesn't work.
Sinon:
let clock = sinon.useFakeTimers(moment().toDate());
clock.tick(60*60*23);
And my scheduledJob:
let job_2 = schedule.scheduleJob(new Date(date.toISOString()), function (user) {
console.log("get's here..");
if (user.status === 'pending') {
user.remove(function (error) {
if (!error) {
mid.addEvent({
action: 'deleted_user'
}, {
name: user.name
}, function (error) {
if (error) {
console.log("error: " + error);
}
});
}
});
}
}.bind(null, user));
Has anyone any idea?
#MPasman
What does your test look like? The node-schedule authors test their code with sinon so I don't see why this would be an issue.
see this for examples
I was able to test my job in angular 6 using jasmine's fakeAsync like so:
it('should call appropriate functions when cronJob is triggered, bad ass test',
fakeAsync(() => {
const channelSpy = spyOn(messengerService, 'createChannels');
const listenerSpy = spyOn(messengerService, 'createListener');
const messengerSpy = spyOn(messengerService.messenger,
'unsubscribeAll');
// reset your counts
channelSpy.calls.reset();
listenerSpy.calls.reset();
messengerSpy.calls.reset();
messengerService.cronJob.cancel();
// run cron job every second
messengerService.cronJobTime = '* * * * * *';
messengerService.scheduleMyJsCronJob();
tick(3150);
messengerService.cronJob.cancel();
expect(channelSpy.calls.count()).toEqual(3);
expect(listenerSpy.calls.count()).toEqual(3);
expect(messengerSpy.calls.count()).toEqual(3);
}));
The actual function in the service I am testing:
scheduleMyJsCronJob(): void {
this.cronJob = scheduleJob(this.cronJobTime, () => {
// logic to unsubscribe at midnight
this.messenger.unsubscribeAll();
// create new channels
this.createChannels(
this.sessionStorageService.getItem('general-settings'));
this.createListener(
this.sessionStorageService.getItem('general-settings'));
});
}
The basic strategy is:
1) spy on functions that your cronJob should call when scheduled
2) cancel all previous Jobs if any (you could also do this in an afterEach, which runs after each unit test). Node-schedule also offers a function called scheduledJobs which returns an object with all the functions scheduled. You can loop over it and cancel them all)
3) set the cronTime to run every second for easier testing
4) tick the clock a little over a second (in my case i did a little over 3 seconds)
5) cancel the job to stop it (otherwise it will keep running and your test will timeout).
6) expect your function(s) to be called x amount of times
Hope this helps you.
Basically sinon.useFakeTimers() method replaces setInterval and setTimeout asynchronous methods with a built in synchronous methods that you can control using clock.tick()
clock.tick(time) method invokes the replaced synchronous methods and basically forwards the time by time specified.
node-schedule on the other hand is a cron-like job scheduler so it doesn't use setInterval and setTimeout
https://www.npmjs.com/package/node-schedule
https://sinonjs.org/releases/v1.17.6/fake-timers/
Hope this makes it a little more clear
tl;dr: The initial question was "How to trigger a callback every digest cycle?" but the underlying question is much more interesting and since this answers both, I went ahead and modified the title. =)
Context: I'm trying to control when angular has finished compiling the HTML (for SEO prerendering reasons), after resolving all of its dependencies, ngincludes, API calls, etc.
The "smartest" way I have found so far is via checking whether digest cycles have stabilized.So I figured that if I run a callback each time a digest cycle is triggered and hold on to the current time, if no other cycle is triggered within an arbitrary lapse (2000ms), we can consider that the compilation has stabilized and the page is ready to be archived for SEO crawlers.
Progress so far: I figured watching $rootScope.$$phase would do but, while lots of interactions should trigger that watcher, I'm finding it only triggers once, at the very first load.
Here's my code:
app.run(function ($rootScope) {
var lastTimeout;
var off = $rootScope.$watch('$$phase', function (newPhase) {
if (newPhase) {
if (lastTimeout) {
clearTimeout(lastTimeout);
}
lastTimeout = setTimeout(function () {
alert('Page stabilized!');
}, 2000);
}
});
Solution: Added Mr_Mig's solution (kudos) plus some improvements.
app.run(function ($rootScope) {
var lastTimeout;
var off = $rootScope.$watch(function () {
if (lastTimeout) {
clearTimeout(lastTimeout);
}
lastTimeout = setTimeout(function() {
off(); // comment if you want to track every digest stabilization
// custom logic
}, 2000);
});
});
I actually do not know if my advice will answer your question, but you could simply pass a listener to the $watch function which will be called on each iteration:
$rootScope.$watch(function(oldVal, newVal){
// add some logic here which will be called on each digest cycle
});
Have a look here: http://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
I'm reloading a module this way:
require('./module.js'); // require the module
delete require.cache('/module.js'); // delete module from cache
require('/module.js'); // re-require the module
But there is a problem if the module contains something like this:
setInterval(function(){
console.log('hello!');
}, 1000);
Every time I reload the module a new setInterval is called, but the last one is NOT closed.
Is there any way to know about each module's (long) running functions so I can stop them before I require it again? Or any suggestions how can I make this work?
I'm open to any crazy ideas.
This is just a wild guess but you may be able to load the module within a domain.
When you are done use domain.dispose() to clear the timers:
The dispose method destroys a domain, and makes a best effort attempt
to clean up any and all IO that is associated with the domain. Streams
are aborted, ended, closed, and/or destroyed. Timers are cleared.
Explicitly bound callbacks are no longer called. Any error events that
are raised as a result of this are ignored.
I would simply set a reference to the interval and expose a method in order to stop it like:
var interval = setInterval(function () {
console.log('hello');
}, 1000);
var clearInt = clearInterval(interval);
I dont think you can hook into any events as you are simply deleting a reference. If it doesnt exist anymore it reloads. Before you do this call the clearInt function.
You could create an IntervalRegistry in your main application:
global.IntervalRegistry = {
intervals : {},
register : function(module, id) {
if (! this.intervals[module])
{
this.intervals[module] = [];
}
this.intervals[module].push(id);
},
clean : function(module) {
for (var i in this.intervals[module])
{
var id = this.intervals[module][i];
clearInterval(id);
}
delete this.intervals[module];
}
};
In your module, you would register the interval created there:
// module.js
IntervalRegistry.register(__filename, setInterval(function() {
console.log('hello!');
}, 1000));
When it's time to clean up, call this:
var modulename = '/full/path/to/module.js'; // !!! see below
IntervalRegistry.clean(modulename);
delete require.cache[modulename];
Remember that modules are stored with their full filename in require.cache.
I have a 'service module' that listens for periodic heartbeats (over socket.io) and then if a heartbeat is missed publishes an event on an event bus (Backbone.Events). Also if the heart beat resumes later, it publishes an event.
My unit test looks something like this:
describe('Heartbeat service', function() {
var HeartbeatService;
var heartbeatInterval = 1;
//the server is expected to send heartbeats FASTER actually.
//otherwise we risk false alarms
jasmine.require(['services/HeartbeatService'], function(Service) {
HeartbeatService = Service;
});
/*Since these tests use setInterval, it'd be erroneous to allow
mocks to be overwritten by another test. Thus not using beforeEach()*/
function createMocks(){
var mockEventAggregator = jasmine.createSpyObj('Events',['trigger']);
return {
target : new HeartbeatService(heartbeatInterval, mockEventAggregator),
ea : mockEventAggregator
};
}
it('should raise an event on the FIRST missed beat', function() {
var mocks = createMocks();
mocks.target.start();
setTimeout(function(){
expect(mocks.ea.trigger).toHaveBeenCalled();
}, 2);
});
//..other similar tests
});
My dislike for this test is that if it fails then it is likely that the reporting tool will list the failures under a different test - since the actual assertion will occur outside the it() function.
You could use SinonJSs fake timer to set the clock 2 ticks forward instead of using setTimeout.
I am not too familiar with the specifics of every javascript implementation on each browser. I do know however that using setTimeout, the method passed in gets called on a separate thread. So would using a setTimeout recursively inside of a method cause its stack to grow indefinitely until it causes a Stack Overflow? Or would it create a separate callstack and destroy the current frame once it goes out of focus? Here is the code that I'm wondering about.
function pollServer()
{
$.getJSON("poll.php", {}, function(data){
window.setTimeout(pollServer, 1000);
});
}
window.setTimeout(pollServer, 0);
I want to poll the server every second or so, but do not want to waste CPU cycles with a 'blocking loop' - also I do not want to set a timelimit on how long a user can access a page either before their browser dies.
EDIT
Using firebug, I set a few breakpoints and by viewing the "Script -> Stack" panel saw that the call stack is literally just "pollServer" and it doesn't grow per call. This is good - however, do any other implementations of JS act differently?
I am not sure if it would create a stack overflow, but I suggest you use setInterval if the period is constant.
This is how prototype implements its PeriodicalExecuter.
// Taken from Prototype (www.prototypejs.org)
var PeriodicalExecuter = Class.create({
initialize: function(callback, frequency) {
this.callback = callback;
this.frequency = frequency;
this.currentlyExecuting = false;
this.registerCallback();
},
registerCallback: function() {
this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},
execute: function() {
this.callback(this);
},
stop: function() {
if (!this.timer) return;
clearInterval(this.timer);
this.timer = null;
},
onTimerEvent: function() {
if (!this.currentlyExecuting) {
try {
this.currentlyExecuting = true;
this.execute();
} finally {
this.currentlyExecuting = false;
}
}
}
});
setTimeout executes sometime later in the future in the event pump loop. Functions passed to setTimeout are not continuations.
If you stop and think about it, what useful purpose or evidencec is there that the call stack is shared by the timeout function.
If they were shared what stack would be shared from the setter to the timeout function ?
Given the setter can do a few returns and pop some frames - what would be passed ?
Does the timeout function block the original thread ?
Does the statement after the setTimeout function execute after the timeout executes ?
Once you answer those questions it clearly becomes evident the answerr is NO.
setTimeout does not grow the callstack, because it returns immediately. As for whether your code will run indefinitely in any browser, I'm not sure, but it seems likely.
take a look at the jQuery "SmartUpdater" plugin.
http://plugins.jquery.com/project/smartupdater
Following features are available:
stop() - to stop updating.
restart() - to start updating after pause with resetting time interval to minTimeout.
continue() - to start updating after pause without resetting time interval.
status attribute - shows current status ( running | stopping | undefined )
updates only if new data is different from the old one.
multiplies time interval each time when data is not changed.
handle ajax failures by stopping to request data after "maxFailedRequests".