sinon.useFakeTimers Doesn't Fire Timeout - javascript

I'm trying to test my timeout functionality using Sinon and CasperJS. This page is showing on a digital sign, so it's not your typical web page - it has a very long lifetime, hence the high timeout value.
Here's the relevant code that I'm trying to test:
RiseVision.Image = (function () {
// Private
function startTimer() {
setTimeout(function() {
var img = document.getElementById("image");
img.style.backgroundImage = "url(http://s3.amazonaws.com/images/logo-small.png?" + new Date().getTime() + ")";
}, 900000);
}
// Public
function ready() {
...
}
return {
"ready": ready
};
})();
I'm using CasperJS for my tests like so:
var e2ePort = system.env.E2E_PORT || 8099;
var url = "http://localhost:"+e2ePort+"/src/widget-e2e.html";
var clock;
casper.test.begin("Image Widget - e2e Testing", {
test: function(test) {
casper.start();
casper.thenOpen(url, function () {
test.assertTitle("Image Widget", "Test page has loaded");
});
casper.then(function () {
casper.waitFor(function waitForUI() {
return this.evaluate(function loadImage() {
// Wait for the background image to be set.
return document.getElementById("image").getAttribute("style") !== "";
});
},
function then() {
// Do some assertions here.
casper.waitFor(function waitForTimer() {
return this.evaluate(function expireTimer() {
clock = sinon.useFakeTimers();
clock.tick(900000);
return document.getElementById("image").getAttribute("style") !==
"background-image: url(http://s3.amazonaws.com/images/logo-small.png);";
});
},
function then() {
// More assertions here.
});
});
});
casper.run(function runTest() {
test.done();
});
}
});
I know this function is executing because I can successfully log messages from inside of it, but it's just not firing my timer. And it doesn't seem to make any difference if I make the startTimer function public.
Any ideas?
Thx.
EDITED - Updated to include more code.

