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(...
Related
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>
I have some code that handles outgoing and incoming data. When I send a request, I expect some kind of an answer from an external source. If no answer has arrived after a timeout of 1500ms, I return something to an error callback and end the request. That all looks something like this:
this.sendRequest = function(sendRequestDevice, sendRequestFID, sendRequestParam, sendRequestAction, sendRequestData,
sendRequestReturnCB, sendRequestErrorCB) {
if (!this.isConnected) {
if (sendRequestErrorCB !== undefined) {
sendRequestErrorCB(VacuumSystem.ERROR_NOT_CONNECTED);
}
return;
}
// Packet creation
var sendRequestAddress = this.getDeviceAddress(sendRequestDevice);
var sendRequestPacket = pack(sendRequestAddress, sendRequestAction, sendRequestParam, sendRequestData);
// var sendRequestSEQ = this.getSequenceNumberFromPacket(sendRequestHeader);
// Sending the created packet
if (sendRequestDevice.expectedResponse.length === 0) {
// Setting the requesting current device's current request
sendRequestDevice.expectedResponse.push({
FID: sendRequestFID,
timeout: setTimeout(this.sendRequestTimeout.bind
(this, sendRequestDevice, sendRequestErrorCB), this.timeout),
returnCB: sendRequestReturnCB,
errorCB: sendRequestErrorCB
});
this.serialport.write(sendRequestPacket);
} else {
setTimeout(this.sendRequest.bind(this, sendRequestDevice, sendRequestFID, sendRequestParam,
sendRequestAction, sendRequestData, sendRequestReturnCB, sendRequestErrorCB ), 1000)
}
};
this.sendRequestTimeout = function (timeoutDevice, timeoutErrorCB) {
// if the response is not received in 'this.timeout' ms, throw an error
clearTimeout(timeoutDevice.expectedResponse[0].timeout);
timeoutDevice.expectedResponse.splice(0, 1);
if (timeoutErrorCB !== undefined){
timeoutErrorCB(VacuumSystem.ERROR_TIMEOUT);
console.log('Error Timeout');
}
return;
};
I want to test this behavior with a unit test using mocha and chai. Basically I just want to assert that the error callback is called after 1.5s with the argument VacuumSystem.ERROR_TIMEOUT. What I tried is:
describe('behavior', function() {
beforeEach(function () {
returnCallback = sinon.spy();
errorCallback = sinon.spy();
// this function calls the sendRequest function with the needed parameters
PPT100.getPressure(returnCallback, errorCallback);
});
it('should return a Timeout Error when no response is received', function (done) {
setTimeout(function () {
expect(errorCallback.callCount).to.equal(1);
sinon.assert.calledWith(errorCallback, VacuumSystem.ERROR_TIMEOUT)
done();
}, PPT100.ipcon.timeout);
});
});
I know that the error is returned since I can see the log message 'Error Timeout', but expect fails for the sinon spy. What am I doing wrong here?
Description: I want to read a particular label from the webpage, whose value changes to one of "started", "inprogress", "success", "Error". Once the label value changes to "success" or "Error" there will not be any further changes.
Issue: When I read the label value using javascript in protractor, the text value of the label is not returned to the calling function; instead it returns 'undefined'. Below is my code, please have a look and let me where the issue is.
CheckColor_Test.js
var commonFunctions = require('../pages/CommonFunctions.js');
describe("Run Test", function () {
it("should stop once the status reached Success or Error", function () {
var processStatus = commonFunctions.refreshTillProcessFinish();
expect(processStatus).toContain('Success','Error');
});
});
CommonFunctions.js
Var CommonFunctions = function(){
var label = element(by.id('Status'));
var refreshStatusBtn = element(by.css('[ng-click="getJob()"]'));
this.getStatusValue = function () {
return label.then(function (headers) {
return headers.getText();
});
};
this.refreshTillRefreshFinish = function () {
var refreshStatusMonitor = function (currentStatus) {
return currentStatus.then(function (Status) {
if (Status == 'Success' || Status.includes("Error")) {
console.log(Status);
return Status;
} else {
refreshStatusBtn.click();
console.log(Status);
browser.sleep(2000);
refreshStatusMonitor (currentStatus);
}
});
};
return refreshStatusMonitor (this.getStatusValue);
};
}
module.exports = new CommonFunctions();
Executing in Protractor:
I have configured protractor in Webstorm, hence I used to run using that.
Expected Result:
The test should get successful and passed
Actual Result:
The test fails with below error.
"C:\Program Files (x86)\JetBrains\WebStorm 2016.1.1\bin\runnerw.exe" "C:\Program Files\nodejs\node.exe" node_modules\protractor\built\cli.js D:\Somesh_HDD\WebstormProjects\ProjectUBET\conf.js
[22:19:59] I/direct - Using ChromeDriver directly...
[22:19:59] I/launcher - Running 1 instances of WebDriver
Spec started
Started
InProgress
Success
Run Test
? should stop once the status reached Success or Error
- Expected undefined to contain 'Success', 'Error'.
**************************************************
* Failures *
**************************************************
1) Run Test should stop once the status reached Success or Error
- Expected undefined to contain 'Success', 'Error'.
Executed 1 of 1 spec (1 FAILED) in 33 secs.
[22:20:36] I/launcher - 0 instance(s) of WebDriver still running
[22:20:36] I/launcher - chrome #01 failed 1 test(s)
[22:20:36] I/launcher - overall: 1 failed spec(s)
[22:20:36] E/launcher - Process exited with error code 1
Process finished with exit code 1
The following return value:
return currentStatus.then(...);
is not the value returned by this statement:
return Status;
In fact, the latter is returned to one of the recursive calls of refreshStatusMonitor which is not captured anywhere.
Because this is asynchronous code involving promises, the return value of currentStatus should be a promise as well, which would bubble up via refreshStatusMonitor, refreshTillRefreshFinish to your test, which then also needs to be adapted to wait for the promise to be fulfilled before expecting anything.
I would also advise against the use of browser.sleep(...) as it completely blocks your JavaScript environment. You could use setTimeout(...) instead.
Here is some untested code which builds on those ideas:
this.refreshTillRefreshFinish = function () {
// create a promise
var deferred = protractor.promise.defer();
var refreshStatusMonitor = function (currentStatus) {
currentStatus.then(function refresh(Status) {
if (Status == 'Success' || Status.includes("Error")) {
// Signal the completion via the promise.
// This triggers the `then` callback in your revised test
deferred.fulfill(Status);
} else {
refreshStatusBtn.click();
console.log(Status);
// Use setTimeout so JavaScript is not blocked here:
setTimeout(function () {
refreshStatusMonitor(currentStatus);
}, 2000);
}
});
};
refreshStatusMonitor(this.getStatusValue);
// Don't wait for the result to happen while blocking everything,
// instead return a custom-made promise immediately
return deferred.promise;
};
Your test should then also take into account that you are dealing with a promise:
it("should stop once the status reached Success or Error", function () {
var processStatus = commonFunctions.refreshTillProcessFinish().then(function () {
expect(processStatus).toContain('Success','Error');
done();
});
}, 20000); // set timeout to 20 seconds
Note that Jasmine has a default timeout of 2 seconds, so you need to provide that extra argument at the end.
NB: Such asynchronous tests are not very suitable for running batches of unit tests.
Is your script able to click on the refresh button recursively?
i have made few changes to your existing script by introducing promises inside the recursive method.Just give a try.
var CommonFunctions = function(){
var label = element(by.id('Status'));
var refreshStatusBtn = element(by.css('[ng-click="getJob()"]'));
this.refreshTillRefreshFinish = function () {
var defer = protractor.promise().defer();
var refreshStatusMonitor = function () {
label.getText().then(function (Status) {
if (Status == 'Success' || Status.includes("Error")) {
defer.fulfill(Status);
} else {
refreshStatusBtn.click();
browser.sleep(2000);
refreshStatusMonitor ();
}
});
return defer.promise;
};
return refreshStatusMonitor ();
};
}
module.exports = new CommonFunctions();
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.
});
I am adding some qunit test cases for a module. Few of the test cases have sync processes which I am using the standard stop() and start() as per docs.
My questions is, isn't the fact that the extra 1 second from setTimeout(function () { start();}, 1000); is added to the runtime of the test run, making the results in accurate?
I am a little not satisfied that +1000ms is added to the runtime as outside of the testsuite, inside the real app that uses that module that process completes without the 1000ms added here to carry out the test.
So when I pass this interface to less technical tester I have to explain in the title of the test to subtract that 1000 from that test before adding them up or whatever to calculate overall speed etc. [I basically want a way to have that extra timeout removed from the results automatically]
Module code below:
define("tests/admin.connections.tests", ["mods/admin.connections", "datacontext"], function (connections, datacontext) {
module("ADMIN PAGE CONNECTION LIST MODULE", {
setup: function () {
//ok(true, "once extra assert per test for Search Modules");
}
});
test('Module is available?', function () {
equal(_.isUndefined(connections), false, "connections js module exists");
equal(_.isObject(connections), true, "connections js module is valid object");
});
test('HTML and CSS loading correctly? [Subtract 1 second from time to get the real time lapsed]', function () {
function testHtml(html) {
var d = document.createElement('htmlTestDiv');
d.innerHTML = html;
return d.innerHTML.replace(/\s+/g, ' ');;
}
stop();
$.get('http://media.concepglobal.com/cbaddons/templates/connections.html', function (data) {
equal(testHtml(connections.html), data.replace(/\s+/g, ' '), 'Html of the module was correctly loaded');
});
$.get('http://media.concepglobal.com/cbaddons/styles/connections.css', function (data) {
equal(testHtml(connections.css), data.replace(/\s+/g, ' '), 'CSS of the module was correctly loaded');
});
setTimeout(function () { start();}, 1000);
});
test('getConnectionsByUserId Async Method [Subtract 1 second from time to get the real time lapsed]', function () {
function getConnectionsByUserId(successCallback) {
amplify.request("getConnectionsByUserId", { uid: '0' }, function (data) {
connections.userConnectionsCallback(data);
successCallback();
});
}
stop();
getConnectionsByUserId(function () {
var connectionsReturnedData = connections.connectionListViewModel.connections();
expect(2);
ok(_.isArray(connectionsReturnedData), 'Valid array has been returned for connections: ' + connectionsReturnedData);
equal(connectionsReturnedData[0].type(), "sitecore", 'First returned object has a type property of "' + connectionsReturnedData[0].type() + '" and we expected it to be "sitecore"');
});
setTimeout(function () { start(); }, 1000);
});
});
QUnit saves the currently running test in QUnit.config.current, this allows you to change the test during it's execution.
What you probably want is to reset the timer of the test after the timeout.
I created a little example to show what I mean (see on jsFiddle):
asyncTest("Long running test with 2s timeout", function () {
expect(1);
ok(true);
setTimeout(function () {
QUnit.config.current.started = +new Date();
start();
}, 2000);
});
Like that the timer is reset once the timeout is over. This results in more accurate runtime in terms of what is executed. Now only the total time shows how much time was actually used to run all tests.