Skip to content

Commit

Permalink
Merge branch 'master' of github.com:beatfactor/nightwatch
Browse files Browse the repository at this point in the history
* 'master' of github.com:beatfactor/nightwatch:
  added results and errors properties on the test object available from inside tearDown to satisfy #135
  updated tests for waitForElementPresent
  added waitForConditionPollInterval also, changed the set milliseconds logic a bit and updated the api doc
  Fixed an issue where the set locateStrategy wasn't used correctly in the case of xpath
  Fixed an issue when the selenium server wasn't being stopped after a session create error
  added <no such element> to array of DO_NOT_LOG_ERRORS
  add missing semicolon
  add global timeout for waitFor commands. Fixes #129
  Standardize return - important for callbacks.
  • Loading branch information
beatfactor committed Apr 24, 2014
2 parents b7caf9f + 3e70c58 commit 4881749
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 36 deletions.
3 changes: 2 additions & 1 deletion lib/http/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ module.exports = (function() {
};

var DO_NOT_LOG_ERRORS = [
'Unable to locate element'
'Unable to locate element',
'no such element'
];

function HttpRequest(options) {
Expand Down
10 changes: 7 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,13 @@ Nightwatch.prototype.loadKeyCodes = function() {

Nightwatch.prototype.start = function() {
if (!this.sessionId) {
this.startSession().once('selenium:session_create', this.start);
this
.once('selenium:session_create', this.start)
.once('error', function(data, error) {
console.error(Logger.colors.red('Connection refused!'),
'Is selenium server started?');
})
.startSession();
return this;
}

Expand Down Expand Up @@ -378,8 +384,6 @@ Nightwatch.prototype.startSession = function () {
}
})
.on('error', function(data, err) {
console.error(Logger.colors.red('Connection refused!'),
'Is selenium server started?');
self.emit('error', data, err);
})
.send();
Expand Down
30 changes: 25 additions & 5 deletions lib/runner/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ module.exports = new (function() {
client.once('nightwatch:finished', function(results, errors) {
onComplete(results, errors);
});
client.once('error', function(data, error) {
finishCallback({message: 'Unable to start a new session.'}, false);
});
clientFn.call(context, context.client);
client.start();
};
Expand Down Expand Up @@ -146,14 +149,11 @@ module.exports = new (function() {
tearDown = function(context, clientFn, client, onTestComplete) {
if (module.tearDown.length === 0) {
startClient(context, clientFn, client, function(results, errors) {
module.tearDown();
onTestComplete(results, errors);
callTearDown(module, results, errors, onTestComplete, false);
});
} else if (module.tearDown.length >= 0) {
startClient(context, clientFn, client, function(results, errors) {
module.tearDown(function() {
onTestComplete(results, errors);
});
callTearDown(module, results, errors, onTestComplete, true);
});
}
};
Expand All @@ -166,6 +166,26 @@ module.exports = new (function() {
setTimeout(next, 0);
}

function callTearDown(module, results, errors, onTestComplete, hasCallback) {
module.results = results;
module.errors = errors;

var tearDownFn = module.tearDown;
var args = [];
var callbackFn = function() {
onTestComplete(results, errors);
};
if (hasCallback) {
args.push(callbackFn);
}

tearDownFn.apply(module, args);

if (!hasCallback) {
callbackFn();
}
}

function printResults(testresults, modulekeys) {
var elapsedTime = new Date().getTime() - globalStartTime;
if (testresults.passed > 0 && testresults.errors === 0 && testresults.failed === 0) {
Expand Down
124 changes: 104 additions & 20 deletions lib/selenium/commands/_waitForElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,124 @@ function WaitForElement() {
this.abortOnFailure = true;
this.selector = null;
this.locateStrategy = this.client.locateStrategy || 'css selector';
this.rescheduleInterval = 500; //ms
this.rescheduleInterval = this.client.api.globals.waitForConditionPollInterval || 500; //ms
this.protocol = require('../protocol.js')(this.client);
}

util.inherits(WaitForElement, events.EventEmitter);

/*!
* The public command function which will be called by the test runner. Arguments can be passed in a variety of ways:
* The public command function which will be called by the test runner. Arguments can be passed in a variety of ways.
*
* waitForElement('body', 500)
* waitForElement('body', 500, 'test message)
* The custom message always is last and the callback is always before the message or last if a message is not passed.
*
* The second argument is always the time in milliseconds. The third argument can be either of:
* - abortOnFailure: this can overwrite the default behaviour of aborting the test if the condition is not met within the specified time
* - rescheduleInterval: this can overwrite the default polling interval (currently 500ms)
* The above can be supplied also together, in which case the rescheduleInterval is specified before the abortOnFailure.
*
* Some of the multiple usage possibilities:
* ---------------------------------------------------------------------------
* - with no arguments; in this case a global default timeout is expected
* waitForElement('body');
*
* - with a global default timeout and a callback
* waitForElement('body', function() {});
*
* - with a global default timeout, a callback and a custom message
* waitForElement('body', function() {}, 'test message');
*
* - with only the timeout
* waitForElement('body', 500);
*
* - with a timeout and a custom message
* waitForElement('body', 500, 'test message);
*
* - with a timeout and a callback
* waitForElement('body', 500, function() { .. });
*
* - with a timeout and a custom abortOnFailure
* waitForElement('body', 500, true);
*
* - with a timeout, a custom abortOnFailure and a custom message
* waitForElement('body', 500, true, 'test message');
*
* - with a timeout, a custom abortOnFailure and a callback
* waitForElement('body', 500, true, function() { .. });
*
* - with a timeout, a custom abortOnFailure, a callback and a custom message
* waitForElement('body', 500, true, function() { .. }, 'test message');
*
* - with a timeout, a custom reschedule interval and a callback
* waitForElement('body', 500, 100, function() { .. });
*
* - with a timeout, a custom rescheduleInterval and a custom abortOnFailure
* waitForElement('body', 500, 100, false);
*
*
* @param {string} selector
* @param {number} milliseconds
* @param {function|boolean|string} callbackOrAbort
* @param {number|function|string} milliseconds
* @param {function|boolean|string|number} callbackOrAbort
* @returns {WaitForElement}
*/
WaitForElement.prototype.command = function(selector, milliseconds, callbackOrAbort) {
if (typeof milliseconds !== 'number') {
throw new Error('waitForElement expects second parameter to be number; ' +
typeof (milliseconds) + ' given');
}
this.startTimer = new Date().getTime();
this.ms = this.setMilliseconds(milliseconds);

if (typeof arguments[2] === 'boolean') {
if (typeof arguments[1] === 'function') {
////////////////////////////////////////////////
// The command was called with an implied global timeout:
//
// waitForElement('body', function() {});
// waitForElement('body', function() {}, 'custom message');
////////////////////////////////////////////////
this.cb = arguments[1];
} else if (typeof arguments[2] === 'boolean') {
////////////////////////////////////////////////
// The command was called with a custom abortOnFailure:
//
// waitForElement('body', 500, false);
////////////////////////////////////////////////
this.abortOnFailure = arguments[2];

// The optional callback is the 4th argument now
this.cb = arguments[3] || function() {};
} else if (typeof arguments[2] === 'number') {
////////////////////////////////////////////////
// The command was called with a custom rescheduleInterval:
//
// waitForElement('body', 500, 100);
////////////////////////////////////////////////
this.rescheduleInterval = arguments[2];

if (typeof arguments[3] === 'boolean') {
////////////////////////////////////////////////
// The command was called with a custom rescheduleInterval and custom abortOnFailure:
//
// waitForElement('body', 500, 100, false);
////////////////////////////////////////////////
this.abortOnFailure = arguments[3];

// The optional callback is the 5th argument now
this.cb = arguments[4] || function() {};
} else {
// The optional callback is the 4th argument now
this.cb = arguments[3] || function() {};
}
} else {
// The optional callback is the 3th argument now
this.cb = (typeof callbackOrAbort === 'function' && callbackOrAbort) || function() {};
}

// support for a custom message
this.message = null;
var lastArgument = arguments[arguments.length - 1];
if (typeof lastArgument === 'string') {
this.message = lastArgument;
if (arguments.length > 1) {
var lastArgument = arguments[arguments.length - 1];
if (typeof lastArgument === 'string') {
this.message = lastArgument;
}
}

this.ms = milliseconds;
this.selector = selector;
this.checkElement();
return this;
Expand Down Expand Up @@ -170,6 +236,27 @@ WaitForElement.prototype.formatMessage = function (defaultMsg, timeMs) {
return format(this.message || defaultMsg, this.selector, timeMs || this.ms);
};

/**
* Set the time in milliseconds to wait for the condition, accepting a given value or a globally defined default
*
* @param {number} [timeoutMs]
* @throws Will throw an error if the global default is undefined or a non-number
* @returns {number}
*/
WaitForElement.prototype.setMilliseconds = function (timeoutMs) {
if (timeoutMs && typeof timeoutMs === 'number') {
return timeoutMs;
}

var globalTimeout = this.client.api.globals.waitForConditionTimeout;
if (typeof globalTimeout !== 'number') {
throw new Error('waitForElement expects second parameter to have a global default ' +
'(waitForConditionTimeout) to be specified if not passed as the second parameter ');
}

return globalTimeout;
};

/**
* A smaller version of util.format that doesn't support json and
* if a placeholder is missing, it is omitted instead of appended
Expand All @@ -181,7 +268,7 @@ function format(f) {
var i = 1;
var args = arguments;
var len = args.length;
var str = String(f).replace(formatRegExp, function(x) {
return String(f).replace(formatRegExp, function(x) {
if (x === '%%') {
return '%';
}
Expand All @@ -197,9 +284,6 @@ function format(f) {
return x;
}
});

return str;
}


module.exports = WaitForElement;
module.exports = WaitForElement;
2 changes: 1 addition & 1 deletion lib/selenium/commands/waitForElementPresent.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ WaitForElementPresent.prototype.elementNotFound = function(result, now) {
}

var defaultMsg = 'Timed out while waiting for element <%s> to be present for %d milliseconds.';
return this.fail(false, 'not found', this.expectedValue, defaultMsg);
return this.fail({value:false}, 'not found', this.expectedValue, defaultMsg);
};

module.exports = WaitForElementPresent;
2 changes: 1 addition & 1 deletion lib/selenium/element-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,6 @@ module.exports = function(client) {
elementCommands.submitForm = 'submit';

function addElementCommand(protocolAction, extraArgs) {
var defaultUsing = client.locateStrategy || 'css selector';
var self = this;
extraArgs = extraArgs || 0;
var expectedArgs = 3 + extraArgs;
Expand All @@ -322,6 +321,7 @@ module.exports = function(client) {
args.push(noopFn);
}

var defaultUsing = client.locateStrategy || 'css selector';
if (expectedArgs - args.length === 1) {
args.unshift(defaultUsing);
}
Expand Down
5 changes: 5 additions & 0 deletions tests/mocks.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
"postdata" : "{\"using\":\"css selector\",\"value\":\"#weblogin\"}",
"response" : "{\"sessionId\":\"1352110219202\",\"status\":0,\"value\":{\"ELEMENT\":\"0\"},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":604376696}"
},
{
"url" : "/wd/hub/session/1352110219202/element",
"postdata" : "{\"using\":\"xpath\",\"value\":\"//weblogin\"}",
"response" : "{\"sessionId\":\"1352110219202\",\"status\":0,\"value\":{\"ELEMENT\":\"0\"},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":604376696}"
},
{
"url" : "/wd/hub/session/1352110219202/buttondown",
"postdata" : "{\"button\":0}",
Expand Down
5 changes: 5 additions & 0 deletions tests/sampletests/mixed/sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ module.exports = {

tearDown : function(callback) {
var client = this.client;
var self = this;
setTimeout(function() {
client.globals.test.ok('tearDown callback called.');
client.globals.test.deepEqual(self.results, {
passed: 0, failed: 0, errors: 0, skipped: 0, tests: []
});
client.globals.test.deepEqual(self.errors, []);
callback();
}, 100);
}
Expand Down
21 changes: 21 additions & 0 deletions tests/src/commands/testClick.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,28 @@ module.exports = {
test.equals(result.status, 0)
test.done();
});
},

testClickCommandWithXpath : function(test) {
var client = this.client.api;

MockServer.addMock({
"url" : "/wd/hub/session/1352110219202/element/0/click",
"response" : JSON.stringify({
sessionId: "1352110219202",
status:0
})
});

client
.useXpath()
.click('//weblogin', function callback(result) {
test.equals(result.status, 0)
})
.click('css selector', '#weblogin', function callback(result) {
test.equals(result.status, 0)
test.done();
});
},

tearDown : function(callback) {
Expand Down
8 changes: 4 additions & 4 deletions tests/src/commands/testWaitForElementPresent.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,24 @@ module.exports = {
});
},

testSuccessWithCustomMessage : function(test) {
testFailureWithCustomMessage : function(test) {
this.client.api.waitForElementPresent('.weblogin', 100, function callback(result, instance) {
test.equal(instance.message, 'Element .weblogin found in 100 milliseconds');
test.equal(result, false);
test.equal(result.value, false);
test.done();
}, 'Element %s found in %d milliseconds');
},

testFailure : function(test) {
this.client.api.waitForElementPresent('.weblogin', 600, function callback(result) {
test.equal(result, false);
test.equal(result.value, false);
test.done();
});
},

testFailureNoAbort : function(test) {
this.client.api.waitForElementPresent('.weblogin', 600, false, function callback(result) {
test.equal(result, false);
test.equal(result.value, false);
test.done();
});
},
Expand Down
Loading

0 comments on commit 4881749

Please sign in to comment.