You probably mean to use a single clock instance and not create one every 50 ms (that's what waitFor does).
casper.evaluate(function() {
window._fakeClock = sinon.useFakeTimers();
});
casper.waitFor(function waitForTimer() {
return this.evaluate(function expireTimer() {
window._fakeClock.tick(900001);
return document.getElementById("image").getAttribute("style") !==
"background-image: url(http://s3.amazonaws.com/images/logo-small.png);";
});
},
function then() {
this.evaluate(function() {
window._fakeClock.restore();
});
// More assertions here.
});

Related

Get Status from PHP /w two ajax calls

Given two api endpoints in my php backend:
Long Function Endpoint: /_api/_long_function &
Status Endpoint: /_api/_status_long_function
In the LongFunction I write session data and in StatusFunction I read them and output them in json. I think it is not relevant to the question but I could show the session getting and setting as well if needed.
I now wanted to start the long function with an ajax call and then periodically request the status with a second ajax call.
The JS Code
$(document).on("click", "#submitSummoner", function () {
...INIT VARS...
/**
* Periodically check Session Status
*/
var to,
clearTime = false,
called = 0,
set_delay = 1000,
callout = function () {
console.log('Called SessionCheck ' + called);
$.get($url_session, {
my_data: my_data
})
.done(function (response) {
if (called === 0) {
console.log('Called LongFunction');
$.get($url, {
my_data, my_data
}).done(function (data) {
console.log('Finished Request');
if (to) {
clearTimeout(to);
clearTime = true;
}
}).fail(function () {
if (to) {
clearTime = true;
clearTimeout(to);
}
console.log('Failed Request');
});
}
called++;
// This is the output I need
console.log('Current Status: '+response.status);
})
.always(function () {
if (clearTime === false) {
to = setTimeout(callout, set_delay);
}
});
};
callout();
});
Unfortunately what happens now is this:
Console Output
1: Called SessionCheck 0
2: Called LongFunction (immediately after)
3: Current Status: Whatever (immediatley)
4: Called SessionCheck 1 (immidately)
LONG PAUSE UNTIL LONG FUNCTION FINISHED
5: Current Status: Finished
after Finished the setTimout gets cleared as expected.
But why does the the setTimeout not get called every 1000ms altough the long function takes 10000ms+?
It seems like the second SessionCheck waits for the Long Check to finish, I don't know why? Is it possible that the server "hangs", CPU/RAM seem fine during the longFunction. Anything else that could "lock" it
Even it the php session check function crashes, shouldn't the function still try it again after 1000ms?
Any hint appreciated!
Solution
I figured it out, the session wasn't saving properly in the LongFunction: https://codingexplained.com/coding/php/solving-concurrent-request-blocking-in-php
I've re-written your solution using jQuery promises to simulate the get requests, and it works as you desired. I believe this proves the issue is on the back-end, that the "short" get request is not resolving the 2nd time. Please provide the code that resolves that request.
var to,
clearTime = false,
called = 0,
set_delay = 1000,
callout = function () {
console.log('Called SessionCheck ' + called);
var shortGet = $.Deferred();
shortGet
.done(function () {
if (called === 0) {
var longGet = $.Deferred();
console.log('Called LongFunction');
longGet.done(function (data) {
console.log('Finished Request');
if (to) {
clearTimeout(to);
clearTime = true;
}
}).fail(function () {
if (to) {
clearTime = true;
clearTimeout(to);
}
console.log('Failed Request');
});
setTimeout(function() {
longGet.resolve();
}, 10000);
}
called++;
// This is the output I need
console.log('Current Status: still going');
})
.always(function () {
if (clearTime === false) {
to = setTimeout(callout, set_delay);
}
});
setTimeout(function() {
shortGet.resolve();
}, 200);
};
callout();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Elegant way of executing callback functions with params [duplicate]

wifiservice.js:
angular.module('app.WifiServices', [])
.factory('WifiService', function(){
var unique_array = angular.fromJson('[]');
function win_wifi(e){
alert("Success");
}
function fail_wifi(e){
alert("Error");
}
function connectWifi(wifi_ssid){
WifiWizard.connectNetwork(wifi_ssid, win_wifi, fail_wifi);
}
function listHandler(a){
var network_array = [];
for(var i=0; i<a.length; i++){
network_array.push("SSID: " + a[i].SSID + " Signal: " + a[i].level);
}
unique_array = network_array.filter(function(elem, pos) {
return network_array.indexOf(elem) == pos;
});
// alert("Wifi List Ready!");
}
function getScanResult(){
WifiWizard.getScanResults(listHandler, failNetwork);
}
function successNetwork(e){
window.setTimeout(function(){
getScanResult();
}, 3000);
}
function failNetwork(e){
alert("Network Failure: " + e);
}
window.setTimeout(function(){
WifiWizard.startScan(successNetwork, failNetwork);
}, 1000);
return {
list: function(){
return unique_array;
},
connectionToWifi: function(name){
connectWifi(name);
}
};
});
My whole controller:
app.controller('WifiController', ['$scope', 'WifiService', function($scope, WifiService) {
$scope.wifiList = [];
window.setTimeout(function() {
$scope.wifiList = WifiService.list();
// alert($scope.wifiList);
$scope.$apply();
}, 5000);
$scope.getList = function() {
$scope.wifiList = WifiService.list();
return $scope.wifiList;
}
$scope.connectWifi = function(name) {
WifiService.connectionToWifi(name);
}
$scope.checkin = function() {
$scope.getList()
.then(function(result) {
console.log(result);
});
}
}]);
What I am trying to do is, to call the $scope.getList(), which returns a list of the surrounding wifi SSIDs, then within $scope.checkin() I would like to process those data.
Since scanning needs some time I have to wait the getList function to finish, thats Why I am trying to use .then, but it gives me the error says on the title. Any ideas?
How to create a AngularJS promise from a callback-based API
To create an AngularJS promise from a callback-based API such as WifiWizard.connectNetwork, use $q.defer:
function connectWifi(wifi_ssid) {
var future = $q.defer();
var win_wifi = future.resolve;
var fail_wifi = future.reject;
WifiWizard.connectNetwork(wifi_ssid, win_wifi, fail_wifi);
return future.promise;
};
The above example returns a $q Service promise that either resolves or rejects using the callbacks from the API.
Well, I came up with something different:
var unique_array = [];
$scope.wifiList = [];
$ionicLoading.show({
template: "Scanning surrounding AP's..."
});
window.setTimeout(function() {
$scope.wifiList = WifiService.list();
// alert($scope.wifiList);
while ($scope.wifiList == []) {
console.log('Scanning...');
}
$scope.$apply();
$ionicLoading.hide();
}, 5000);
What I realize is that the scanning starts once I load the view. So, I added an IonicLoader to force the user to wait, and not be able to press any buttons till the scan is finished. So no function shall wait one another. Not quite code-wise correct, but it does what I need.

Several async calls in jasmine 1.3

I am trying to make two async call in one jasmine test suite.
The second call should wait until the first one is finished then make a call.
So simple setup:
it('async tests', function(){
runs(function() {
flagToServer = false;
flagFromServer = false;
value1 = 0;
value2 = 0;
dataToGet = "";
dataToSend = "";
setTimeout(function() {
flagFromServer = true;
data = getDataFromServer();
}, 500);
});
waitsFor(function() {
value1++;
return flag;
}, "The Value should be incremented", 750);
runs(function() {
expect(value1).toBeGreaterThan(0);
expect(data).toBe(expectedData);
});
//second async call to server;
runs(function() {
dataToSend = manipulate(dataToGet);
setTimeout(function() {
sendDataToServer(dataToSend);
flagToServer = true;
}, 500);
});
waitsFor(function() {
value2++;
return flagToServer;
});
runs(function() {
expect(value2).toBeGreaterThan(0);
expect(eventFromServer).toBe('got data');
});
});
Is it possible to do something above? I could not find a usage where several waitsFor/runs blocks are used together. Is it the right way to test several async calls one after another?
So after experimenting with our setup, I have got that it is indeed possible to use several runs and waitsfor in one suit. They will be executed in order they were defined.
run(function() {
runs(function() {
async();
})
waitsFor(function() {
return data;
}, "Data should have come", 2000);
runs(function() {
expect(data).not.toBeEmpty();
async(data);
})
waitsFor(function() {
return manipulatedData;
});
runs(function() {
expect(manipulatedData).not.toBeEmpty();
});
Each respective wiatsFor will wait for condition that should come from their respective run blocks or timeout.

Selenium Javascript Wait

I'm trying to use selenium-webdriver in Node to crawl Google finance pages. The driver.wait function does not appear to work as expected. I have set my mocha timeout to be 10 seconds and the driver.wait timeout be 9 seconds. The test passes about half of the time, but when it fails, it doesn't take anywhere near 9 seconds to fail - it actually fails in about 1 second and then takes another 8 before closing the test. I'm obviously missing something, but I've included the commented-out iterations of different things I've tried in order to make this work (including setTimeout). If anyone can help me see the error in my thinking, I would be much obliged. Here's the code:
(function () {
var assert = require("chai").assert;
var webdriver = require("selenium-webdriver");
var urlGoogleFinanceRoot = "https://www.google.com/finance";
describe("Selenium", function () {
it("should fetch a couple of pages and keep all of the content", function (done) {
var driver = new webdriver.Builder().withCapabilities(webdriver.Capabilities.chrome()).build();
webdriver.promise.controlFlow().on("uncaughtException", function (e) {
console.error("Error1: " + e);
});
// driver.get(urlGoogleFinanceRoot + "?q=BAC").then(function () {
// return setTimeout(function () {
// return driver.findElement(webdriver.By.xpath("//table[#class='snap-data']")).isDisplayed();
// }, 9000).then(function (isDisplayed) {
// assert.isTrue(isDisplayed);
// driver.quit();
// done();
// });
// });
// driver.wait(function () {
// return driver.get(urlGoogleFinanceRoot + "?q=BAC").then(function () {
// return driver.wait(function () {
// return driver.findElement(webdriver.By.xpath("//table[#class='snap-data']")).isDisplayed();
// }, 9000);
// });
// }, 9000).then(function (isDisplayed) {
// assert.isTrue(isDisplayed);
// driver.quit();
// done();
// });
// driver.wait(function(){
// return driver.get(urlGoogleFinanceRoot + "?q=BAC").then(function(){
// return driver.findElement(webdriver.By.xpath("//table[#class='snap-data']")).isDisplayed();
// });
// },5000).then(function(isDisplayed){
// assert.isTrue(isDisplayed);
// driver.quit();
// done();
// });
driver.get(urlGoogleFinanceRoot + "?q=BAC").then(function () {
driver.wait(function () {
return driver.findElement(webdriver.By.xpath("//table[#class='snap-data']")).isDisplayed();
}, 9000).then(function (isReady) {
assert.isTrue(isReady);
driver.quit();
done();
});
});
});
});
})();
and here's the output:
Selenium
Error1: NoSuchElementError: no such element
(Session info: chrome=44.0.2403.107)
(Driver info: chromedriver=2.16.333243 (0bfa1d3575fc1044244f21ddb82bf870944ef961),platform=Linux 3.16.0-4-amd64 x86_64)
1) should fetch a couple of pages and keep all of the content
0 passing (10s)
1 failing
1) Selenium should fetch a couple of pages and keep all of the content:
Error: timeout of 10000ms exceeded. Ensure the done() callback is being called in this test.
from this doc I understand that when you provide a function, it waits until the promise is resolved, but guessing that it is run only once, so you gotto try something like:
driver.get(urlGoogleFinanceRoot + "?q=BAC").then(function () {
driver.wait(webdriver.until.elementLocated(webdriver.By.xpath("//table[#class='snap-data']")), 9000)
.then(...

Looping over urls to do the same thing

I am tring to scrape a few sites. Here is my code:
for (var i = 0; i < urls.length; i++) {
url = urls[i];
console.log("Start scraping: " + url);
page.open(url, function () {
waitFor(function() {
return page.evaluate(function() {
return document.getElementById("progressWrapper").childNodes.length == 1;
});
}, function() {
var price = page.evaluate(function() {
// do something
return price;
});
console.log(price);
result = url + " ; " + price;
output = output + "\r\n" + result;
});
});
}
fs.write('test.txt', output);
phantom.exit();
I want to scrape all sites in the array urls, extract some information and then write this information to a text file.
But there seems to be a problem with the for loop. When scraping only one site without using a loop, all works as I want. But with the loop, first nothing happens, then the line
console.log("Start scraping: " + url);
is shown, but one time too much.
If url = {a,b,c}, then phantomjs does:
Start scraping: a
Start scraping: b
Start scraping: c
Start scraping:
It seems that page.open isn't called at all.
I am newbie to JS so I am sorry for this stupid question.
PhantomJS is asynchronous. By calling page.open() multiple times using a loop, you essentially rush the execution of the callback. You're overwriting the current request before it is finished with a new request which is then again overwritten. You need to execute them one after the other, for example like this:
page.open(url, function () {
waitFor(function() {
// something
}, function() {
page.open(url, function () {
waitFor(function() {
// something
}, function() {
// and so on
});
});
});
});
But this is tedious. There are utilities that can help you with writing nicer code like async.js. You can install it in the directory of the phantomjs script through npm.
var async = require("async"); // install async through npm
var tests = urls.map(function(url){
return function(callback){
page.open(url, function () {
waitFor(function() {
// something
}, function() {
callback();
});
});
};
});
async.series(tests, function finish(){
fs.write('test.txt', output);
phantom.exit();
});
If you don't want any dependencies, then it is also easy to define your own recursive function (from here):
var urls = [/*....*/];
function handle_page(url){
page.open(url, function(){
waitFor(function() {
// something
}, function() {
next_page();
});
});
}
function next_page(){
var url = urls.shift();
if(!urls){
phantom.exit(0);
}
handle_page(url);
}
next_page();

Categories

Resources