nightwatch
Advanced tools
Comparing version 0.6.15 to 0.7.0
@@ -10,2 +10,3 @@ { | ||
"disable_colors": false, | ||
"test_workers" : false, | ||
@@ -53,2 +54,3 @@ "selenium" : { | ||
"enabled" : false, | ||
"on_failure" : true, | ||
"path" : "" | ||
@@ -55,0 +57,0 @@ }, |
@@ -9,3 +9,3 @@ # Contributing to Nightwatch | ||
1. Please do not ask for support or questions in the __Issues__ list. The [mailing list](https://groups.google.com/forum/#!forum/nightwatchjs) is a much better place for discussions and it helps keeping things separate | ||
2. Search for a similar issue here: https://github.com/beatfactor/nightwatch/search?type=Issues and add your scenario there and anything else which you think will help with fixing it | ||
2. Search for a similar issue here: https://github.com/nightwatchjs/nightwatch/search?type=Issues and add your scenario there and anything else which you think will help with fixing it | ||
3. Please do not repport issues you have with Selenium or the individual browser drivers that cannot or should not be solved in Nightwatch | ||
@@ -12,0 +12,0 @@ 4. When submitting a new issue please include a sample test (for complex scenarios) which would reproduce the problem you're experiencing. The test should be against a public url |
@@ -0,1 +1,2 @@ | ||
/* jshint expr: true */ | ||
module.exports = { | ||
@@ -5,12 +6,24 @@ tags: ['google'], | ||
client | ||
.page.google().goToGoogle() | ||
.assert.title('Google') | ||
.assert.visible('input[name="q"]') | ||
.setValue('input[type=text]', 'nightwatch') | ||
.waitForElementVisible('button[name=btnG]', 1000) | ||
.click('button[name=btnG]') | ||
.pause(1000) | ||
.assert.containsText('#main', 'Night Watch') | ||
.end(); | ||
.url('http://google.no') | ||
.pause(1000); | ||
client.expect.element('body').to.be.present.before(1000); | ||
client.expect.element('#lst-ib').to.have.css('display'); | ||
client.expect.element('body').to.have.attribute('class').which.contains('vasq'); | ||
client.expect.element('body').to.have.attribute('class').which.matches(/vasq$/); | ||
client.expect.element('body').to.have.attribute('class').before(1000); | ||
client.expect.element('#lst-ib').to.be.enabled; | ||
client.expect.element('#hplogo').text.to.match(/Norge/).before(1000); | ||
client.setValue('#lst-ib', 'Norway').pause(500); | ||
client.expect.element('#lst-ib').to.have.value.equal('Norway'); | ||
client.expect.element('#lst-ib').to.be.an('input'); | ||
client.expect.element('#lst-ib').to.be.not.selected; | ||
client.expect.element('#lst-ib').to.be.visible; | ||
client.end(); | ||
} | ||
}; |
@@ -22,8 +22,2 @@ var nightwatch = require('./lib/index.js'); | ||
}, | ||
tests: [ | ||
'tests/*.js', | ||
'tests/extra/**/*.js', | ||
'tests/src/**/*.js', | ||
'tests/sampletests/**/*.js' | ||
], | ||
gruntfile: { | ||
@@ -30,0 +24,0 @@ src: 'Gruntfile.js' |
@@ -312,3 +312,2 @@ var util = require('util'); | ||
function addElementCommand(protocolAction, extraArgs) { | ||
var self = this; | ||
extraArgs = extraArgs || 0; | ||
@@ -332,41 +331,41 @@ var expectedArgs = 3 + extraArgs; | ||
var using = args.shift(), value = args.shift(); | ||
var using = args.shift(); | ||
var value = args.shift(); | ||
var callback = args.pop(); | ||
function CommandAction() { | ||
events.EventEmitter.call(this); | ||
return new CommandAction(using, value, protocolAction, args, callback); | ||
}; | ||
} | ||
var $this = this; | ||
function CommandAction(using, value, protocolAction, args, callback) { | ||
events.EventEmitter.call(this); | ||
var el = Protocol.element(using, value, function(result) { | ||
if (result.status !== 0) { | ||
callback.call(client.api, result); | ||
var errorMessage = 'Unable to locate element: "' + value + '" using: ' + using; | ||
client.results.errors++; | ||
client.errors.push(errorMessage); | ||
console.log(Logger.colors.red('ERROR:'), errorMessage); | ||
$this.emit('complete', el, $this); | ||
} else { | ||
result = result.value.ELEMENT; | ||
var $this = this; | ||
var el = Protocol.element(using, value, function(result) { | ||
if (result.status !== 0) { | ||
callback.call(client.api, result); | ||
var errorMessage = 'Unable to locate element: "' + value + '" using: ' + using; | ||
client.results.errors++; | ||
client.errors.push(errorMessage); | ||
console.log(Logger.colors.red('ERROR:'), errorMessage); | ||
$this.emit('complete', el, $this); | ||
} else { | ||
result = result.value.ELEMENT; | ||
args.push(function(r) { | ||
callback.call(client.api, r); | ||
}); | ||
args.push(function(r) { | ||
callback.call(client.api, r); | ||
}); | ||
args.unshift(result); | ||
args.unshift(result); | ||
var c = Protocol[protocolAction].apply(Protocol, args).once('complete', function() { | ||
$this.emit('complete', c, $this); | ||
}); | ||
} | ||
var c = Protocol[protocolAction].apply(Protocol, args).once('complete', function() { | ||
$this.emit('complete', c, $this); | ||
}); | ||
} | ||
}); | ||
} | ||
util.inherits(CommandAction, events.EventEmitter); | ||
util.inherits(CommandAction, events.EventEmitter); | ||
return new CommandAction(); | ||
}; | ||
} | ||
for (var commandName in elementCommands) { | ||
Object.keys(elementCommands).forEach(function(commandName) { | ||
var args = elementCommands[commandName]; | ||
@@ -378,3 +377,3 @@ if (!Array.isArray(args)) { | ||
returnValue[commandName] = addElementCommand.apply(client.api, args); | ||
} | ||
}); | ||
@@ -381,0 +380,0 @@ // alias |
@@ -0,1 +1,4 @@ | ||
var elementByRecursion = require('./element-commands/_elementByRecursion.js'); | ||
var elementsByRecursion = require('./element-commands/_elementsByRecursion.js'); | ||
module.exports = function(Nightwatch) { | ||
@@ -138,2 +141,18 @@ | ||
Actions.element = function(using, value, callback) { | ||
if (using == 'recursion') { | ||
return new elementByRecursion(Nightwatch).command(value, callback); | ||
} | ||
return element(using, value, callback); | ||
}; | ||
/*! | ||
* element protocol action | ||
* | ||
* @param {string} using | ||
* @param {string} value | ||
* @param {function} callback | ||
* @private | ||
*/ | ||
function element(using, value, callback) { | ||
var strategies = ['class name', 'css selector', 'id', 'name', 'link text', | ||
@@ -153,2 +172,29 @@ 'partial link text', 'tag name', 'xpath']; | ||
}, callback); | ||
} | ||
/** | ||
* Search for an element on the page, starting from the identified element. The located element will be returned as a WebElement JSON object. | ||
* | ||
* @link /session/:sessionId/element/:id/element | ||
* @param {string} id ID of the element to route the command to. | ||
* @param {string} using The locator strategy to use. | ||
* @param {string} value The search target. | ||
* @param {function} [callback] Optional callback function to be called when the command finishes. | ||
* @api protocol | ||
*/ | ||
Actions.elementIdElement = function(id, using, value, callback) { | ||
var strategies = ['class name', 'css selector', 'id', 'name', 'link text', | ||
'partial link text', 'tag name', 'xpath']; | ||
using = using.toLocaleLowerCase(); | ||
if (strategies.indexOf(using) === -1) { | ||
throw new Error('Provided locating strategy is not supported: ' + | ||
using + '. It must be one of the following:\n' + | ||
strategies.join(', ')); | ||
} | ||
return postRequest('/element/' + id + '/element', { | ||
using: using, | ||
value: value | ||
}, callback); | ||
}; | ||
@@ -167,6 +213,22 @@ | ||
Actions.elements = function(using, value, callback) { | ||
if (using == 'recursion') { | ||
return new elementsByRecursion(Nightwatch).command(value, callback); | ||
} | ||
return elements(using, value, callback); | ||
}; | ||
/*! | ||
* elements protocol action | ||
* | ||
* @param {string} using | ||
* @param {string} value | ||
* @param {function} callback | ||
* @private | ||
*/ | ||
function elements(using, value, callback) { | ||
var check = /class name|css selector|id|name|link text|partial link text|tag name|xpath/gi; | ||
if (!check.test(using)) { | ||
throw new Error('Please provide any of the following using strings as the first parameter: ' + | ||
'class name, css selector, id, name, link text, partial link text, tag name or xpath'); | ||
'class name, css selector, id, name, link text, partial link text, tag name, or xpath. Given: ' + using); | ||
} | ||
@@ -178,2 +240,29 @@ | ||
}, callback); | ||
} | ||
/** | ||
* Search for multiple elements on the page, starting from the identified element. The located element will be returned as a WebElement JSON objects. | ||
* | ||
* @link /session/:sessionId/element/:id/elements | ||
* @param {string} id ID of the element to route the command to. | ||
* @param {string} using The locator strategy to use. | ||
* @param {string} value The search target. | ||
* @param {function} [callback] Optional callback function to be called when the command finishes. | ||
* @api protocol | ||
*/ | ||
Actions.elementIdElements = function(id, using, value, callback) { | ||
var strategies = ['class name', 'css selector', 'id', 'name', 'link text', | ||
'partial link text', 'tag name', 'xpath']; | ||
using = using.toLocaleLowerCase(); | ||
if (strategies.indexOf(using) === -1) { | ||
throw new Error('Provided locating strategy is not supported: ' + | ||
using + '. It must be one of the following:\n' + | ||
strategies.join(', ')); | ||
} | ||
return postRequest('/element/' + id + '/elements', { | ||
using: using, | ||
value: value | ||
}, callback); | ||
}; | ||
@@ -297,3 +386,3 @@ | ||
/** | ||
* Determine if an OPTION element, or an INPUT element of type checkbox or radiobutton is currently selected. | ||
* Determine if an OPTION element, or an INPUT element of type checkbox or radio button is currently selected. | ||
* | ||
@@ -996,6 +1085,6 @@ * @link /session/:sessionId/element/:id/selected | ||
Actions.dismiss_alert = Actions.dismissAlert; | ||
/** | ||
* Gets the text of the log type specified | ||
* Gets the text of the log type specified | ||
* | ||
@@ -1023,3 +1112,3 @@ * @link /session/:sessionId/log | ||
}; | ||
///////////////////////////////////////////////////////////////////////////// | ||
@@ -1026,0 +1115,0 @@ // Helpers |
@@ -11,2 +11,3 @@ /*! | ||
var Assertion = require('./assertion.js'); | ||
var Page = require('../page-object/page.js'); | ||
@@ -95,14 +96,15 @@ module.exports = new (function() { | ||
*/ | ||
function loadAssertions() { | ||
client.api.assert = {}; | ||
function loadAssertions(parent) { | ||
parent = parent || client.api; | ||
parent.assert = {}; | ||
if (client.options.start_session) { | ||
client.api.verify = {}; | ||
parent.verify = {}; | ||
} | ||
for (var prop in assertModule) { | ||
if (assertModule.hasOwnProperty(prop)) { | ||
client.api.assert[prop] = (function(prop) { | ||
parent.assert[prop] = (function(prop) { | ||
return makeAssertion(prop, true); | ||
})(prop); | ||
if (client.options.start_session) { | ||
client.api.verify[prop] = (function (prop) { | ||
parent.verify[prop] = (function (prop) { | ||
return makeAssertion(prop, false); | ||
@@ -117,4 +119,4 @@ })(prop); | ||
loadAssertionFiles(dirPath, client.api.assert, true); | ||
loadAssertionFiles(dirPath, client.api.verify, false); | ||
loadAssertionFiles(dirPath, parent.assert, true); | ||
loadAssertionFiles(dirPath, parent.verify, false); | ||
} | ||
@@ -174,7 +176,8 @@ } | ||
*/ | ||
function loadProtocolActions() { | ||
function loadProtocolActions(parent) { | ||
parent = parent || client.api; | ||
var protocol = require('./../api/protocol.js')(client); | ||
var actions = Object.keys(protocol); | ||
actions.forEach(function(command) { | ||
addCommand(command, protocol[command], client.api, client.api); | ||
addCommand(command, protocol[command], client.api, parent); | ||
}); | ||
@@ -184,20 +187,32 @@ } | ||
/** | ||
* Loads the composite commands defined by nightwatch | ||
* Loads all the composite commands defined by nightwatch | ||
*/ | ||
function loadClientCommands() { | ||
// adding element specific commands | ||
function loadAllCommands(parent) { | ||
loadElementCommands(parent); | ||
loadClientCommands(parent); | ||
loadCommandFiles(client.api, parent, true); | ||
} | ||
/** | ||
* Loads the element composite commands defined by nightwatch | ||
*/ | ||
function loadElementCommands(parent) { | ||
parent = parent || client.api; | ||
var elementCommands = require('./../api/element-commands.js')(client); | ||
var entries = Object.keys(elementCommands); | ||
var entries = Object.keys(elementCommands); | ||
entries.forEach(function(command) { | ||
addCommand(command, elementCommands[command], client.api, client.api); | ||
addCommand(command, elementCommands[command], client.api, parent); | ||
}); | ||
} | ||
// adding client specific commands | ||
/** | ||
* Loads all the client commands defined by nightwatch | ||
*/ | ||
function loadClientCommands(parent) { | ||
parent = parent || client.api; | ||
var clientCommands = require('./../api/client-commands.js')(client); | ||
entries = Object.keys(clientCommands); | ||
var entries = Object.keys(clientCommands); | ||
entries.forEach(function(command) { | ||
addCommand(command, clientCommands[command], client.api, client.api, true); | ||
addCommand(command, clientCommands[command], client.api, parent, true); | ||
}); | ||
loadCommandFiles(client.api); | ||
} | ||
@@ -208,20 +223,25 @@ | ||
*/ | ||
function loadCommandFiles(context) { | ||
var relativePath = './../api/commands/'; | ||
var commandFiles = fs.readdirSync(path.join(__dirname, relativePath)); | ||
var commandName; | ||
var commandModule; | ||
function loadCommandFiles(context, parent, shouldLoadClientCommands) { | ||
var relativePaths = ['./../api/element-commands/']; | ||
if (shouldLoadClientCommands) { | ||
relativePaths.push('./../api/client-commands/'); | ||
} | ||
for (var i = 0, len = commandFiles.length; i < len; i++) { | ||
var ext = path.extname(commandFiles[i]); | ||
commandName = path.basename(commandFiles[i], ext); | ||
if (ext === '.js' && commandName.substr(0, 1) !== '_') { | ||
commandModule = require(__dirname + relativePath + commandFiles[i]); | ||
var m = loadCommandModule(commandModule, context); | ||
addCommand(commandName, m.command, m.context, client.api); | ||
relativePaths.forEach(function(relativePath) { | ||
var commandFiles = fs.readdirSync(path.join(__dirname, relativePath)); | ||
var commandName; | ||
var commandModule; | ||
for (var i = 0, len = commandFiles.length; i < len; i++) { | ||
var ext = path.extname(commandFiles[i]); | ||
commandName = path.basename(commandFiles[i], ext); | ||
if (ext === '.js' && commandName.substr(0, 1) !== '_') { | ||
commandModule = require(__dirname + relativePath + commandFiles[i]); | ||
var m = loadCommandModule(commandModule, context); | ||
addCommand(commandName, m.command, m.context, parent); | ||
} | ||
} | ||
} | ||
}); | ||
} | ||
/** | ||
@@ -233,5 +253,6 @@ * Loads a command module either specified as an object with a `command` method | ||
* @param {object} context | ||
* @param {object} [addt_props] | ||
* @returns {{command: function, context: *}} | ||
*/ | ||
function loadCommandModule(module, context, addt_props) { | ||
function loadCommandModule(module, context, addt_props, return_val) { | ||
var m = {command: null, context: context}; | ||
@@ -326,4 +347,5 @@ | ||
*/ | ||
function loadCustomAssertions(folder) { | ||
function loadCustomAssertions(folder, parent) { | ||
folder = folder || custom_assertions_path; | ||
parent = parent || client.api; | ||
if (!custom_assertions_path) { | ||
@@ -334,3 +356,3 @@ return; | ||
if (Array.isArray(folder)) { | ||
folder.forEach(function(folderName) { | ||
folder.forEach(function(folderName, parent) { | ||
loadCustomAssertions(folderName); | ||
@@ -341,22 +363,61 @@ }); | ||
loadCustomAssertionFolder(folder); | ||
loadCustomAssertionFolder(folder, parent); | ||
} | ||
function loadCustomAssertionFolder(folderName) { | ||
function loadCustomAssertionFolder(folderName, parent) { | ||
var absPath = path.resolve(folderName); | ||
loadAssertionFiles(absPath, client.api.assert, true); | ||
loadAssertionFiles(absPath, client.api.verify, false); | ||
loadAssertionFiles(absPath, parent.assert, true); | ||
loadAssertionFiles(absPath, parent.verify, false); | ||
} | ||
function loadExpectAssertions(parent) { | ||
parent = parent || client.api; | ||
var Expect = require('../api/expect.js')(client); | ||
var assertions = Object.keys(Expect); | ||
parent.expect = {}; | ||
assertions.forEach(function(assertion) { | ||
parent.expect[assertion] = function() { | ||
var args = Array.prototype.slice.call(arguments); | ||
var command = Expect[assertion].apply(parent, args); | ||
function F(element) { | ||
events.EventEmitter.call(this); | ||
this.client = client; | ||
this.element = element; | ||
} | ||
util.inherits(F, events.EventEmitter); | ||
F.prototype.command = function() { | ||
this.element.locate(this); | ||
return this; | ||
}; | ||
var instance = new F(command.element); | ||
CommandQueue.add(assertion, instance.command, instance, []); | ||
return command.expect; | ||
}; | ||
}); | ||
} | ||
/** | ||
* Loads page object files | ||
* @param {string} [dirPath] | ||
*/ | ||
function loadPageObjects() { | ||
if (!page_objects_path) { | ||
function loadPageObjects(dirPath) { | ||
if (!page_objects_path && !dirPath) { | ||
return; | ||
} | ||
client.api.page = {}; | ||
dirPath = dirPath || page_objects_path; | ||
client.api.page = client.api.page || {}; | ||
var absPath = path.resolve(page_objects_path); | ||
if (Array.isArray(dirPath)) { | ||
dirPath.forEach(function(folder) { | ||
loadPageObjects(folder); | ||
}); | ||
return; | ||
} | ||
var absPath = path.resolve(dirPath); | ||
var pageFiles = fs.readdirSync(absPath); | ||
@@ -385,2 +446,20 @@ | ||
if (useEnhancedModel(pageFnOrObject)) { | ||
var loadOntoPageObject = function(parent) { | ||
if (client.options.start_session) { | ||
loadElementCommands(parent); | ||
loadCommandFiles(client.api, parent, false); | ||
loadExpectAssertions(parent); | ||
// Alias | ||
parent.expect.section = parent.expect.element; | ||
} | ||
loadAssertions(parent); | ||
loadCustomCommands(null, parent); | ||
loadCustomAssertions(null, parent); | ||
return parent; | ||
}; | ||
pageFnOrObject.name = name; | ||
return new Page(pageFnOrObject, loadOntoPageObject, context, client); | ||
} | ||
return new (function() { | ||
@@ -396,2 +475,6 @@ if (typeof pageFnOrObject == 'function') { | ||
function useEnhancedModel(pageFnOrObject) { | ||
return typeof pageFnOrObject == 'object' && (pageFnOrObject.elements || pageFnOrObject.sections); | ||
} | ||
/** | ||
@@ -454,4 +537,5 @@ * | ||
loadProtocolActions(); | ||
loadClientCommands(); | ||
loadAllCommands(); | ||
loadPageObjects(); | ||
loadExpectAssertions(); | ||
} | ||
@@ -458,0 +542,0 @@ loadAssertions(); |
@@ -231,6 +231,6 @@ var util = require('util'); | ||
stacktrace : stacktrace, | ||
failure : failure !== '' ? failure : false | ||
elementNotFound : failure !== '' ? failure : false | ||
}); | ||
if (!passed && abortOnFailure && client.options.start_session) { | ||
if (!passed && abortOnFailure && client.options.start_session && client.options.skip_testcases_on_fail) { | ||
client.terminate(); | ||
@@ -237,0 +237,0 @@ } |
@@ -107,2 +107,3 @@ /*! | ||
this.options.start_session = this.options.start_session || (typeof this.options.start_session == 'undefined'); | ||
this.options.skip_testcases_on_fail = this.options.skip_testcases_on_fail || (typeof this.options.skip_testcases_on_fail == 'undefined'); | ||
@@ -213,5 +214,10 @@ this.api.options.log_screenshot_data = this.options.log_screenshot_data || | ||
if (this.options.end_session_on_fail) { | ||
this.enqueueCommand('session', ['delete'], function(result) { | ||
//this.enqueueCommand('session', ['delete'], function(result) { | ||
// self.finished(); | ||
//}); | ||
this.api.end(function() { | ||
self.finished(); | ||
}); | ||
this.queue.run(); | ||
} else { | ||
@@ -282,2 +288,6 @@ this.finished(); | ||
this.clearResult(); | ||
}; | ||
Nightwatch.prototype.clearResult = function() { | ||
this.errors.length = 0; | ||
@@ -330,17 +340,3 @@ this.results.passed = 0; | ||
if (screenshotContent && self.options.screenshots.enabled) { | ||
var d = new Date(); | ||
var dateStamp = d.toLocaleString('en-GB', { | ||
weekday : 'narrow', | ||
year : 'numeric', | ||
month : '2-digit', | ||
day : '2-digit', | ||
timeZoneName : 'short', | ||
hour : '2-digit', | ||
minute : '2-digit', | ||
second : '2-digit', | ||
era : 'short' | ||
}).replace(/:/g,'').replace(/\s/g,'-').replace(/-\(.+?\)/,''); | ||
var fileNamePath = path.resolve(path.join(self.options.screenshots.path, 'ERROR_' + | ||
dateStamp + '.png')); | ||
var fileNamePath = Utils.getScreenshotFileName('ERROR_', self.options.screenshots.path); | ||
self.saveScreenshotToFile(fileNamePath, screenshotContent); | ||
@@ -547,2 +543,1 @@ result.lastScreenshotFile = fileNamePath; | ||
})(); | ||
@@ -146,2 +146,6 @@ /** | ||
// $ nightwatch --retries | ||
this.command('retries') | ||
.description('Retries failed or errored testcases up <n> times.'); | ||
// $ nightwatch -h | ||
@@ -162,2 +166,1 @@ // $ nightwatch --help | ||
})(); | ||
@@ -8,2 +8,4 @@ var fs = require('fs'); | ||
var Selenium = require('../selenium.js'); | ||
var ChildProcess = require('./child-process.js'); | ||
var ErrorHandler = require('./errorhandler.js'); | ||
@@ -65,2 +67,4 @@ var SETTINGS_DEPRECTED_VAL = './settings.json'; | ||
this.argv.env = this.argv.env || 'default'; | ||
// reading the settings file | ||
@@ -77,2 +81,4 @@ try { | ||
} | ||
this.settings.output = this.settings.output || typeof this.settings.output == 'undefined'; | ||
} catch (ex) { | ||
@@ -86,6 +92,2 @@ Logger.error(ex); | ||
isParallelMode : function() { | ||
return process.env.__NIGHTWATCH_PARALLEL_MODE === '1'; | ||
}, | ||
/** | ||
@@ -122,2 +124,3 @@ * Looks for pattern ${VAR_NAME} in settings | ||
} | ||
try { | ||
@@ -153,32 +156,4 @@ var fullPath = path.resolve(this.settings.globals_path); | ||
/** | ||
* | ||
* @param {object|null} err | ||
* @param {object} results | ||
* @param {function} finished | ||
*/ | ||
globalErrorHandler : function(err, results, finished) { | ||
finished = finished || function() {}; | ||
if (results && results.errors) { | ||
console.log(results.errmessages.join('\n')); | ||
} | ||
if (err) { | ||
Logger.enable(); | ||
if (!err.message) { | ||
err.message = 'There was an error while running the test.'; | ||
} | ||
this.logError(err); | ||
finished(false); | ||
process.exit(1); | ||
} else { | ||
var result = true; | ||
if (results.failed || results.errors) { | ||
result = false; | ||
} | ||
finished(result); | ||
} | ||
singleTestRun: function () { | ||
return typeof this.argv.test == 'string'; | ||
}, | ||
@@ -192,4 +167,3 @@ | ||
var testsource; | ||
if (typeof this.argv.test == 'string') { | ||
if (this.singleTestRun()) { | ||
testsource = (this.argv.test.indexOf(process.cwd()) === -1) ? | ||
@@ -210,3 +184,3 @@ path.join(process.cwd(), this.argv.test) : | ||
this.argv.testcase = null; | ||
this.logWarning('Option --testcase used without --test is ignored.'); | ||
ErrorHandler.logWarning('Option --testcase used without --test is ignored.'); | ||
} | ||
@@ -271,6 +245,6 @@ if (typeof this.argv.group == 'string') { | ||
console.error('There was an error while starting the Selenium server:'); | ||
self.globalErrorHandler({ | ||
ErrorHandler.handle({ | ||
message : error_out | ||
}); | ||
return; | ||
@@ -319,3 +293,4 @@ } | ||
testcase : self.argv.testcase, | ||
end_session_on_fail : self.endSessionOnFail | ||
end_session_on_fail : self.endSessionOnFail, | ||
retries : self.argv.retries | ||
}, function(err, results) { | ||
@@ -328,6 +303,6 @@ self.stopSelenium(function() { | ||
afterGlobal.call(globalsContext, function done() { | ||
self.globalErrorHandler(err, results, finished); | ||
ErrorHandler.handle(err, results, finished); | ||
}); | ||
} catch (ex) { | ||
self.globalErrorHandler(ex, results, finished); | ||
ErrorHandler.handle(ex, results, finished); | ||
} | ||
@@ -374,2 +349,5 @@ }); | ||
return this; | ||
} else if (this.parallelModeWorkers()) { | ||
this.setupParallelMode(null, done); | ||
return this; | ||
} | ||
@@ -382,5 +360,15 @@ | ||
setGlobalOutputOptions: function () { | ||
this.test_settings.output = this.test_settings.output || (this.settings.output && typeof this.test_settings.output == 'undefined'); | ||
this.test_settings.silent = this.test_settings.silent || typeof this.test_settings.silent == 'undefined'; | ||
if (this.argv.verbose) { | ||
this.test_settings.silent = false; | ||
} | ||
return this; | ||
}, | ||
/** | ||
* Sets the specific test settings for the specified environment | ||
* @param {string} env | ||
* @param {string} [env] | ||
* @returns {CliRunner} | ||
@@ -390,20 +378,15 @@ */ | ||
// picking the environment specific test settings | ||
this.test_settings = this.settings.test_settings[env]; | ||
this.test_settings.custom_commands_path = this.settings.custom_commands_path || ''; | ||
this.test_settings.custom_assertions_path = this.settings.custom_assertions_path || ''; | ||
this.test_settings.page_objects_path = this.settings.page_objects_path || ''; | ||
this.test_settings = env && this.settings.test_settings[env] || {}; | ||
if (env) { | ||
this.test_settings.custom_commands_path = this.settings.custom_commands_path || ''; | ||
this.test_settings.custom_assertions_path = this.settings.custom_assertions_path || ''; | ||
this.test_settings.page_objects_path = this.settings.page_objects_path || ''; | ||
this.inheritFromDefaultEnv(); | ||
this.updateTestSettings(); | ||
// read the external globals, if any | ||
this.readExternalGlobals(); | ||
this.inheritFromDefaultEnv(); | ||
this.updateTestSettings(); | ||
this.readExternalGlobals(); | ||
} | ||
this.test_settings.output = this.test_settings.output || this.settings.output || | ||
(typeof this.test_settings.output == 'undefined' && typeof this.settings.output == 'undefined'); | ||
this.setGlobalOutputOptions(); | ||
this.test_settings.silent = this.test_settings.silent || typeof this.test_settings.silent == 'undefined'; | ||
if (this.argv.verbose) { | ||
this.test_settings.silent = false; | ||
} | ||
if (typeof this.argv.skipgroup == 'string') { | ||
@@ -447,3 +430,3 @@ this.test_settings.skipgroup = this.argv.skipgroup.split(','); | ||
// overwrite selenium settings per environment | ||
if (this.test_settings.selenium && typeof (this.test_settings.selenium) == 'object') { | ||
if (Utils.isObject(this.test_settings.selenium)) { | ||
for (var prop in this.test_settings.selenium) { | ||
@@ -487,3 +470,3 @@ this.settings.selenium[prop] = this.test_settings.selenium[prop]; | ||
var deprecationNotice = function(propertyName, newSettingName) { | ||
self.logWarning('DEPRECATION NOTICE: Property ' + propertyName + ' is deprecated since v0.5. Please' + | ||
ErrorHandler.logWarning('DEPRECATION NOTICE: Property ' + propertyName + ' is deprecated since v0.5. Please' + | ||
' use the "cli_args" object on the "selenium" property to define "' + newSettingName + '". E.g.:'); | ||
@@ -521,3 +504,3 @@ var demoObj = '{\n' + | ||
mergeCliArgs : function() { | ||
if (this.test_settings.cli_args && typeof this.test_settings.cli_args == 'object') { | ||
if (Utils.isObject(this.test_settings.cli_args)) { | ||
for (var prop in this.test_settings.cli_args) { | ||
@@ -532,2 +515,14 @@ if (this.test_settings.cli_args.hasOwnProperty(prop)) { | ||
//////////////////////////////////////////////////////////////////////////////////// | ||
// Parallelism related | ||
//////////////////////////////////////////////////////////////////////////////////// | ||
isParallelMode : function() { | ||
return process.env.__NIGHTWATCH_PARALLEL_MODE === '1'; | ||
}, | ||
parallelModeWorkers: function () { | ||
return !this.isParallelMode() && !this.singleTestRun() && (this.settings.test_workers === true || | ||
Utils.isObject(this.settings.test_workers) && this.settings.test_workers.enabled); | ||
}, | ||
/** | ||
@@ -544,6 +539,6 @@ * Enables parallel execution mode | ||
this.startSelenium(function() { | ||
self.startChildProcesses(envs, function(o, code) { | ||
self.startChildProcesses(envs, function(code) { | ||
self.stopSelenium(function() { | ||
if (done) { | ||
done(o, code); | ||
done(self.childProcessOutput, code); | ||
} | ||
@@ -557,23 +552,5 @@ if (code) { | ||
return this; | ||
}, | ||
/** | ||
* Returns an array of cli arguments to be passed to the child process, | ||
* based on the args passed to the main process | ||
* @returns {Array} | ||
*/ | ||
getChildProcessArgs : function(mainModule) { | ||
var args = [mainModule]; | ||
for (var i = 2; i < process.argv.length; i++) { | ||
if (process.argv[i] == '-e' || process.argv[i] == '--env') { | ||
i++; | ||
} else { | ||
args.push(process.argv[i]); | ||
} | ||
} | ||
return args; | ||
}, | ||
getAvailableColors : function () { | ||
@@ -607,149 +584,123 @@ var availColors = [ | ||
startChildProcesses : function(envs, doneCallback) { | ||
var execFile = require('child_process').execFile, child, self = this; | ||
var mainModule = process.mainModule.filename; | ||
doneCallback = doneCallback || function() {}; | ||
var availColors = this.getAvailableColors(); | ||
var prevIndex = 0; | ||
var output = {}; | ||
var globalExitCode = 0; | ||
var processStartDelay = this.settings.parallel_process_delay || 10; | ||
var writeToSdtout = function(data, item, index) { | ||
data = data.replace(/^\s+|\s+$/g, ''); | ||
output[item] = output[item] || []; | ||
this.childProcessOutput = {}; | ||
ChildProcess.prevIndex = 0; | ||
var env_output = ''; | ||
var color_pair = availColors[index%4]; | ||
if (prevIndex !== index) { | ||
prevIndex = index; | ||
if (self.settings.live_output) { | ||
env_output += '\n'; | ||
} | ||
} | ||
var args = this.getChildProcessArgs(envs); | ||
if (self.settings.disable_colors) { | ||
env_output += ' ' + item + ' '; | ||
} else { | ||
env_output += Logger.colors[color_pair[1]](' ' + item + ' ', | ||
Logger.colors.background[color_pair[0]]); | ||
} | ||
if (envs === null) { | ||
this.startTestWorkers(availColors, args, doneCallback); | ||
return this; | ||
} | ||
if (self.settings.live_output) { | ||
env_output += ' ' + data; | ||
} else { | ||
env_output += '\t' + data + '\n'; | ||
} | ||
this.startEnvChildren(envs, availColors, args, doneCallback); | ||
}, | ||
if (self.settings.live_output) { | ||
console.log(env_output); | ||
startEnvChildren : function(envs, availColors, args, doneCallback) { | ||
var self = this; | ||
envs.forEach(function(environment, index) { | ||
self.childProcessOutput[environment] = []; | ||
var childArgs = args.slice(); | ||
childArgs.push('--env', environment); | ||
var child = new ChildProcess(environment, index, self.childProcessOutput[environment], self.settings, childArgs); | ||
child.setLabel(environment + ' environment'); | ||
self.runningProcesses[child.itemKey] = child; | ||
self.runningProcesses[child.itemKey].run(availColors, function(output, exitCode) { | ||
if (self.processesRunning() === 0) { | ||
if (!self.settings.live_output) { | ||
self.printChildProcessOutput(); | ||
} | ||
doneCallback(exitCode); | ||
} | ||
}); | ||
}); | ||
}, | ||
getChildProcessArgs : function(envs) { | ||
var childProcessArgs = []; | ||
for (var i = 2; i < process.argv.length; i++) { | ||
if (envs && (process.argv[i] == '-e' || process.argv[i] == '--env')) { | ||
i++; | ||
} else { | ||
output[item].push(env_output); | ||
childProcessArgs.push(process.argv[i]); | ||
} | ||
}; | ||
} | ||
envs.forEach(function(item, index) { | ||
var cliArgs = self.getChildProcessArgs(mainModule); | ||
cliArgs.push('--env', item, '--parallel-mode'); | ||
var env = process.env; | ||
var itemKey = self.getChildProcessEnvKey(item, index); | ||
return childProcessArgs; | ||
}, | ||
setTimeout(function() { | ||
env.__NIGHTWATCH_PARALLEL_MODE = 1; | ||
env.__NIGHTWATCH_ENV = item; | ||
env.__NIGHTWATCH_ENV_KEY = itemKey; | ||
startTestWorkers : function(availColors, args, doneCallback) { | ||
var workerCount = this.getTestWorkersCount(); | ||
var source = this.getTestSource(); | ||
child = execFile(process.execPath, cliArgs, { | ||
cwd : process.cwd(), | ||
encoding: 'utf8', | ||
env : env | ||
}, function (error, stdout, stderr) {}); | ||
this.initTestSettings(); | ||
var self = this; | ||
self.runningProcesses[itemKey] = true; | ||
Runner.readPaths(source, this.test_settings, function(error, modulePaths) { | ||
if (error) { | ||
ErrorHandler.handle(error, null, doneCallback); | ||
return; | ||
} | ||
console.log('Started child process for env:', | ||
self.settings.disable_colors ? (' ' + itemKey + ' ') : (Logger.colors.yellow(' ' + itemKey + ' ', Logger.colors.background.black)), '\n'); | ||
var remaining = modulePaths.length; | ||
Utils.processAsyncQueue(workerCount, modulePaths, function(modulePath, index, next) { | ||
var filename = path.basename(modulePath, '.js'); | ||
child.stdout.on('data', function (data) { | ||
writeToSdtout(data, itemKey, index); | ||
}); | ||
self.childProcessOutput[filename] = []; | ||
child.stderr.on('data', function (data) { | ||
writeToSdtout(data, itemKey, index); | ||
}); | ||
var childArgs = args.slice(); | ||
childArgs.push('--test', modulePath); | ||
var child = new ChildProcess(filename, index, self.childProcessOutput[filename], self.settings, childArgs); | ||
child.setLabel(Utils.getModuleKey(modulePath, self.settings.src_folders, modulePaths)); | ||
self.runningProcesses[child.itemKey] = child; | ||
self.runningProcesses[child.itemKey].run(availColors, function (output, exitCode) { | ||
remaining -=1; | ||
if (remaining > 0) { | ||
next(); | ||
} else { | ||
if (!self.settings.live_output) { | ||
self.printChildProcessOutput(); | ||
} | ||
child.on('close', function(code) { | ||
if (!self.processesRunning()) { | ||
doneCallback(output, globalExitCode); | ||
doneCallback(exitCode); | ||
} | ||
}); | ||
}); | ||
}); | ||
}, | ||
child.on('exit', function (code) { | ||
if (code) { | ||
globalExitCode = 2; | ||
} | ||
if (!self.settings.live_output) { | ||
var child_output = output[itemKey] || ''; | ||
for (var i = 0; i < child_output.length; i++) { | ||
process.stdout.write(child_output[i]); | ||
} | ||
console.log(''); | ||
} | ||
self.runningProcesses[itemKey] = false; | ||
}); | ||
}, index * processStartDelay); | ||
printChildProcessOutput : function() { | ||
var self = this; | ||
Object.keys(this.childProcessOutput).forEach(function(environment) { | ||
self.childProcessOutput[environment].forEach(function(output) { | ||
process.stdout.write(output + '\n'); | ||
}); | ||
}); | ||
}, | ||
processesRunning : function() { | ||
for (var item in this.runningProcesses) { | ||
if (this.runningProcesses.hasOwnProperty(item) && this.runningProcesses[item]) { | ||
return true; | ||
} | ||
getTestWorkersCount : function() { | ||
var workers = 1; | ||
if (this.settings.test_workers === true || this.settings.test_workers.workers === 'auto') { | ||
workers = require('os').cpus().length; | ||
} else if ('number' === typeof this.settings.test_workers.workers) { | ||
workers = this.settings.test_workers.workers; | ||
} | ||
return false; | ||
}, | ||
getChildProcessEnvKey : function(env, index) { | ||
return env + '_' + (index+1); | ||
return workers; | ||
}, | ||
logWarning : function(message) { | ||
console.warn(Logger.colors.brown(message)); | ||
}, | ||
logError : function(err) { | ||
if (!err) { | ||
return; | ||
} | ||
var util = require('util'); | ||
console.error(''); | ||
var stackTrace = err && err.stack; | ||
if (!stackTrace) { | ||
var data; | ||
if (err.message) { | ||
data = err.data; | ||
err = err.message; | ||
processesRunning : function() { | ||
var running = 0; | ||
for (var item in this.runningProcesses) { | ||
if (this.runningProcesses.hasOwnProperty(item) && this.runningProcesses[item].processRunning) { | ||
running += 1; | ||
} | ||
if (typeof err == 'string') { | ||
process.stderr.write(Logger.colors.red(err)); | ||
if (data) { | ||
if (typeof data == 'object' && Object.keys(data).length > 0) { | ||
data = util.inspect(data); | ||
} | ||
process.stderr.write(data + '\n'); | ||
} | ||
process.stderr.write('\n'); | ||
} else { | ||
console.error(err); | ||
} | ||
return; | ||
} | ||
var parts = stackTrace.split('\n'); | ||
process.stderr.write(Logger.colors.red(parts.shift()) + '\n'); | ||
process.stderr.write(parts.join('\n') + '\n\n'); | ||
return running; | ||
} | ||
@@ -756,0 +707,0 @@ }; |
@@ -57,2 +57,28 @@ var util = require('util'); | ||
ClientManager.prototype.publishTestResults = function(testcase, results, errors) { | ||
if (!this['@client'].api.currentTest) { | ||
return this; | ||
} | ||
var currentTestSuite = this['@client'].api.currentTest.results; | ||
currentTestSuite.passed += results.passed; | ||
currentTestSuite.failed += results.failed; | ||
currentTestSuite.errors += results.errors; | ||
currentTestSuite.skipped += results.skipped; | ||
currentTestSuite.tests += results.tests.length; | ||
currentTestSuite.testcases = currentTestSuite.testcases || {}; | ||
currentTestSuite.testcases[testcase] = { | ||
passed : results.passed, | ||
failed : results.failed, | ||
errors : results.errors, | ||
skipped : results.skipped, | ||
tests : results.tests.length, | ||
assertions : results.tests | ||
}; | ||
return this; | ||
}; | ||
ClientManager.prototype.results = function(type, value) { | ||
@@ -66,2 +92,6 @@ if (typeof value == 'undefined') { | ||
ClientManager.prototype.clearResult = function() { | ||
return this['@client'].clearResult(); | ||
}; | ||
ClientManager.prototype.terminated = function() { | ||
@@ -72,3 +102,3 @@ return this['@client'].terminated; | ||
ClientManager.prototype.print = function(startTime) { | ||
return this['@client'].printResult.call(this['@client'], startTime); | ||
return this['@client'].printResult(startTime); | ||
}; | ||
@@ -75,0 +105,0 @@ |
@@ -92,8 +92,12 @@ var fs = require('fs'); | ||
if (this.globalResults.passed > 0 && this.globalResults.errors === 0 && this.globalResults.failed === 0) { | ||
var allSkipped = Object.keys(modulekeys.modules).length === results.steps.length; | ||
if ((this.globalResults.passed > 0 || allSkipped) && this.globalResults.errors === 0 && this.globalResults.failed === 0) { | ||
if (!showSummary) { | ||
return; | ||
} | ||
console.log(Logger.colors.green('OK. ' + this.globalResults.passed), | ||
'total assertions passed. (' + Utils.formatElapsedTime(elapsedTime) + ')'); | ||
var count = this.globalResults.passed; | ||
var message = (count > 1 ? ' total assertions' : ' assertion') + ' passed.'; | ||
console.log(Logger.colors.green('OK. ' + count) + message + ' (' + Utils.formatElapsedTime(elapsedTime) + ')'); | ||
} else { | ||
@@ -113,3 +117,3 @@ var skipped = ''; | ||
console.log(Logger.colors.light_red('TEST FAILURE:'), errorsMsg + Logger.colors.red(this.globalResults.failed) + | ||
' assertions failed, ' + Logger.colors.green(this.globalResults.passed) + ' passed', '(' + Utils.formatElapsedTime(elapsedTime) + ')' + skipped); | ||
' assertions failed, ' + Logger.colors.green(this.globalResults.passed) + ' passed', '(' + Utils.formatElapsedTime(elapsedTime) + ')' + skipped); | ||
@@ -130,5 +134,5 @@ this.printSummary(); | ||
test.assertions.forEach(function(a) { | ||
if (a.failure !== false) { | ||
if (a.elementNotFound !== false) { | ||
if (this.options.start_session) { | ||
console.log(' ' + a.message + ' - ' + a.failure); | ||
console.log(' ' + a.message + ' - ' + a.elementNotFound); | ||
} else { | ||
@@ -135,0 +139,0 @@ console.log(' ' + a.stacktrace.split('\n').join('\n ')); |
@@ -79,5 +79,6 @@ var Walk = require('./walk.js'); | ||
results.steps.length > 1)) { | ||
testSuite.printResult(time); | ||
testSuite.printResult(time); | ||
} | ||
}).run().then(function onTestSuiteResolved(testResults) { | ||
var testSuite = globalResults.modules[moduleKey]; | ||
@@ -88,2 +89,3 @@ testSuite.skipped = testResults.steps; | ||
testSuite.tests = Object.keys(testSuite.completed).length + (testSuite.skipped && testSuite.skipped.length || 0); | ||
var failures = 0; | ||
@@ -106,21 +108,7 @@ var errors = 0; | ||
/** | ||
* | ||
* @param {Array} testSource | ||
* @param {object} opts | ||
* @param {object} additionalOpts | ||
* @param {function} finishCallback | ||
*/ | ||
this.run = function runner(testSource, opts, additionalOpts, finishCallback) { | ||
opts.parallelMode = process.env.__NIGHTWATCH_PARALLEL_MODE == '1'; | ||
if (opts.parallelMode) { | ||
opts.currentEnv = process.env.__NIGHTWATCH_ENV_KEY; | ||
} | ||
opts.live_output = additionalOpts.live_output; | ||
opts.start_session = additionalOpts.start_session; | ||
opts.report_prefix = ''; | ||
this.readPaths = function(testSource, opts, cb) { | ||
var modulePaths = []; | ||
var joinedPaths = 0; | ||
var modulesCount = 0; | ||
globalStartTime = new Date().getTime(); | ||
finishCallback = finishCallback || function () {}; | ||
if (typeof testSource == 'string') { | ||
@@ -141,3 +129,2 @@ testSource = [testSource]; | ||
var modulePaths = [], joinedPaths = 0, modulesCount = 0; | ||
var errorMessage = ['No tests defined! using source folder:', fullPaths]; | ||
@@ -151,3 +138,3 @@ if (opts.tag_filter) { | ||
if (err.code == 'ENOENT') { | ||
finishCallback({ | ||
cb({ | ||
message : 'Cannot read source folder: ' + err.path | ||
@@ -157,3 +144,3 @@ }, false); | ||
} | ||
finishCallback(err, false); | ||
cb(err, false); | ||
return; | ||
@@ -170,51 +157,82 @@ } | ||
opts.modulesNo = modulePaths && modulePaths.length || 0; | ||
if (modulePaths.length === 0) { | ||
finishCallback({message: errorMessage.join(' ')}); | ||
cb({message: errorMessage.join(' ')}); | ||
return; | ||
} | ||
(function runNextModule() { | ||
var modulePath = modulePaths.shift(); | ||
var promise = runTestModule(modulePath, fullPaths, opts, additionalOpts, finishCallback); | ||
if (promise === null) { | ||
return; | ||
} | ||
cb(null, modulePaths, fullPaths); | ||
} | ||
}, opts); | ||
}; | ||
promise.then(function(testResults) { | ||
if (modulePaths.length) { | ||
setTimeout(function() { | ||
runNextModule(); | ||
}, 100); | ||
} else { | ||
var reporter = new Reporter(globalResults, testResults, globalStartTime, { | ||
output_folder : additionalOpts.output_folder, | ||
filename_prefix : opts.report_prefix, | ||
globals : opts.globals, | ||
reporter : additionalOpts.reporter, | ||
start_session : opts.start_session | ||
}); | ||
/** | ||
* | ||
* @param {Array} testSource | ||
* @param {object} opts | ||
* @param {object} additionalOpts | ||
* @param {function} finishCallback | ||
*/ | ||
this.run = function runner(testSource, opts, additionalOpts, finishCallback) { | ||
opts.parallelMode = process.env.__NIGHTWATCH_PARALLEL_MODE == '1'; | ||
if (opts.parallelMode) { | ||
opts.currentEnv = process.env.__NIGHTWATCH_ENV_KEY; | ||
} | ||
opts.live_output = additionalOpts.live_output; | ||
opts.start_session = additionalOpts.start_session; | ||
opts.report_prefix = ''; | ||
if (opts.output) { | ||
reporter.printTotalResults(globalResults, testResults); | ||
} | ||
globalStartTime = new Date().getTime(); | ||
finishCallback = finishCallback || function () {}; | ||
reporter.save().then(function() { | ||
reporter.globalReporter(opts.globals)(globalResults, function() { | ||
finishCallback(null, globalResults); | ||
}); | ||
}, function(err) { | ||
console.log(Logger.colors.yellow('Output folder doesn\'t exist and cannot be created.')); | ||
console.log(err.stack); | ||
finishCallback(null); | ||
}); | ||
} | ||
}, function(err) { | ||
finishCallback(err, globalResults); | ||
}); | ||
})(); | ||
this.readPaths(testSource, opts, function (err, modulePaths, fullPaths) { | ||
if (err) { | ||
finishCallback(err, false); | ||
return; | ||
} | ||
}, opts); | ||
(function runNextModule() { | ||
var modulePath = modulePaths.shift(); | ||
var promise = runTestModule(modulePath, fullPaths, opts, additionalOpts, finishCallback); | ||
if (promise === null) { | ||
return; | ||
} | ||
promise.then(function(testResults) { | ||
if (modulePaths.length) { | ||
setTimeout(function() { | ||
runNextModule(); | ||
}, 100); | ||
} else { | ||
var reporter = new Reporter(globalResults, testResults, globalStartTime, { | ||
output_folder : additionalOpts.output_folder, | ||
filename_prefix : opts.report_prefix, | ||
globals : opts.globals, | ||
reporter : additionalOpts.reporter, | ||
start_session : opts.start_session | ||
}); | ||
if (opts.output) { | ||
reporter.printTotalResults(globalResults, testResults); | ||
} | ||
reporter.save().then(function() { | ||
reporter.globalReporter(opts.globals)(globalResults, function() { | ||
finishCallback(null, globalResults); | ||
}); | ||
}, function(err) { | ||
console.log(Logger.colors.yellow('Output folder doesn\'t exist and cannot be created.')); | ||
console.log(err.stack); | ||
finishCallback(null); | ||
}); | ||
} | ||
}, function(err) { | ||
finishCallback(err, globalResults); | ||
}); | ||
})(); | ||
}); | ||
processListener(finishCallback); | ||
}; | ||
})(); |
@@ -48,3 +48,6 @@ /** | ||
SeleniumServer.prototype.start = function() { | ||
process.stdout.write(Logger.colors.light_purple('Starting selenium server' + (this.settings.parallelMode ? ' in parallel mode' : '') +'... ')); | ||
if (this.settings.output) { | ||
process.stdout.write(Logger.colors.light_purple('Starting selenium server' + (this.settings.parallelMode ? ' in parallel mode' : '') +'... ')); | ||
} | ||
this.setCliArgs(); | ||
@@ -51,0 +54,0 @@ this.process = child_process.spawn('java', this.cliOpts); |
@@ -7,5 +7,7 @@ var path = require('path'); | ||
function TestCase(suite, testFn) { | ||
function TestCase(suite, testFn, numRetries, maxRetries) { | ||
this.suite = suite; | ||
this.testFn = testFn; | ||
this.numRetries = numRetries; | ||
this.maxRetries = maxRetries; | ||
} | ||
@@ -18,4 +20,9 @@ | ||
process.stdout.write('\n'); | ||
console.log((opts.parallelMode && !opts.live_output ? 'Results for: ' : 'Running: '), | ||
Logger.colors.green(this.testFn)); | ||
if (this.numRetries > 0) { | ||
console.log('Retrying (' + this.numRetries + '/' + this.maxRetries + '): ', | ||
Logger.colors.red(this.testFn)); | ||
} else { | ||
console.log((opts.parallelMode && !opts.live_output ? 'Results for: ' : 'Running: '), | ||
Logger.colors.green(this.testFn)); | ||
} | ||
} | ||
@@ -22,0 +29,0 @@ return this; |
@@ -33,2 +33,3 @@ var path = require('path'); | ||
this.options = opts; | ||
this.maxRetries = addtOpts.retries; | ||
this.suiteName = Utils.getTestSuiteName(this.module.moduleKey); | ||
@@ -136,2 +137,4 @@ this.setupClient(); | ||
var doneFn = self.adaptDoneCallback(done, 'afterEach', deferred); | ||
self.client.publishTestResults(self.currentTest, self.module.get().results, errors); | ||
if (expectedArgs < 2) { | ||
@@ -194,2 +197,6 @@ // user has only supplied the done callback argument (pre v0.6 behaviour), e.g.: | ||
TestSuite.prototype.clearResult = function(startTime) { | ||
return this.client.clearResult(); | ||
}; | ||
TestSuite.prototype.runTestSuiteModule = function() { | ||
@@ -214,18 +221,5 @@ var self = this; | ||
TestSuite.prototype.onTestCaseFinished = function(results, errors, time) { | ||
var elapsedTime = (time/1000).toPrecision(4); | ||
this.testResults.passed += results.passed; | ||
this.testResults.failed += results.failed; | ||
this.testResults.errors += results.errors; | ||
this.testResults.skipped += results.skipped; | ||
this.testResults.tests += results.tests.length; | ||
this.testResults.time += time; | ||
this.testResults.testcases[this.currentTest] = { | ||
passed : results.passed, | ||
failed : results.failed, | ||
errors : results.errors, | ||
skipped : results.skipped, | ||
tests : results.tests.length, | ||
time : elapsedTime | ||
}; | ||
this.testResults.testcases[this.currentTest] = this.testResults.testcases[this.currentTest] || {}; | ||
this.testResults.testcases[this.currentTest].time = (time/1000).toPrecision(4); | ||
this.emit('testcase:finished', results, errors, time); | ||
@@ -247,3 +241,2 @@ }; | ||
this.setCurrentTest(); | ||
var self = this; | ||
@@ -254,4 +247,20 @@ deffered = deffered || Q.defer(); | ||
var testCase = new TestCase(this, this.currentTest); | ||
testCase.print().run().then(function(response) { | ||
deffered = this.runTestCase(this.currentTest, deffered, 0); | ||
} else { | ||
deffered.resolve(); | ||
} | ||
return deffered.promise; | ||
}; | ||
TestSuite.prototype.runTestCase = function(currentTest, deffered, numRetries) { | ||
var self = this; | ||
var testCase = new TestCase(this, currentTest, numRetries, this.maxRetries); | ||
testCase.print().run().then(function(response) { | ||
if ((response.results.failed || response.results.errors) && numRetries < self.maxRetries) { | ||
numRetries++; | ||
self.clearResult(); | ||
self.runTestCase(currentTest, deffered, numRetries); | ||
} else { | ||
self.onTestCaseFinished(response.results, response.errors, response.time); | ||
@@ -266,10 +275,8 @@ | ||
} | ||
}, function(error) { | ||
deffered.reject(error); | ||
}); | ||
} else { | ||
deffered.resolve(); | ||
} | ||
} | ||
}, function(error) { | ||
deffered.reject(error); | ||
}); | ||
return deffered.promise; | ||
return deffered; | ||
}; | ||
@@ -396,2 +403,2 @@ | ||
module.exports = TestSuite; | ||
module.exports = TestSuite; |
@@ -0,1 +1,2 @@ | ||
var path = require('path'); | ||
var Util = module.exports = {}; | ||
@@ -56,1 +57,102 @@ | ||
}; | ||
var formatRegExp = /%[sdj%]/g; | ||
/** | ||
* A smaller version of util.format that doesn't support json and | ||
* if a placeholder is missing, it is omitted instead of appended | ||
* | ||
* @param f | ||
* @returns {string} | ||
*/ | ||
Util.format = function format(f) { | ||
var i = 1; | ||
var args = arguments; | ||
var len = args.length; | ||
return String(f).replace(formatRegExp, function(x) { | ||
if (x === '%%') { | ||
return '%'; | ||
} | ||
if (i >= len) { | ||
return x; | ||
} | ||
switch (x) { | ||
case '%s': | ||
return String(args[i++]); | ||
case '%d': | ||
return Number(args[i++]); | ||
default: | ||
return x; | ||
} | ||
}); | ||
}; | ||
Util.getScreenshotFileName = function(prefix, screenshots_path) { | ||
prefix = prefix.replace(/\s/g, '-').replace(/"|'/g, ''); | ||
var d = new Date(); | ||
var dateParts = d.toString().replace(/:/g,'').split(' '); | ||
dateParts.shift(); | ||
dateParts.pop(); | ||
var dateStamp = dateParts.join('-'); | ||
return path.resolve(path.join(screenshots_path, prefix + '_' + dateStamp + '.png')); | ||
}; | ||
Util.isObject = function(obj) { | ||
return (typeof obj == 'object') && (obj !== null); | ||
}; | ||
Util.processAsyncQueue = function(concurrency, files, cb) { | ||
var maxWorkers = Math.min(concurrency, files.length); | ||
var queue = []; | ||
var add = function(item) { | ||
queue.push(item); | ||
}; | ||
var workers = 0; | ||
var index = 0; | ||
var next = function() { | ||
workers -= 1; | ||
process(); | ||
}; | ||
for (var i = 0; i < files.length; i++) { | ||
add(files[i]); | ||
} | ||
var process = function() { | ||
while (workers < maxWorkers) { | ||
workers += 1; | ||
if (queue.length) { | ||
var item = queue.shift(); | ||
cb(item, index++, next); | ||
} | ||
} | ||
}; | ||
process(); | ||
}; | ||
Util.getModuleKey = function(filePath, srcFolders, fullPaths) { | ||
var modulePathParts = filePath.split(path.sep); | ||
var diffInFolder = ''; | ||
var folder = ''; | ||
var parentFolder = ''; | ||
var moduleName = modulePathParts.pop(); | ||
filePath = modulePathParts.join(path.sep); | ||
if (srcFolders) { | ||
for (var i = 0; i < srcFolders.length; i++) { | ||
folder = path.resolve(srcFolders[i]); | ||
if (fullPaths.length > 1) { | ||
parentFolder = folder.split(path.sep).pop(); | ||
} | ||
if (filePath.indexOf(folder) === 0) { | ||
diffInFolder = filePath.substring(folder.length + 1); | ||
break; | ||
} | ||
} | ||
} | ||
return path.join(parentFolder, diffInFolder, moduleName); | ||
}; |
{ | ||
"name": "nightwatch", | ||
"description": "A node.js bindings implementation for selenium 2.0/webdriver", | ||
"version": "0.6.15", | ||
"version": "0.7.0", | ||
"author": { | ||
@@ -13,8 +13,8 @@ "name": "Andrei Rusu", | ||
"type": "MIT", | ||
"url": "https://raw.github.com/beatfactor/nightwatch/master/LICENSE" | ||
"url": "https://raw.github.com/nightwatchjs/nightwatch/master/LICENSE" | ||
}, | ||
"bugs": "https://github.com/beatfactor/nightwatch/issues", | ||
"bugs": "https://github.com/nightwatchjs/nightwatch/issues", | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:beatfactor/nightwatch.git" | ||
"url": "git@github.com:nightwatchjs/nightwatch.git" | ||
}, | ||
@@ -26,3 +26,4 @@ "dependencies": { | ||
"optimist": ">=0.3.5", | ||
"q": "^1.1.2" | ||
"q": "^1.1.2", | ||
"chai-nightwatch": "~0.1.x" | ||
}, | ||
@@ -29,0 +30,0 @@ "devDependencies": { |
@@ -19,3 +19,3 @@ # Nightwatch | ||
```sh | ||
$ git clone git@github.com:beatfactor/nightwatch.git | ||
$ git clone git@github.com:nightwatchjs/nightwatch.git | ||
$ cd nightwatch | ||
@@ -35,2 +35,2 @@ $ npm install | ||
### Setup Guides | ||
Browser specific setup and usage guides along with debugging instructions can be found on the [**Wiki**](https://github.com/beatfactor/nightwatch/wiki). | ||
Browser specific setup and usage guides along with debugging instructions can be found on the [**Wiki**](https://github.com/nightwatchjs/nightwatch/wiki). |
@@ -7,3 +7,6 @@ { | ||
"globals_path" : "./globals.js", | ||
"test_workers" : { | ||
"enabled" : false | ||
}, | ||
"output" : false, | ||
"selenium" : { | ||
@@ -10,0 +13,0 @@ "start_process" : false, |
@@ -71,2 +71,7 @@ { | ||
"url" : "/wd/hub/session/1352110219202/element", | ||
"postdata" : "{\"using\":\"css selector\",\"value\":\"#signupSection\"}", | ||
"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\"}", | ||
@@ -76,2 +81,7 @@ "response" : "{\"sessionId\":\"1352110219202\",\"status\":0,\"value\":{\"ELEMENT\":\"0\"},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":604376696}" | ||
{ | ||
"url" : "/wd/hub/session/1352110219202/element/0/element", | ||
"postdata" : "{\"using\":\"css selector\",\"value\":\"#helpBtn\"}", | ||
"response" : "{\"sessionId\":\"1352110219202\",\"status\":0,\"value\":{\"ELEMENT\":\"1\"},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":604376696}" | ||
}, | ||
{ | ||
"url" : "/wd/hub/session/1352110219202/elements", | ||
@@ -92,2 +102,12 @@ "postdata" : "{\"using\":\"css selector\",\"value\":\"#weblogin\"}", | ||
{ | ||
"url" : "/wd/hub/session/1352110219202/elements", | ||
"postdata" : "{\"using\":\"css selector\",\"value\":\"#signupSection\"}", | ||
"response" : "{\"sessionId\":\"1352110219202\",\"status\":0,\"value\":[{\"ELEMENT\":\"0\"},{\"ELEMENT\":\"1\"}],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":604376696}" | ||
}, | ||
{ | ||
"url" : "/wd/hub/session/1352110219202/element/0/elements", | ||
"postdata" : "{\"using\":\"css selector\",\"value\":\"#helpBtn\"}", | ||
"response" : "{\"sessionId\":\"1352110219202\",\"status\":0,\"value\":[{\"ELEMENT\":\"1\"}],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":604376696}" | ||
}, | ||
{ | ||
"url" : "/wd/hub/session/1352110219202/buttondown", | ||
@@ -94,0 +114,0 @@ "postdata" : "{\"button\":0}", |
@@ -20,9 +20,15 @@ var nightwatch = require('../index.js'); | ||
return nightwatch.client(opts).start().once('error', function() { | ||
if (callback) { | ||
callback(); | ||
} | ||
process.exit(); | ||
}); | ||
return nightwatch.client(opts) | ||
.on('selenium:session_create', function() { | ||
if (callback) { | ||
callback(); | ||
} | ||
}) | ||
.once('error', function() { | ||
if (callback) { | ||
callback(); | ||
} | ||
process.exit(); | ||
}).start(); | ||
} | ||
}; |
@@ -32,8 +32,10 @@ try { | ||
'src', | ||
'src/expect', | ||
'src/runner', | ||
'src/protocol', | ||
'src/http', | ||
'src/index', | ||
'src/assertions', | ||
'src/commands', | ||
'src/protocol', | ||
'src/http', | ||
'src/index' | ||
'src/page-object' | ||
], options, function(err) { | ||
@@ -40,0 +42,0 @@ setTimeout(function() { |
@@ -11,2 +11,3 @@ var MockServer = require('mockserver'); | ||
var client = this.client.api; | ||
MockServer.addMock({ | ||
@@ -13,0 +14,0 @@ 'url' : '/wd/hub/session/1352110219202/element/0/clear', |
@@ -170,2 +170,3 @@ var os = require('os'); | ||
eq(client.options.use_xpath, true); | ||
eq(client.options.skip_testcases_on_fail, true); | ||
eq(client.api.launchUrl, '/home'); | ||
@@ -172,0 +173,0 @@ eq(client.api.launch_url, '/home'); |
@@ -594,3 +594,11 @@ | ||
}); | ||
var CliRunner = require('../../../'+ BASE_PATH +'/../lib/runner/cli/clirunner.js'); | ||
mockery.registerMock('./errorhandler.js', { | ||
handle : function(err) { | ||
test.equals(err.message, 'Server already running.'); | ||
test.equals(runner.settings.parallelMode, true); | ||
test.done(); | ||
} | ||
}); | ||
var CliRunner = require('../../../'+ BASE_PATH + '/../lib/runner/cli/clirunner.js'); | ||
var runner = new CliRunner({ | ||
@@ -605,10 +613,4 @@ config : './nightwatch.json', | ||
runner.globalErrorHandler = function(err) { | ||
test.equals(err.message, 'Server already running.'); | ||
test.equals(runner.settings.parallelMode, true); | ||
test.done(); | ||
}; | ||
runner.startSelenium(); | ||
} | ||
}; |
@@ -1,2 +0,1 @@ | ||
var BASE_PATH = process.env.NIGHTWATCH_COV ? 'lib-cov' : 'lib'; | ||
@@ -28,4 +27,4 @@ var util = require('util'); | ||
setTimeout(function() { | ||
this.emit('close'); | ||
this.emit('exit'); | ||
this.emit('close'); | ||
}.bind(this), 11); | ||
@@ -41,2 +40,7 @@ }; | ||
}); | ||
mockery.registerMock('os', { | ||
cpus : function() { | ||
return [0, 1]; | ||
} | ||
}); | ||
@@ -50,2 +54,3 @@ callback(); | ||
mockery.disable(); | ||
process.env.__NIGHTWATCH_PARALLEL_MODE = null; | ||
callback(); | ||
@@ -65,3 +70,3 @@ }, | ||
test.equals(code, 0); | ||
test.deepEqual(output, {}); | ||
test.deepEqual(output, { 'default': [], mixed: [] }); | ||
test.equals(self.allArgs.length, 2); | ||
@@ -73,4 +78,4 @@ test.equals(self.allArgs[0][2], 'default'); | ||
test.ok('mixed_2' in runner.runningProcesses); | ||
test.equals(runner.runningProcesses['default_1'], false); | ||
test.equals(runner.runningProcesses['mixed_2'], false); | ||
test.equals(runner.runningProcesses['default_1'].processRunning, false); | ||
test.equals(runner.runningProcesses['mixed_2'].processRunning, false); | ||
test.done(); | ||
@@ -97,4 +102,4 @@ }); | ||
test.ok('mixed_2' in runner.runningProcesses); | ||
test.equals(runner.runningProcesses['mixed_1'], false); | ||
test.equals(runner.runningProcesses['mixed_2'], false); | ||
test.equals(runner.runningProcesses['mixed_1'].processRunning, false); | ||
test.equals(runner.runningProcesses['mixed_2'].processRunning, false); | ||
test.done(); | ||
@@ -104,3 +109,74 @@ }); | ||
test.ok(runner.parallelMode); | ||
}, | ||
testParallelExecutionWithWorkersDefaults : function(test) { | ||
var self = this; | ||
var CliRunner = require('../../../' + BASE_PATH + '/../lib/runner/cli/clirunner.js'); | ||
var runner = new CliRunner({ | ||
config : './extra/parallelism.json' | ||
}); | ||
runner.init(function() { | ||
test.ok(runner.isParallelMode()); | ||
test.equals(self.allArgs.length, 17); | ||
test.ok('sample_1' in runner.runningProcesses); | ||
test.ok('sampleSingleTest_2' in runner.runningProcesses); | ||
var child = runner.runningProcesses.sample_1; | ||
test.deepEqual(child.env_output, []); | ||
test.ok(child.mainModule.length > 0); | ||
test.strictEqual(child.index, 0); | ||
test.equal(child.startDelay, 10); | ||
test.equal(child.environment, 'sample'); | ||
test.deepEqual(child.settings, runner.settings); | ||
test.strictEqual(child.globalExitCode, 0); | ||
test.equal(child.args.length, 2); | ||
test.equal(child.args[0], '--test'); | ||
test.done(); | ||
}); | ||
test.ok(runner.settings.test_workers); | ||
test.ok(runner.parallelModeWorkers()); | ||
}, | ||
testParallelExecutionWithWorkersAuto : function(test) { | ||
var self = this; | ||
var CliRunner = require('../../../' + BASE_PATH + '/../lib/runner/cli/clirunner.js'); | ||
var runner = new CliRunner({ | ||
config : './extra/parallelism-auto.json' | ||
}); | ||
runner.init(function() { | ||
test.ok(runner.isParallelMode()); | ||
test.equals(self.allArgs.length, 17); | ||
test.done(); | ||
}); | ||
test.deepEqual(runner.settings.test_workers, { | ||
enabled : true, | ||
workers : 'auto' | ||
}); | ||
test.ok(runner.parallelModeWorkers()); | ||
}, | ||
testParallelExecutionWithWorkersCount : function(test) { | ||
var self = this; | ||
var CliRunner = require('../../../' + BASE_PATH + '/../lib/runner/cli/clirunner.js'); | ||
var runner = new CliRunner({ | ||
config : './extra/parallelism-count.json' | ||
}); | ||
runner.init(function() { | ||
test.ok(runner.isParallelMode()); | ||
test.equals(self.allArgs.length, 17); | ||
test.done(); | ||
}); | ||
test.deepEqual(runner.settings.test_workers, { | ||
enabled : true, | ||
workers : 6 | ||
}); | ||
test.ok(runner.parallelModeWorkers()); | ||
} | ||
}; |
@@ -49,2 +49,22 @@ | ||
testRunRetries : function(test) { | ||
test.expect(11); | ||
var testsPath = path.join(process.cwd(), '/sampletests/withfailures'); | ||
this.Runner.run([testsPath], { | ||
seleniumPort : 10195, | ||
silent : true, | ||
output : false, | ||
globals : { | ||
test : test | ||
} | ||
}, { | ||
output_folder : false, | ||
start_session : true, | ||
retries: 1 | ||
}, function(err, results) { | ||
test.equals(err, null); | ||
test.done(); | ||
}); | ||
}, | ||
'test run multiple sources and same module name' : function(test) { | ||
@@ -537,3 +557,21 @@ var srcFolders = [ | ||
}); | ||
}, | ||
testRunCurrentTestInAfterEach : function(test) { | ||
var testsPath = path.join(process.cwd(), '/sampletests/withaftereach/sampleSingleTest.js'); | ||
this.Runner.run([testsPath], { | ||
seleniumPort : 10195, | ||
silent : true, | ||
output : false, | ||
globals : { | ||
test : test | ||
} | ||
}, { | ||
output_folder : false, | ||
start_session : true | ||
}, function(err, results) { | ||
test.equals(err, null); | ||
test.done(); | ||
}); | ||
} | ||
}; |
@@ -94,7 +94,27 @@ var BASE_PATH = process.env.NIGHTWATCH_COV ? 'lib-cov' : 'lib'; | ||
test.ok(typeof client.api.page == 'object'); | ||
test.ok('SimplePage' in client.api.page); | ||
client.api.page.SimplePage(test); | ||
test.ok('SimplePageFn' in client.api.page); | ||
test.ok('simplePageObj' in client.api.page); | ||
client.api.page.SimplePageFn(test); | ||
var simplePage = client.api.page.simplePageObj(); | ||
test.equals(typeof simplePage, 'object'); | ||
}, | ||
testAddPageObjectArrayPath : function(test) { | ||
var client = this.client; | ||
client.on('selenium:session_create', function(sessionId) { | ||
test.done(); | ||
}); | ||
client.options.page_objects_path = ['./extra/pageobjects', './extra/otherPageobjects']; | ||
Api.init(client); | ||
Api.loadPageObjects(); | ||
test.ok(typeof client.api.page == 'object'); | ||
test.ok('simplePageObj' in client.api.page); | ||
test.ok('otherPage' in client.api.page); | ||
}, | ||
testAddCustomAssertion : function(test) { | ||
@@ -101,0 +121,0 @@ var client = this.client; |
@@ -23,2 +23,17 @@ var BASE_PATH = process.env.NIGHTWATCH_COV ? 'lib-cov' : 'lib'; | ||
testElementIdElement : function(test) { | ||
var client = this.client; | ||
var protocol = this.protocol; | ||
this.client.on('selenium:session_create', function(sessionId) { | ||
var command = protocol.elementIdElement('0', 'id', '#weblogin', function callback() { | ||
test.done(); | ||
}); | ||
test.equal(command.request.method, 'POST'); | ||
test.equal(command.data, '{"using":"id","value":"#weblogin"}'); | ||
test.equal(command.request.path, '/wd/hub/session/1352110219202/element/0/element'); | ||
}); | ||
}, | ||
testElementPlural : function(test) { | ||
@@ -38,2 +53,17 @@ var protocol = this.protocol; | ||
testElementIdElementPlural : function(test) { | ||
var client = this.client; | ||
var protocol = this.protocol; | ||
this.client.on('selenium:session_create', function(sessionId) { | ||
var command = protocol.elementIdElements('0', 'id', '#weblogin', function callback() { | ||
test.done(); | ||
}); | ||
test.equal(command.request.method, 'POST'); | ||
test.equal(command.data, '{"using":"id","value":"#weblogin"}'); | ||
test.equal(command.request.path, '/wd/hub/session/1352110219202/element/0/elements'); | ||
}); | ||
}, | ||
testElementActive : function(test) { | ||
@@ -40,0 +70,0 @@ var protocol = this.protocol; |
Sorry, the diff of this file is not supported yet
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
12812472
292
34491
6
180
12
+ Addedchai-nightwatch@~0.1.x
+ Addedassertion-error@1.0.0(transitive)
+ Addedchai-nightwatch@0.1.1(transitive)
+ Addeddeep-eql@0.1.3(transitive)
+ Addedtype-detect@0.1.1(transitive)