Mocha not respecting timeout or done callback when running CasperJS tests - javascript
So here is the issue that I'm trying to solve with casperjs and mocha. I'm trying to test an element's text value on a page to see if its updating over a period of... 5-10 seconds. The idea being I grab the value, push it in to an array, wait 500ms and repeat until the array has 20 items. Thats about 10 seconds. Then run underscore/lodash's _.uniq function on the array and test that the array length is > 1.
The problem I'm running in to is that mocha is not waiting for this to complete because stating the test is a success/failure. I thought I could increase mocha's timeout but this has not made any difference. Please see the code below. I've commented it for readability.
it('has elements whose values update', function () {
// 20 seconds, which should be plenty of time
this.timeout(20000);
casper.then(function() {
// The test array
var values = [],
// So we can stop the intervals
intervalId;
function getValue () {
// Grab element's text value
var value = casper.evaluate(function () { return $('#element').text(); });
// Push in to our test array
values.push(value);
// 20 * 500ms == 10 seconds or 10000ms
// When set to 500ms, this block never runs. The test passes before it has a chance to
if (values.length === 20) {
// Stop it from checking the value any further
clearInterval(intervalId);
// Test to see we've had more than one unique value in the 10 seconds
expect(_.uniq(values).length).to.be.gt(1);
}
}
// Wait for the value on the page to populate
// It defaults to '-' when the page loads
casper.waitFor(function () {
return this.evaluate(function () {
return $('#element').text() !== '-';
});
// Start the check with a delay of 500ms between each check
}, function then() {
intervalId = setInterval(getValue, 500);
});
});
});
With the interval value set at 500ms I get 2-3 element values in values before mocha moves on to the next test. Even odder is when I console.log(values) they are printing on screen AFTER mocha as determined the test passed. The reason is that values.length never gets to 10 so the expect call is never called. The test is assumed to be passing. Here is the test output at 500ms interval:
Dashboard
✓ has elements whose values update (202ms)
Values: ["20,832,022"]
Values: ["20,832,022","20,832,372"]
Values: ["20,832,022","20,832,372","20,832,722"]
✓ has the page title of leads (41ms)
2 passing (11s)
It passes even though there isn't 20 items. It never checks it due to a timeout somewhere. Here is the output with 50ms interval:
Dashboard
✓ has elements whose values update (341ms)
Values: ["20,400,667"]
Values: ["20,400,667","20,400,718"]
Values: ["20,400,667","20,400,718","20,400,718"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871","20,400,871"]
Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871","20,400,871","20,400,922"]
Final Values: ["20,400,667","20,400,718","20,400,718","20,400,769","20,400,769","20,400,820","20,400,820","20,400,871","20,400,871","20,400,922"]
✓ has the page title of leads (41ms)
2 passing (8s)
I get more with the 50ms but that's only a half second of testing. Some of the other values on the page take longer to update to this is not viable.
I've tried passing the done callback to the it statement but mocha ignores it and doesn't wait for it to be called.
Is this a limitation of the tools or am I using them wrong?
I have tried using the done callback using the method below.
it('has elements whose values update', function (done) {
and
expect(_.uniq(values).length).to.be.gt(1);
done();
It still ignores that I've marked the test as async. At 500ms it still passes without getting to the if statement or done call. At 50ms it throws this error:
done() called multiple times
I am using mocha-casperjs. Could this be affecting it?
It seems that mocha-casperjs doesn't use the default done. It knows that the test step is complete because it uses CasperJS' control flow. In your case, you break out of the control flow by calling getValue through a setInterval.
It would be better to refactor your code to use recursive calls to getValue like this:
function getValue () {
// Grab element's text value
var value = this.evaluate(function () { return $('#element').text(); });
// Push in to our test array
values.push(value);
// 20 * 500ms == 10 seconds or 10000ms
// When set to 500ms, this block never runs. The test passes before it has a chance to
if (values.length === 20) {
// Test to see we've had more than one unique value in the 10 seconds
expect(_.uniq(values).length).to.be.gt(1);
} else {
this.wait(500, getValue);
}
}
// Wait for the value on the page to populate
// It defaults to '-' when the page loads
casper.waitFor(function () {
return this.evaluate(function () {
return $('#element').text() !== '-';
});
// Start the check with a delay of 500ms between each check
}, function then() {
this.wait(500, getValue);
});
This makes getValue a casper step.
Another solution without much refactoring is letting a second waitFor run along side of the broken control flow. This needs a semi-global variable someGlobalVariable. Maybe the intervalId can be used for this, but it's probably better to use someGlobalVariable = false; at the top.
intervalId = setInterval(getValue, 500);
this.waitFor(function check(){
return someGlobalVariable;
}, function then(){
// do something else
}, null, 20000);
and let it stop with
expect(_.uniq(values).length).to.be.gt(1);
someGlobalVariable = true;
Related
Testing how many times a method was called from another method's return statement in AngularJS
Newbie to JS. I have this function. First, it checks for data in cache. If it's not there, it calls backend for this data and saves it to cache. function doSomething() { return getCachedData() .catch(function() { return getData() .then(saveDataToCache); }); } I need to test how many times cache and backend are called on the first and the second time the method doSomething is executed. Here is my test: it('should test', function() { spyOn(this.service, 'doSomething').andCallThrough(); spyOn(this.service, 'getCachedData').andCallThrough(); spyOn(this.service, 'getData').andCallThrough(); this.service.doSomething(); this.$rootScope.$apply(); expect(this.service.doSomething.callCount).toBe(1); expect(this.service.getCachedData.callCount).toBe(1); expect(this.service.getData.callCount).toBe(1); this.service.doSomething(); this.$rootScope.$apply(); expect(this.service.doSomething.callCount).toBe(2); expect(this.service.getCachedData.callCount).toBe(2); expect(this.service.getData.callCount).toBe(1); }); However, I am getting an error saying that call count for getCachedData and getData is always 0, both the first and the second time. Changing this.$rootScope.$apply(); to this.$rootScope.$digest(); doesn't improve anything. I also tested it manually and everything seems to be working fine. I also noticed that if I change the function doSomething as below then the counts for getCachedData are 2 & 2, which is correct. function doSomething() { this.getCachedData(); return getCachedData() .catch(function() { return getData() .then(saveDataToCache); }); }
create an object. for example: system_metrics ={ cache_calls:0, backend_calls:0, reset_cache:function(){this.cache_calls=0;}, reset_backend_calls:function(){this.backend_calls=0;}, add_cache_call:function(){this.cache_calls++;} add_backend_call:function(){this.cache_calls++;} } in function getCachedData(){ //add this line system_metrics.add_cache_call(); } in function getData(){ //add this line system_metrics.add_backend_call(); } I need to test how many times cache and backend are called on the first and the second time the method doSomething is executed in function doSomething(){ if(system_metrics.cache_calls === 1){ //the first time the method doSomething is executed } if(system_metrics.cache_calls === 2){ //the second time the method doSomething is executed } if(system_metrics.backend_calls === 1){ //the first time the method doSomething is executed } if(system_metrics.backend_calls === 2){ //the second time the method doSomething is executed } } at anytime during execution.. lets say at the end of the day you can now check system_metrics.cache_calls to give you total number of cache calls for the day.. thats if you never resetted the cache calls at anytime during execution you can clear the number of cache calls with system_metrics.reset_cache_calls if your question is to check how many times cache was called when doSomething runs for the first time or second time. if your question is to check how many times backend was called when doSomething runs for the first time or second time. add the following to your system_metrics do_something_calls add_do_something_call reset_do_something_call & add add_do_something_call to your do_something function i think you get the picture with this approach you can track any metric that you want anytime that you want to
Rxjs increment loop counter after limit
I have sample code of observable to print some values sample4Observable() { var observable = Observable.create(function (observer) { for (var i = 1; i <= 4; i++) { if (i <= 3) { observer.next(i); } if (i === 4) { setTimeout(() => { observer.next(i); observer.complete(); }, 5000); } } }); console.log('just before subscribe'); observable.subscribe({ next: x => console.log('got value ' + x), error: err => console.error('something wrong occurred: ' + err), complete: () => console.log('done'), }); console.log('just after subscribe'); } the output is just before subscribe got value 1 got value 2 got value 3 just after subscribe got value 5 done My question is my loop is end when the value reached to 4. So the next value will be 4 instead of 5. why RxJS skip the value 4? In order to get the value for 4 i can change my code like below. But in above scenario I am little bit confusing. Can anyone explain setTimeout((i) => { observer.next(i); observer.complete(); }, 5000,i);
It isn't relate anything with RxJS anyway, this is the javascript execution happens. Since you're looping up until i<=4, count is reaching to 4 and once i==4 you're firing up a function with setTimeout. So as soon setTimeout gets registered, it doesn't get evaluate directly. setTimeout is async event, browser place it inside something called as Event Loop. And wait till all synchronous execution of javascript get over. Since you had i++ in for loop i value becomes 5. Afterwards all synchronous execution completion. Javascript visit Event Loop to evaluate asynchronous operation(they can be events as well). That time your setTimeout evaluate and it wait till 5000(5 sec), then it gets i value as 5 as expected. The point is even if you set timeout 0 instead of 5000 you will get same result i.e. i = 5. Since javascript is single threaded, it always evaluate synchronous code followed by asynchronous code. There you could pass i value as parameter to setTimeout function while calling it. Provided i value will be available inside the scope of function directly, so that wouldn't checking the global value. Check MSDN Docs here setTimeout((i) => { observer.next(i); observer.complete(); }, 5000,i);
When you set a timeout, the inner function will be executed after the time has passed. When the loop reaches i === 4 it starts the timeout, the inner function is not yet executed. The loop goes one more round incrementing i to 5, then the loop terminates because the condition i <= 4 is not met anymore. After the 5 seconds have passed (which is after i has been incremented to 5), the timeout is fulfilled and the function is executed. Although when the timeout was started i === 4 was true, now when the timeout function is executed, it is i === 5. This is not directly linked to RxJS. This simpler example should illustrate it. Just run it in your browser console: let i = 0; setTimeout(() => console.log(`the value of i is ${i}`), 500); i++;
Settimeout not returning instantly
I was practicing with callback functions and this question is one I can't seem to figure out. function foo () { var data = 10; bar(function (players) { data = players; }); return data; } function bar (callback) { setTimeout(callback, 0); } var result = foo(); I expected result to be undefined since data = players and there is nothing passed in as players. Since the setTimeout function uses 0 as the delay, shouldn't it run first, and then return data? I looked at the MDN page and there seems to be information on throttling for nested timeouts to >=4ms. Does this also apply in this case?
Since the setTimeout function uses 0 as the delay, shouldn't it run first, and then return data? No, even with a delay of 0, the callback passed to setTimeout is scheduled to run in the next tick of the event loop. In other words, the callback is guaranteed to be executed after the current execution ran to completion.
Protractor waits during ignore synchronization, browser implicitTimeout vs browser.wait timeout
I have an application that kicks off a $timeout when a button is clicked, so I must work with ignoreSynchronization set to true. During this time, I need to wait for elements to be added to the page, and I am experiencing some interesting behavior during the waits: The wait timeout passed in during browser.wait(element, timeout, error message) does nothing. The only wait that matters is the implicitTimeout set on the browser. On top of that, the ENTIRE implicit timeout will be used. If the element is found to be present, it will continue checking until the end of the timeout. This means the tests will always run slowly, with the max time given. describe('Cool Page', () =>{ beforeEach(function(){ browser.ignoreSynchronization = true; return browser.sleep(250); }); afterEach(function(){ browser.ignoreSynchronization = false; return browser.sleep(250); }); it('can open menu with timeout', function(){ // No timeout at this point coolPage.wait.ellipsesIcons().then(() =>{ // Clicking the ellipses icons kicks off a $timeout of 100 seconds coolPage.ellipsesIcons.click().then(() =>{ coolPage.wait.dropdownOptions().then(() => { expect(coolPage.dropdownOptions.getText()).toContain('Option 1'); }); }); }); }); }) . export = new CoolPage; class CoolPageextends PageObject { private elements; constructor(){ super(); ... // Lots of other stuff this.initWait(); } ... // Initializing elements and other things private initWait() { this.wait = { ellipsesIcons: () => { // Timeout of 5 seconds will be used - regardless of isPresent resolving as true or false, the entire 5 seconds will be used browser.manage().timeouts().implicitlyWait(5000); // The 2 milliseconds passed in here does nothing at all browser.wait(element(by.css('.fa-ellipses-h')).isPresent(), 2, 'Ellipses Icon(...) was not present in time'); // Must reset implicit wait back to the original 25 seconds it was set too in the conf browser.manage().timeouts().implicitlyWait(25000); return browser.sleep(150); }, dropdownOptions: () => { // This two seconds wait WILL be used browser.manage().timeouts().implicitlyWait(2000); // This five second wait WILL NOT be used browser.wait(element(by.css('[name="change-status"]')).isPresent(), 5000, 'Options actions menu item was not present in time'); browser.manage().timeouts().implicitlyWait(25000); return browser.sleep(150); }, } } The timeouts passed in through browser.wait have no effect here. My questions are: What does the browser.wait timeout do? When is the implicit wait used? I thought it was only waiting for pages to load Is there any way to pass in a timeout that will actually be used? If not, is there any way for the wait to exit as soon as the condition is met, rather than using the entire timeout?
Try to use expected conditions with browser wait. Take a look at it('should wait for a series of periodic increments' for an example using function textToBePresentInElement. To check if an element is present, I might try out visibilityOf or presenceOf.
Watching setTimeout loops so that only one is running at a time
I'm creating a content rotator in jQuery. 5 items total. Item 1 fades in, pauses 10 seconds, fades out, then item 2 fades in. Repeat. Simple enough. Using setTimeout I can call a set of functions that create a loop and will repeat the process indefinitely. I now want to add the ability to interrupt this rotator at any time by clicking on a navigation element to jump directly to one of the content items. I originally started going down the path of pinging a variable constantly (say every half second) that would check to see if a navigation element was clicked and, if so, abandon the loop, then restart the loop based on the item that was clicked. The challenge I ran into was how to actually ping a variable via a timer. The solution is to dive into JavaScript closures...which are a little over my head but definitely something I need to delve into more. However, in the process of that, I came up with an alternative option that actually seems to be better performance-wise (theoretically, at least). I have a sample running here: http://jsbin.com/uxupi/14 (It's using console.log so have fireBug running) Sample script: $(document).ready(function(){ var loopCount = 0; $('p#hello').click(function(){ loopCount++; doThatThing(loopCount); }) function doThatOtherThing(currentLoopCount) { console.log('doThatOtherThing-'+currentLoopCount); if(currentLoopCount==loopCount){ setTimeout(function(){doThatThing(currentLoopCount)},5000) } } function doThatThing(currentLoopCount) { console.log('doThatThing-'+currentLoopCount); if(currentLoopCount==loopCount){ setTimeout(function(){doThatOtherThing(currentLoopCount)},5000); } } }) The logic being that every click of the trigger element will kick off the loop passing into itself a variable equal to the current value of the global variable. That variable gets passed back and forth between the functions in the loop. Each click of the trigger also increments the global variable so that subsequent calls of the loop have a unique local variable. Then, within the loop, before the next step of each loop is called, it checks to see if the variable it has still matches the global variable. If not, it knows that a new loop has already been activated so it just ends the existing loop. Thoughts on this? Valid solution? Better options? Caveats? Dangers? UPDATE: I'm using John's suggestion below via the clearTimeout option. However, I can't quite get it to work. The logic is as such: var slideNumber = 0; var timeout = null; function startLoop(slideNumber) { //... code is here to do stuff here to set up the slide based on slideNumber... slideFadeIn() } function continueCheck() { if (timeout != null) { // cancel the scheduled task. clearTimeout(timeout); timeout = null; return false; } else { return true; } }; function slideFadeIn() { if (continueCheck){ // a new loop hasn't been called yet so proceed... $mySlide.fadeIn(fade, function() { timeout = setTimeout(slideFadeOut,display); }); } }; function slideFadeOut() { if (continueCheck){ // a new loop hasn't been called yet so proceed... slideNumber=slideNumber+1; $mySlide.fadeOut(fade, function() { //... code is here to check if I'm on the last slide and reset to #1... timeout = setTimeout(function(){startLoop(slideNumber)},100); }); } }; startLoop(slideNumber); The above kicks of the looping. I then have navigation items that, when clicked, I want the above loop to stop, then restart with a new beginning slide: $(myNav).click(function(){ clearTimeout(timeout); timeout = null; startLoop(thisItem); }) If I comment out 'startLoop...' from the click event, it, indeed, stops the initial loop. However, if I leave that last line in, it doesn't actually stop the initial loop. Why? What happens is that both loops seem to run in parallel for a period. So, when I click my navigation, clearTimeout is called, which clears it.
What you should do is save the handle returned by setTimeout and clear it with clearTimeout to interrupt the rotator. var timeout = null; function doThatThing() { /* Do that thing. */ // Schedule next call. timeout = setTimeout(doThatOtherThing, 5000); } function doThatOtherThing() { /* Do that other thing. */ // Schedule next call. timeout = setTimeout(doThatThing, 5000); } function interruptThings() { if (timeout != null) { // Never mind, cancel the scheduled task. clearTimeout(timeout); timeout = null; } } When a navigation element is clicked simply call interruptThings(). The nice part is that it will take effect immediately and you don't need to do any polling or anything else complicated.