Skip to content

Commit 25bbe7b

Browse files
committed
allow timeout + retry on selenium sessions timeout
The Nightwatch.startSession http action often times out despite the selenium server being totally responsive. This PR adds timeout and retry parameters to the http module, allowing the tests to continue despite a failed session attempt.
1 parent 9d603d1 commit 25bbe7b

File tree

5 files changed

+108
-16
lines changed

5 files changed

+108
-16
lines changed

lib/http/request.js

+31-11
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ var format = util.format;
77

88
module.exports = (function() {
99
var Settings = {
10-
selenium_host : 'localhost',
11-
selenium_port : 4444,
12-
default_path : '/wd/hub',
13-
credentials : null,
14-
use_ssl : false,
15-
proxy : null
10+
selenium_host : 'localhost',
11+
selenium_port : 4444,
12+
default_path : '/wd/hub',
13+
credentials : null,
14+
use_ssl : false,
15+
proxy : null,
16+
timeout : 60000,
17+
retry_attempts : 0
1618
};
1719

1820
var DO_NOT_LOG_ERRORS = [
@@ -29,11 +31,13 @@ module.exports = (function() {
2931
util.inherits(HttpRequest, events.EventEmitter);
3032

3133
HttpRequest.prototype.setOptions = function(options) {
32-
this.data = options.data && jsonStringify(options.data) || '';
33-
this.contentLength = this.data.length;
34-
this.reqOptions = this.createOptions(options);
35-
this.hostname = formatHostname(this.reqOptions.host, this.reqOptions.port);
36-
this.request = null;
34+
this.data = options.data && jsonStringify(options.data) || '';
35+
this.contentLength = this.data.length;
36+
this.reqOptions = this.createOptions(options);
37+
this.hostname = formatHostname(this.reqOptions.host, this.reqOptions.port);
38+
this.request = null;
39+
this.timeout = Settings.timeout;
40+
this.retryAttempts = Settings.retry_attempts;
3741

3842
return this;
3943
};
@@ -165,6 +169,16 @@ module.exports = (function() {
165169
self.emit('error', {}, response);
166170
});
167171

172+
this.request.setTimeout(this.timeout, function(){
173+
if (self.retryAttempts) {
174+
Logger.info('retrying on http timeout');
175+
self.retryAttempts = self.retryAttempts - 1;
176+
self.send();
177+
} else {
178+
self.emit('error', {});
179+
}
180+
});
181+
168182
Logger.info('Request: ' + this.reqOptions.method + ' ' + this.hostname + this.reqOptions.path,
169183
'\n - data: ', this.data, '\n - headers: ', JSON.stringify(this.reqOptions.headers));
170184

@@ -205,6 +219,12 @@ module.exports = (function() {
205219
HttpRequest.setDefaultPathPrefix = function(path) {
206220
Settings.default_path = path;
207221
};
222+
HttpRequest.setTimeout = function(timeout) {
223+
Settings.timeout = timeout;
224+
};
225+
HttpRequest.setRetryAttempts = function(retryAttempts) {
226+
Settings.retry_attempts = retryAttempts;
227+
};
208228

209229
///////////////////////////////////////////////////////////
210230
// Helpers

lib/index.js

+13-4
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,13 @@ Nightwatch.prototype.setOptions = function(options) {
113113

114114
this.api.options.log_screenshot_data = this.options.log_screenshot_data ||
115115
(typeof this.options.log_screenshot_data == 'undefined');
116-
var seleniumPort = this.options.seleniumPort || this.options.selenium_port;
117-
var seleniumHost = this.options.seleniumHost || this.options.selenium_host;
118-
var useSSL = this.options.useSsl || this.options.use_ssl;
119-
var proxy = this.options.proxy;
116+
var seleniumPort = this.options.seleniumPort || this.options.selenium_port;
117+
var seleniumHost = this.options.seleniumHost || this.options.selenium_host;
118+
var useSSL = this.options.useSsl || this.options.use_ssl;
119+
var proxy = this.options.proxy;
120+
var timeoutOptions = this.options.request_timeout_options || {};
121+
var timeout = timeoutOptions.timeout;
122+
var retryAttempts = timeoutOptions.retry_attempts;
120123

121124
if (seleniumPort) {
122125
HttpRequest.setSeleniumPort(seleniumPort);
@@ -130,6 +133,12 @@ Nightwatch.prototype.setOptions = function(options) {
130133
if (proxy) {
131134
HttpRequest.setProxy(proxy);
132135
}
136+
if (timeout) {
137+
HttpRequest.setTimeout(timeout);
138+
}
139+
if (retryAttempts) {
140+
HttpRequest.setRetryAttempts(retryAttempts);
141+
}
133142

134143
if (typeof this.options.default_path_prefix == 'string') {
135144
HttpRequest.setDefaultPathPrefix(this.options.default_path_prefix);

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@
3737
"jscoverage": "latest",
3838
"jshint": "~2.4.4",
3939
"jsonlint": "~1.6.0",
40+
"lodash.once": "^4.1.1",
4041
"mocha": "^2.3.4",
4142
"mocha-lcov-reporter": "^1.2.0",
4243
"mock-spawn": "^0.2.1",
4344
"mockery": "~1.4.0",
44-
"nock": "~0.45.0",
45+
"nock": "https://github.com/NickStefan/nock/tarball/set-allow-unmock",
4546
"nodeunit": "latest"
4647
},
4748
"bin": {

test/lib/nocks.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var nock = require('nock');
2+
nock.setAllowUnmocked(true);
23

34
module.exports = {
45
createSession : function() {

test/src/http/testRequest.js

+61
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var Logger = common.require('util/logger');
44
var nock = require('nock');
55
var assert = require('assert');
66
var mockery = require('mockery');
7+
var once = require('lodash.once');
78

89
module.exports = {
910
'test HttpRequest' : {
@@ -31,6 +32,9 @@ module.exports = {
3132
},
3233

3334
afterEach: function () {
35+
HttpRequest.setTimeout(60000) // back to default after these tests
36+
HttpRequest.setRetryAttempts(0);
37+
3438
mockery.disable();
3539
},
3640

@@ -204,6 +208,63 @@ module.exports = {
204208
done();
205209
}).send();
206210

211+
},
212+
213+
testRequestTimeout: function (done) {
214+
nock('http://localhost:4444')
215+
.get('/wd/hub/123456/element')
216+
.socketDelay(10000)
217+
.reply(200, {});
218+
219+
var options = {
220+
path: '/:sessionId/element',
221+
method: 'GET',
222+
sessionId: '123456'
223+
};
224+
225+
HttpRequest.setTimeout(1000);
226+
227+
var request = new HttpRequest(options);
228+
229+
request.on('error', function () {
230+
assert.ok(true); // should trigger error on timeout
231+
done();
232+
}).send();
233+
234+
},
235+
236+
testRetryAttempts: function (done) {
237+
nock('http://localhost:4444')
238+
.get('/wd/hub/123456/element')
239+
.socketDelay(10000)
240+
.reply(200, {})
241+
.get('/wd/hub/123456/element')
242+
.socketDelay(100)
243+
.reply(200, {})
244+
245+
var options = {
246+
path: '/:sessionId/element',
247+
method: 'GET',
248+
sessionId: '123456'
249+
};
250+
251+
HttpRequest.setTimeout(800);
252+
HttpRequest.setRetryAttempts(1);
253+
254+
var request = new HttpRequest(options);
255+
var doneOnce = once(function(){
256+
// the nock library is still passing 200 for the first request
257+
// rather than truly timing out
258+
done();
259+
});
260+
261+
request
262+
.on('success', function (result) {
263+
assert.ok(true); // should succeed
264+
doneOnce();
265+
}).send();
266+
207267
}
208268
}
269+
209270
};

0 commit comments

Comments
 (0)