protractor
Advanced tools
Comparing version 0.18.1 to 0.19.0
@@ -136,3 +136,3 @@ #!/usr/bin/env node | ||
global.element = browser.element; | ||
global.by = protractor.By; | ||
global.by = global.By = protractor.By; | ||
global.list = list; | ||
@@ -139,0 +139,0 @@ |
@@ -0,1 +1,73 @@ | ||
# 0.19.1 | ||
_Note: Major version 0 releases are for initial development, and backwards incompatible changes may be introduced at any time._ | ||
## Features | ||
- ([77393d0](https://github.com/angular/protractor/commit/77393d08343ef16ddc2b8042e187c9d68fe7bf2f)), ([6848180](https://github.com/angular/protractor/commit/68481801d506941ebf00fab71f87be510c7a87ba)), ([cca82ca](https://github.com/angular/protractor/commit/cca82caab6ae444b368eebe040a69967d774737e)) | ||
feat(runner/launcher): major runner updates to allow multiple capabilities | ||
Adding simultaneous runner capability (grid-style), refactoring launch/runner init system, and | ||
abstracting out configParser module. | ||
- ([642de06](https://github.com/angular/protractor/commit/642de06e8bbabf82c7b8e0a64a280df5c4daf01c)) | ||
feat(protractor): add removeMockModule method | ||
- ([88c339f](https://github.com/angular/protractor/commit/88c339fc1d392717a0a5b8265806934b40158c5f)) | ||
feat(runner): add adapter for cucumber.js | ||
Conflicts: | ||
lib/runner.js | ||
## Bug Fixes | ||
- ([8924bbc](https://github.com/angular/protractor/commit/8924bbca9e8f04073a29534bf16b0867a1ede7a0)) | ||
fix(cli): convert capabilities arguments to dot-notation for WebDriver compatibility | ||
- ([a96d32f](https://github.com/angular/protractor/commit/a96d32f44a92ba9447fc843bc0aca7b91b777635)) | ||
fix(webdriver-manager): upcase in IE download url | ||
The url for the Win32 version of the IEDriverServer is apparently case sensitive: _win32_ vs | ||
_Win32_ | ||
## Breaking Changes | ||
- ([05eb42b](https://github.com/angular/protractor/commit/05eb42bb482c7cb36b48af1a86210afc442aa112)) | ||
refactor(locators): moves scope in locators to last argument | ||
scope defaults to document, and is an optional argument so now be moved to the end. Came up from | ||
debugging and trying to use window.clientSideScripts.findInputs('username'); which failed. | ||
Refactored to match original intent. | ||
BREAKING CHANGE: anything relying on clientsidescripts should no longer pass | ||
element scope as first argument. | ||
Before: | ||
window.clientSideScripts.findInputs(document, 'username'); | ||
After: | ||
window.clientSideScripts.findInputs(document, 'username'); | ||
Also, any custom locators using addLocator will now break since the | ||
arguments order has chnaged. To migrate the code follow the example below: | ||
Before: | ||
var findMenuItem = function() { | ||
var domScope = arguments[0]; | ||
var myArg = arguments[1]; | ||
// balh blah blah | ||
}; | ||
by.addLocator('menuItem', findMenuItem); | ||
After: | ||
var findMenuItem = function() { | ||
var myArg = arguments[0]; | ||
var domScope = arguments[1]; | ||
// balh blah blah | ||
}; | ||
by.addLocator('menuItem', findMenuItem); | ||
Closes #497 | ||
# 0.18.1 | ||
@@ -2,0 +74,0 @@ _Note: Major version 0 releases are for initial development, and backwards incompatible changes may be introduced at any time._ |
@@ -40,2 +40,17 @@ Setting up your Browser | ||
Testing against multiple browsers | ||
--------------------------------- | ||
If you would like to test against multiple browsers at once, use the multiCapabilities configuration option. | ||
```javascript | ||
multiCapabilities: [{ | ||
'browserName': 'firefox' | ||
}, { | ||
'browserName': 'chrome' | ||
}] | ||
``` | ||
Protractor will run tests in parallel against each set of capabilities. Please note that if multiCapabilities is defined, the runner will ignore the `capabilities` configuration. | ||
PhantomJS | ||
@@ -50,4 +65,14 @@ ------------------------------------- | ||
// should be able to omit this property if phantomjs installed globally | ||
'phantomjs.binary.path':'./node_modules/phantomjs/bin/phantomjs' | ||
/* | ||
* Can be used to specify the phantomjs binary path. | ||
* This can generally be ommitted if you installed phantomjs globally. | ||
*/ | ||
'phantomjs.binary.path':'./node_modules/phantomjs/bin/phantomjs', | ||
/* | ||
* Command line arugments to pass to phantomjs. | ||
* Can be ommitted if no arguments need to be passed. | ||
* Acceptable cli arugments: https://github.com/ariya/phantomjs/wiki/API-Reference#wiki-command-line-options | ||
*/ | ||
'phantomjs.cli.args':['--logfile=PATH', '--loglevel=DEBUG'] | ||
} | ||
@@ -54,0 +79,0 @@ ``` |
@@ -64,4 +64,4 @@ Debugging Protractor Tests | ||
We use `browser.debugger();` instead of node's `debugger;` statement so that | ||
the test pauses after the get command has beenexecuted*. Using `debugger;` | ||
pauses the test after the get command isscheduled* but has not yet | ||
the test pauses after the get command has been executed. Using `debugger;` | ||
pauses the test after the get command is scheduled but has not yet | ||
been sent to the browser. | ||
@@ -80,2 +80,5 @@ | ||
// Should return the input element with model 'username'. | ||
// You can also limit the scope of the locator | ||
> window.clientSideScripts.findInputs('username', document.getElementById('#myEl')); | ||
``` | ||
@@ -110,3 +113,3 @@ | ||
./bin/elementexplorer.js <urL> | ||
node ./bin/elementexplorer.js <urL> | ||
@@ -150,3 +153,3 @@ This will load up the URL on webdriver and put the terminal into a REPL loop. | ||
stream.write(buf = new Buffer(data, 'base64')); | ||
stream.write(new Buffer(data, 'base64')); | ||
stream.end(); | ||
@@ -153,0 +156,0 @@ } |
/** | ||
* The command line interface for interacting with the Protractor runner. | ||
* It takes care of parsing the config file and command line options. | ||
* It takes care of parsing command line options. | ||
* | ||
@@ -27,3 +27,3 @@ * Values from command line options override values from the config. | ||
var path = require('path'); | ||
var runner = require('./runner.js'); | ||
var child = require('child_process'); | ||
var argv = require('optimist'). | ||
@@ -71,5 +71,29 @@ usage('Usage: protractor [options] [configFile]\n' + | ||
// WebDriver capabilities properties require dot notation, but optimist parses | ||
// that into an object. Re-flatten it. | ||
var flattenObject = function(obj) { | ||
var prefix = arguments[1] || ''; | ||
var out = arguments[2] || {}; | ||
for (var prop in obj) { | ||
if (obj.hasOwnProperty(prop)) { | ||
typeof obj[prop] === 'object' ? | ||
flattenObject(obj[prop], prefix + prop + '.', out) : | ||
out[prefix + prop] = obj[prop]; | ||
} | ||
} | ||
return out; | ||
}; | ||
// Any file names should be resolved relative to the current working directory. | ||
var processFilePatterns = function(list) { | ||
if (argv.capabilities) { | ||
argv.capabilities = flattenObject(argv.capabilities); | ||
} | ||
/** | ||
* Helper to resolve comma separated lists of file pattern strings relative to | ||
* the cwd. | ||
* | ||
* @private | ||
* @param {Array} list | ||
*/ | ||
var processFilePatterns_ = function(list) { | ||
var patterns = list.split(','); | ||
@@ -80,26 +104,12 @@ patterns.forEach(function(spec, index, arr) { | ||
return patterns; | ||
} | ||
}; | ||
if (argv.specs) { | ||
argv.specs = processFilePatterns(argv.specs); | ||
argv.specs = processFilePatterns_(argv.specs); | ||
} | ||
if (argv.exclude) { | ||
argv.exclude = processFilePatterns(argv.exclude); | ||
if (argv.excludes) { | ||
argv.excludes = processFilePatterns_(argv.excludes); | ||
} | ||
['seleniumServerJar', 'chromeDriver', 'onPrepare'].forEach(function(name) { | ||
if (argv[name]) { | ||
argv[name] = path.resolve(process.cwd(), argv[name]); | ||
} | ||
}); | ||
var configFilename = argv._[0]; | ||
if (configFilename) { | ||
var configPath = path.resolve(process.cwd(), configFilename); | ||
var config = require(configPath).config; | ||
config.configDir = path.dirname(configPath); | ||
runner.addConfig(config); | ||
} | ||
runner.addConfig(argv); | ||
runner.runOnce(); | ||
// Run the launcher | ||
require('./launcher').init(argv); |
@@ -33,4 +33,4 @@ /** | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The binding, e.g. {{cat.name}}. | ||
* arguments[0] {string} The binding, e.g. {{cat.name}}. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -40,4 +40,4 @@ * @return {Array.<Element>} The elements containing the binding. | ||
clientSideScripts.findBindings = function() { | ||
var using = arguments[0] || document; | ||
var binding = arguments[1]; | ||
var binding = arguments[0]; | ||
var using = arguments[1] || document; | ||
var bindings = using.getElementsByClassName('ng-binding'); | ||
@@ -62,5 +62,5 @@ var matches = []; | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[2] {number} The row index. | ||
* arguments[0] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[1] {number} The row index. | ||
* arguments[2] {Element} The scope of the search. | ||
* | ||
@@ -71,5 +71,5 @@ * @return {Array.<Element>} The row of the repeater, or an array of elements | ||
clientSideScripts.findRepeaterRows = function() { | ||
var using = arguments[0] || document; | ||
var repeater = arguments[1]; | ||
var index = arguments[2]; | ||
var repeater = arguments[0]; | ||
var index = arguments[1]; | ||
var using = arguments[2] || document; | ||
@@ -116,4 +116,4 @@ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[0] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -123,4 +123,4 @@ * @return {Array.<Element>} All rows of the repeater. | ||
clientSideScripts.findAllRepeaterRows = function() { | ||
var using = arguments[0] || document; | ||
var repeater = arguments[1]; | ||
var repeater = arguments[0]; | ||
var using = arguments[1] || document; | ||
@@ -161,6 +161,6 @@ var rows = []; | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[2] {number} The row index. | ||
* arguments[3] {string} The column binding, e.g. '{{cat.name}}'. | ||
* arguments[0] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[1] {number} The row index. | ||
* arguments[2] {string} The column binding, e.g. '{{cat.name}}'. | ||
* arguments[3] {Element} The scope of the search. | ||
* | ||
@@ -171,6 +171,6 @@ * @return {Array.<Element>} The element in an array. | ||
var matches = []; | ||
var using = arguments[0] || document; | ||
var repeater = arguments[1]; | ||
var index = arguments[2]; | ||
var binding = arguments[3]; | ||
var repeater = arguments[0]; | ||
var index = arguments[1]; | ||
var binding = arguments[2]; | ||
var using = arguments[3] || document; | ||
@@ -250,5 +250,5 @@ var rows = []; | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[2] {string} The column binding, e.g. '{{cat.name}}'. | ||
* arguments[0] {string} The text of the repeater, e.g. 'cat in cats'. | ||
* arguments[1] {string} The column binding, e.g. '{{cat.name}}'. | ||
* arguments[2] {Element} The scope of the search. | ||
* | ||
@@ -259,5 +259,5 @@ * @return {Array.<Element>} The elements in the column. | ||
var matches = []; | ||
var using = arguments[0] || document; | ||
var repeater = arguments[1]; | ||
var binding = arguments[2]; | ||
var repeater = arguments[0]; | ||
var binding = arguments[1]; | ||
var using = arguments[2] || document; | ||
@@ -336,4 +336,4 @@ var rows = []; | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The model name. | ||
* arguments[0] {string} The model name. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -343,4 +343,4 @@ * @return {Array.<Element>} The matching input elements. | ||
clientSideScripts.findInputs = function() { | ||
var using = arguments[0] || document; | ||
var model = arguments[1]; | ||
var model = arguments[0]; | ||
var using = arguments[1] || document; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
@@ -359,4 +359,4 @@ for (var p = 0; p < prefixes.length; ++p) { | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The model name. | ||
* arguments[0] {string} The model name. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -366,4 +366,4 @@ * @return {Array.<Element>} The matching elements. | ||
clientSideScripts.findByModel = function() { | ||
var using = arguments[0] || document; | ||
var model = arguments[1]; | ||
var model = arguments[0]; | ||
var using = arguments[1] || document; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
@@ -382,4 +382,4 @@ for (var p = 0; p < prefixes.length; ++p) { | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The exact text to match. | ||
* arguments[0] {string} The exact text to match. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -389,4 +389,4 @@ * @return {Array.<Element>} The matching elements. | ||
clientSideScripts.findByButtonText = function() { | ||
var using = arguments[0] || document; | ||
var searchText = arguments[1]; | ||
var searchText = arguments[0]; | ||
var using = arguments[1] || document; | ||
var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); | ||
@@ -413,4 +413,4 @@ var matches = []; | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The exact text to match. | ||
* arguments[0] {string} The exact text to match. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -420,4 +420,4 @@ * @return {Array.<Element>} The matching elements. | ||
clientSideScripts.findByPartialButtonText = function() { | ||
var using = arguments[0] || document; | ||
var searchText = arguments[1]; | ||
var searchText = arguments[0]; | ||
var using = arguments[1] || document; | ||
var elements = using.querySelectorAll('button, input[type="button"], input[type="submit"]'); | ||
@@ -442,7 +442,7 @@ var matches = []; | ||
/** | ||
/** | ||
* Find multiple select elements by model name. | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The model name. | ||
* arguments[0] {string} The model name. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -452,4 +452,4 @@ * @return {Array.<Element>} The matching select elements. | ||
clientSideScripts.findSelects = function() { | ||
var using = arguments[0] || document; | ||
var model = arguments[1]; | ||
var model = arguments[0]; | ||
var using = arguments[1] || document; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
@@ -468,4 +468,4 @@ for (var p = 0; p < prefixes.length; ++p) { | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {string} The model name. | ||
* arguments[0] {string} The model name. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -475,4 +475,4 @@ * @return {Array.<Element>} The matching select elements. | ||
clientSideScripts.findSelectedOptions = function() { | ||
var using = arguments[0] || document; | ||
var model = arguments[1]; | ||
var model = arguments[0]; | ||
var using = arguments[1] || document; | ||
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; | ||
@@ -491,4 +491,4 @@ for (var p = 0; p < prefixes.length; ++p) { | ||
* | ||
* arguments[0] {Element} The scope of the search. | ||
* arguments[1] {String} The model name. | ||
* arguments[0] {String} The model name. | ||
* arguments[1] {Element} The scope of the search. | ||
* | ||
@@ -498,4 +498,4 @@ * @return {Array.<Element>} An array of matching textarea elements. | ||
clientSideScripts.findTextareas = function() { | ||
var using = arguments[0] || document; | ||
var model = arguments[1]; | ||
var model = arguments[0]; | ||
var using = arguments[1] || document; | ||
@@ -502,0 +502,0 @@ var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:']; |
@@ -29,5 +29,4 @@ var util = require('util'); | ||
* the browser. This script will be passed an array of arguments | ||
* that begins with the element scoping the search, and then | ||
* contains any args passed into the locator. It should return | ||
* an array of elements. | ||
* that contains any args passed into the locator followed by the | ||
* element scoping the search. It should return an array of elements. | ||
*/ | ||
@@ -39,6 +38,6 @@ ProtractorBy.prototype.addLocator = function(name, script) { | ||
return driver.findElements( | ||
webdriver.By.js(script), using, varArgs); | ||
webdriver.By.js(script), varArgs, using); | ||
}, | ||
message: 'by.' + name + '("' + varArgs + '")' | ||
} | ||
}; | ||
}; | ||
@@ -57,3 +56,3 @@ }; | ||
webdriver.By.js(clientSideScripts.findBindings), | ||
using, bindingDescriptor); | ||
bindingDescriptor, using); | ||
}, | ||
@@ -74,3 +73,3 @@ message: 'by.binding("' + bindingDescriptor + '")' | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findSelects), using, model); | ||
webdriver.By.js(clientSideScripts.findSelects), model, using); | ||
}, | ||
@@ -90,3 +89,3 @@ message: 'by.select("' + model + '")' | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findSelectedOptions), using, model); | ||
webdriver.By.js(clientSideScripts.findSelectedOptions), model, using); | ||
}, | ||
@@ -107,3 +106,3 @@ message: 'by.selectedOption("' + model + '")' | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findInputs), using, model); | ||
webdriver.By.js(clientSideScripts.findInputs), model, using); | ||
}, | ||
@@ -123,3 +122,3 @@ message: 'by.input("' + model + '")' | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findByModel), using, model); | ||
webdriver.By.js(clientSideScripts.findByModel), model, using); | ||
}, | ||
@@ -139,3 +138,3 @@ message: 'by.model("' + model + '")' | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findByButtonText), using, searchText); | ||
webdriver.By.js(clientSideScripts.findByButtonText), searchText, using); | ||
}, | ||
@@ -155,3 +154,3 @@ message: 'by.buttonText("' + searchText + '")' | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findByPartialButtonText), using, searchText); | ||
webdriver.By.js(clientSideScripts.findByPartialButtonText), searchText, using); | ||
}, | ||
@@ -173,3 +172,3 @@ message: 'by.partialButtonText("' + searchText + '")' | ||
return driver.findElements( | ||
webdriver.By.js(clientSideScripts.findTextareas), using, model); | ||
webdriver.By.js(clientSideScripts.findTextareas), model, using); | ||
}, | ||
@@ -204,3 +203,3 @@ message: 'by.textarea("' + model + '")' | ||
webdriver.By.js(clientSideScripts.findAllRepeaterRows), | ||
using, repeatDescriptor); | ||
repeatDescriptor, using); | ||
}, | ||
@@ -213,3 +212,3 @@ message: 'by.repeater("' + repeatDescriptor + '")', | ||
webdriver.By.js(clientSideScripts.findRepeaterRows), | ||
using, repeatDescriptor, index); | ||
repeatDescriptor, index, using); | ||
}, | ||
@@ -222,3 +221,3 @@ message: 'by.repeater(' + repeatDescriptor + '").row("' + index + '")"', | ||
webdriver.By.js(clientSideScripts.findRepeaterElement), | ||
using, repeatDescriptor, index, binding); | ||
repeatDescriptor, index, binding, using); | ||
}, | ||
@@ -236,3 +235,3 @@ message: 'by.repeater("' + repeatDescriptor + '").row("' + index + | ||
webdriver.By.js(clientSideScripts.findRepeaterColumn), | ||
using, repeatDescriptor, binding); | ||
repeatDescriptor, binding, using); | ||
}, | ||
@@ -246,3 +245,3 @@ message: 'by.repeater("' + repeatDescriptor + '").column("' + binding + | ||
webdriver.By.js(clientSideScripts.findRepeaterElement), | ||
using, repeatDescriptor, index, binding); | ||
repeatDescriptor, index, binding, using); | ||
}, | ||
@@ -249,0 +248,0 @@ message: 'by.repeater("' + repeatDescriptor + '").column("' + |
@@ -19,3 +19,3 @@ var url = require('url'); | ||
*/ | ||
for (foo in webdriver) { | ||
for (var foo in webdriver) { | ||
exports[foo] = webdriver[foo]; | ||
@@ -64,3 +64,3 @@ } | ||
return base; | ||
} | ||
}; | ||
@@ -455,3 +455,3 @@ /** | ||
return originalFns[name].apply(element, arguments); | ||
} | ||
}; | ||
}); | ||
@@ -473,3 +473,3 @@ | ||
return this.findElement(locator); | ||
} | ||
}; | ||
@@ -504,3 +504,3 @@ /** | ||
return this.findElements(locator); | ||
} | ||
}; | ||
@@ -529,3 +529,3 @@ /** | ||
}); | ||
} | ||
}; | ||
@@ -649,2 +649,12 @@ /** | ||
/** | ||
* Remove a registered mock module. | ||
* @param {!string} name The name of the module to remove. | ||
*/ | ||
Protractor.prototype.removeMockModule = function (name) { | ||
var index = this.moduleNames_.indexOf(name); | ||
this.moduleNames_.splice(index, 1); | ||
this.moduleScripts_.splice(index, 1); | ||
}; | ||
/** | ||
* See webdriver.WebDriver.get | ||
@@ -744,3 +754,3 @@ * | ||
var clientSideScriptsList = []; | ||
for (script in clientSideScripts) { | ||
for (var script in clientSideScripts) { | ||
clientSideScriptsList.push( | ||
@@ -751,3 +761,3 @@ script + ': ' + clientSideScripts[script].toString()); | ||
this.driver.executeScript( | ||
'window.clientSideScripts = {' + clientSideScriptsList.join(', ') + '}') | ||
'window.clientSideScripts = {' + clientSideScriptsList.join(', ') + '}'); | ||
@@ -819,3 +829,3 @@ var flow = webdriver.promise.controlFlow(); | ||
return instance; | ||
} | ||
}; | ||
@@ -842,2 +852,2 @@ /** | ||
return lines.join('\n'); | ||
} | ||
}; |
@@ -1,216 +0,205 @@ | ||
'use strict'; | ||
var protractor = require('./protractor'), | ||
webdriver = require('selenium-webdriver'), | ||
ConfigParser = require('./configParser'), | ||
path = require('path'), | ||
util = require('util'), | ||
fs = require('fs'), | ||
q = require('q'); | ||
var util = require('util'); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var webdriver = require('selenium-webdriver'); | ||
var remote = require('selenium-webdriver/remote'); | ||
var chrome = require('selenium-webdriver/chrome'); | ||
var protractor = require('./protractor.js'); | ||
var SauceLabs = require('saucelabs'); | ||
var glob = require('glob'); | ||
/* | ||
* Runner is responsible for starting the execution of a test run and triggering | ||
* setup, teardown, managing config, etc through its various dependencies. | ||
* | ||
* @param {Object} config | ||
* @constructor | ||
*/ | ||
var Runner = function(config) { | ||
this.preparers_ = []; | ||
this.driverprovider_ = null; | ||
this.config_ = config; | ||
var server; | ||
var driver; | ||
var sessionId; | ||
var sauceAccount; | ||
// Default configuration. | ||
var config = { | ||
configDir: './', | ||
seleniumServerJar: null, | ||
seleniumArgs: [], | ||
seleniumPort: null, | ||
seleniumAddress: null, | ||
allScriptsTimeout: 11000, | ||
capabilities: { | ||
'browserName': 'chrome' | ||
}, | ||
rootElement: 'body', | ||
params: {}, | ||
framework: 'jasmine', | ||
jasmineNodeOpts: { | ||
isVerbose: false, | ||
showColors: true, | ||
includeStackTrace: true, | ||
stackFilter: protractor.filterStackTrace | ||
}, | ||
mochaOpts: { | ||
ui: 'bdd', | ||
reporter: 'list' | ||
} | ||
// Init | ||
this.loadDriverProvider_(config); | ||
}; | ||
/** | ||
* Merge config objects together. | ||
* Execute the Runner's test cases through Jasmine. | ||
* | ||
* @param {Object} into | ||
* @param {Object} from | ||
* | ||
* @return {Object} The 'into' config. | ||
* @private | ||
* @param {Array} specs Array of Directory Path Strings | ||
* @param done A callback for when tests are finished. | ||
*/ | ||
var merge = function(into, from) { | ||
for (var key in from) { | ||
if (into[key] instanceof Object && !(into[key] instanceof Array)) { | ||
merge(into[key], from[key]); | ||
} else { | ||
into[key] = from[key]; | ||
} | ||
} | ||
return into; | ||
Runner.prototype.runJasmine_ = function(specs, done) { | ||
var minijn = require('minijasminenode'), | ||
self = this; | ||
require('../jasminewd'); | ||
webdriver.promise.controlFlow().execute(function() { | ||
self.runTestPreparers_(); | ||
}).then(function() { | ||
var jasmineNodeOpts = self.config_.jasmineNodeOpts; | ||
var originalOnComplete = self.config_.onComplete; | ||
jasmineNodeOpts.onComplete = function(runner, log) { | ||
if (originalOnComplete) { | ||
originalOnComplete(runner, log); | ||
} | ||
done(runner.results()); | ||
}; | ||
minijn.addSpecs(specs); | ||
minijn.executeSpecs(jasmineNodeOpts); | ||
}); | ||
}; | ||
/** | ||
* Add the options in the parameter config to this runner instance. | ||
* Execute the Runner's test cases through Mocha. | ||
* | ||
* @param {Object} additionalConfig | ||
* @private | ||
* @param {Array} specs Array of Directory Path Strings | ||
* @param done A callback for when tests are finished. | ||
*/ | ||
var addConfig = function(additionalConfig) { | ||
// All filepaths should be kept relative to the current config location. | ||
// This will not affect absolute paths. | ||
['seleniumServerJar', 'chromeDriver', 'onPrepare'].forEach(function(name) { | ||
if (additionalConfig[name] && additionalConfig.configDir && | ||
typeof additionalConfig[name] === 'string') { | ||
additionalConfig[name] = | ||
path.resolve(additionalConfig.configDir, additionalConfig[name]); | ||
} | ||
}) | ||
merge(config, additionalConfig); | ||
}; | ||
Runner.prototype.runMocha_ = function(specs, done) { | ||
/** | ||
* Cleanup the driver and server after running tests. | ||
* | ||
* @param runner The Jasmine runner object. | ||
*/ | ||
var cleanUp = function(runner) { | ||
var passed = runner.results().failedCount === 0; | ||
var exitCode = passed ? 0 : 1; | ||
var exit = function(exitCode) { | ||
if (typeof config.onCleanUp === 'function') { | ||
config.onCleanUp(exitCode); | ||
} | ||
process.exit(exitCode); | ||
}; | ||
if (sauceAccount) { | ||
sauceAccount.updateJob(sessionId, {'passed': passed}, function(err) { | ||
if (err) { | ||
throw new Error( | ||
'Error updating Sauce pass/fail status: ' + util.inspect(err) | ||
); | ||
var Mocha = require('mocha'), | ||
mocha = new Mocha(this.config_.mochaOpts), | ||
self = this; | ||
// Mocha doesn't set up the ui until the pre-require event, so | ||
// wait until then to load mocha-webdriver adapters as well. | ||
mocha.suite.on('pre-require', function() { | ||
var mochaAdapters = require('selenium-webdriver/testing'); | ||
global.after = mochaAdapters.after; | ||
global.afterEach = mochaAdapters.afterEach; | ||
global.before = mochaAdapters.before; | ||
global.beforeEach = mochaAdapters.beforeEach; | ||
global.it = mochaAdapters.it; | ||
global.it.only = global.iit = mochaAdapters.it.only; | ||
global.it.skip = global.xit = mochaAdapters.xit; | ||
}); | ||
mocha.loadFiles(); | ||
webdriver.promise.controlFlow().execute(function() { | ||
self.runTestPreparers_(); | ||
}).then(function() { | ||
specs.forEach(function(file) { | ||
mocha.addFile(file); | ||
}); | ||
mocha.run(function(failures) { | ||
if (self.config_.onComplete) { | ||
self.config_.onComplete(); | ||
} | ||
exit(exitCode); | ||
var resolvedObj = { | ||
failedCount: failures | ||
}; | ||
done(resolvedObj); | ||
}); | ||
} else if (server) { | ||
util.puts('Shutting down selenium standalone server.'); | ||
server.stop().then(function() { | ||
exit(exitCode); | ||
}); | ||
} else { | ||
exit(exitCode); | ||
} | ||
}); | ||
}; | ||
/** | ||
* Sets up the Selenium server and returns a promise once the server is | ||
* ready to go. After this function is called, config.seleniumAddress | ||
* and config.capabilities are set up. | ||
* Execute the Runner's test cases through Cucumber. | ||
* | ||
* @return {webdriver.promise.Promise.<string>} A promise which resolves to | ||
* the value of the selenium address that will be used. | ||
* @private | ||
* @param {Array} specs Array of Directory Path Strings | ||
* @param done A callback for when tests are finished. | ||
*/ | ||
var setUpSelenium = function() { | ||
// TODO: This should not be tied to the webdriver promise loop, it should use | ||
// another promise system instead. | ||
var deferred = webdriver.promise.defer(); | ||
Runner.prototype.runCucumber_ = function(specs, done) { | ||
var Cucumber = require('cucumber'), | ||
self = this, | ||
execOptions = ['node', 'node_modules/.bin/cucumber-js'], | ||
cucumberResolvedRequire; | ||
if (config.sauceUser && config.sauceKey) { | ||
sauceAccount = new SauceLabs({ | ||
username: config.sauceUser, | ||
password: config.sauceKey | ||
}); | ||
} | ||
// Set up exec options for Cucumber | ||
execOptions = execOptions.concat(specs); | ||
if (self.config_.cucumberOpts) { | ||
var defaultChromedriver; | ||
if (config.chromeDriver) { | ||
if (!fs.existsSync(config.chromeDriver)) { | ||
if (fs.existsSync(config.chromeDriver + '.exe')) { | ||
config.chromeDriver += '.exe'; | ||
} else { | ||
throw 'Could not find chromedriver at ' + config.chromeDriver; | ||
// Process Cucumber Require param | ||
if (self.config_.cucumberOpts.require) { | ||
cucumberResolvedRequire = | ||
ConfigParser.resolveFilePatterns(self.config_.cucumberOpts.require); | ||
if (cucumberResolvedRequire && cucumberResolvedRequire.length) { | ||
execOptions.push('-r'); | ||
execOptions = execOptions.concat(cucumberResolvedRequire); | ||
} | ||
} | ||
} else { | ||
defaultChromedriver = path.resolve( | ||
__dirname, | ||
'../selenium/chromedriver'); | ||
if (fs.existsSync(defaultChromedriver)) { | ||
config.chromeDriver = defaultChromedriver; | ||
} else if (fs.existsSync(defaultChromedriver + '.exe')) { | ||
config.chromeDriver = defaultChromedriver + '.exe'; | ||
// Process Cucumber Tag param | ||
if (self.config_.cucumberOpts.tags) { | ||
execOptions.push('-t'); | ||
execOptions.push(self.config_.cucumberOpts.tags); | ||
} | ||
} | ||
// Priority | ||
// 1) if chromeOnly, use that | ||
// 2) if seleniumAddress is given, use that | ||
// 3) if a sauceAccount is given, use that. | ||
// 4) if a seleniumServerJar is specified, use that | ||
// 5) try to find the seleniumServerJar in protractor/selenium | ||
if (config.chromeOnly) { | ||
util.puts('Using ChromeDriver directly...'); | ||
deferred.fulfill(null); | ||
} else if (config.seleniumAddress) { | ||
util.puts('Using the selenium server at ' + config.seleniumAddress); | ||
deferred.fulfill(config.seleniumAddress); | ||
} else if (sauceAccount) { | ||
config.capabilities.username = config.sauceUser; | ||
config.capabilities.accessKey = config.sauceKey; | ||
if (!config.jasmineNodeOpts.defaultTimeoutInterval) { | ||
config.jasmineNodeOpts.defaultTimeoutInterval = 30 * 1000; | ||
// Process Cucumber Format param | ||
if (self.config_.cucumberOpts.format) { | ||
execOptions.push('-f'); | ||
execOptions.push(self.config_.cucumberOpts.format); | ||
} | ||
config.seleniumAddress = 'http://' + config.sauceUser + ':' + | ||
config.sauceKey + '@ondemand.saucelabs.com:80/wd/hub'; | ||
} | ||
cucumber = Cucumber.Cli(execOptions); | ||
util.puts('Using SauceLabs selenium server at ' + config.seleniumAddress); | ||
deferred.fulfill(config.seleniumAddress); | ||
} else { | ||
util.puts('Starting selenium standalone server...'); | ||
webdriver.promise.controlFlow().execute(function() { | ||
self.runTestPreparers_(); | ||
}).then(function() { | ||
if (!config.seleniumServerJar) { | ||
// Try to use the default location. | ||
var defaultStandalone = path.resolve(__dirname, | ||
'../selenium/selenium-server-standalone-' + | ||
require('../package.json').webdriverVersions.selenium + '.jar'); | ||
if (!fs.existsSync(defaultStandalone)) { | ||
throw new Error('Unable to start selenium. ' + | ||
'You must specify either a seleniumAddress, ' + | ||
'seleniumServerJar, or saucelabs account, or use webdriver-manager.'); | ||
cucumber.run(function(succeeded) { | ||
if (self.config_.onComplete) { | ||
self.config_.onComplete(); | ||
} | ||
var resolvedObj = { | ||
failedCount: succeeded ? 0 : 1 | ||
}; | ||
done(resolvedObj); | ||
}); | ||
}); | ||
}; | ||
/** | ||
* Internal helper for abstraction of polymorphic filenameOrFn properties. | ||
* @private | ||
* @param {Array} source The Array that we'll be iterating through | ||
* as we evaluate whether to require or execute each item. | ||
*/ | ||
Runner.prototype.runFilenamesOrFns_ = function(source) { | ||
var filenameOrFn; | ||
for (var i = 0; i < source.length; i++) { | ||
filenameOrFn = source[i]; | ||
if (filenameOrFn) { | ||
if (typeof filenameOrFn === 'function') { | ||
filenameOrFn(); | ||
} else if (typeof filenameOrFn === 'string') { | ||
require(path.resolve(this.config_.configDir, filenameOrFn)); | ||
} else { | ||
config.seleniumServerJar = defaultStandalone; | ||
// TODO - this is not generic. | ||
throw 'config.onPrepare must be a string or function'; | ||
} | ||
} else if (!fs.existsSync(config.seleniumServerJar)) { | ||
throw new Error('there\'s no selenium server jar at the specified ' + | ||
'location. Do you have the correct version?'); | ||
} | ||
} | ||
}; | ||
if (config.chromeDriver) { | ||
config.seleniumArgs.push( | ||
'-Dwebdriver.chrome.driver=' + config.chromeDriver); | ||
} | ||
server = new remote.SeleniumServer(config.seleniumServerJar, { | ||
args: config.seleniumArgs, | ||
port: config.seleniumPort | ||
}); | ||
/** | ||
* Registrar for testPreparers - executed right before tests run. | ||
* @public | ||
* @param {string/Fn} filenameOrFn | ||
*/ | ||
Runner.prototype.registerTestPreparer = function(filenameOrFn) { | ||
this.preparers_.push(filenameOrFn); | ||
}; | ||
server.start().then(function(url) { | ||
util.puts('Selenium standalone server started at ' + url); | ||
config.seleniumAddress = server.address(); | ||
deferred.fulfill(config.seleniumAddress); | ||
}); | ||
} | ||
return deferred.promise; | ||
/** | ||
* Executor of testPreparers | ||
* @private | ||
*/ | ||
Runner.prototype.runTestPreparers_ = function() { | ||
this.runFilenamesOrFns_(this.preparers_); | ||
}; | ||
@@ -220,189 +209,164 @@ | ||
/** | ||
* Resolve a list of file patterns into a list of individual file paths. | ||
* Grab driver provider based on type | ||
* @private | ||
* | ||
* @param {array} patterns | ||
* @param {boolean} opt_omitWarnings whether to omit did not match warnings | ||
* | ||
* @return {array} The resolved file paths. | ||
* Priority | ||
* 1) if chromeOnly, use that | ||
* 2) if seleniumAddress is given, use that | ||
* 3) if a sauceAccount is given, use that. | ||
* 4) if a seleniumServerJar is specified, use that | ||
* 5) try to find the seleniumServerJar in protractor/selenium | ||
*/ | ||
var resolveFilePatterns = function(patterns, opt_omitWarnings) { | ||
var resolvedFiles = []; | ||
Runner.prototype.loadDriverProvider_ = function() { | ||
var runnerPath; | ||
if (this.config_.chromeOnly) { | ||
runnerPath = './driverProviders/chrome.dp'; | ||
} else if (this.config_.seleniumAddress) { | ||
runnerPath = './driverProviders/hosted.dp'; | ||
} else if (this.config_.sauceUser && this.config_.sauceKey) { | ||
runnerPath = './driverProviders/sauce.dp'; | ||
} else if (this.config_.seleniumServerJar) { | ||
runnerPath = './driverProviders/local.dp'; | ||
} else { | ||
runnerPath = './driverProviders/local.dp'; | ||
} | ||
if (patterns) { | ||
for (var i = 0; i < patterns.length; ++i) { | ||
var matches = glob.sync(patterns[i], {cwd: config.configDir}); | ||
if (!matches.length && !opt_omitWarnings) { | ||
util.puts('Warning: pattern ' + patterns[i] + ' did not match any files.'); | ||
} | ||
for (var j = 0; j < matches.length; ++j) { | ||
resolvedFiles.push(path.resolve(config.configDir, matches[j])); | ||
} | ||
} | ||
this.driverprovider_ = require(runnerPath)(this.config_); | ||
}; | ||
/** | ||
* Responsible for cleaning up test run and exiting the process. | ||
* @private | ||
* @param {int} Standard unix exit code | ||
*/ | ||
Runner.prototype.exit_ = function(exitCode) { | ||
if (typeof this.config_.onCleanUp === 'function') { | ||
this.config_.onCleanUp(exitCode); | ||
} | ||
return resolvedFiles; | ||
}; | ||
/** | ||
* Set up webdriver and run the tests. Note that due to the current setup of | ||
* loading Jasmine and the test specs, this should only be run once. | ||
* Getter for the Runner config object | ||
* @public | ||
* @return {Object} config | ||
*/ | ||
Runner.prototype.getConfig = function() { | ||
return this.config_; | ||
}; | ||
/** | ||
* Sets up convenience globals for test specs | ||
* @private | ||
*/ | ||
Runner.prototype.setupGlobals_ = function(driver) { | ||
var browser = protractor.wrapDriver( | ||
driver, | ||
this.config_.baseUrl, | ||
this.config_.rootElement); | ||
browser.params = this.config_.params; | ||
protractor.setInstance(browser); | ||
// Export protractor to the global namespace to be used in tests. | ||
global.protractor = protractor; | ||
global.browser = browser; | ||
global.$ = browser.$; | ||
global.$$ = browser.$$; | ||
global.element = browser.element; | ||
global.by = global.By = protractor.By; | ||
}; | ||
/** | ||
* The primary workhorse interface. Kicks off the test running process. | ||
* | ||
* @return {webdriver.promise.Promise} A promise that will resolve | ||
* when the test run is finished. | ||
* @public | ||
*/ | ||
var runTests = function() { | ||
if (config.jasmineNodeOpts.specFolders) { | ||
throw new Error('Using config.jasmineNodeOpts.specFolders is deprecated ' + | ||
'since Protractor 0.6.0. Please switch to config.specs.'); | ||
} | ||
Runner.prototype.run = function() { | ||
var self = this, | ||
driver, | ||
specs, | ||
excludes, | ||
testResult; | ||
var resolvedExcludes = resolveFilePatterns(config.exclude, true); | ||
var resolvedSpecs = resolveFilePatterns(config.specs).filter(function (path) { | ||
return resolvedExcludes.indexOf(path) < 0; | ||
// Determine included and excluded specs based on file pattern. | ||
excludes = ConfigParser.resolveFilePatterns( | ||
this.config_.exclude, true, this.config_.configDir); | ||
specs = ConfigParser.resolveFilePatterns( | ||
this.config_.specs, false, this.config_.configDir).filter(function(path) { | ||
return excludes.indexOf(path) < 0; | ||
}); | ||
if (!resolvedSpecs.length) { | ||
if (!specs.length) { | ||
throw new Error('Spec patterns did not match any files.'); | ||
} | ||
// TODO: This should not be tied to the webdriver promise loop, it should use | ||
// another promise system instead. | ||
var runDeferred = webdriver.promise.defer(); | ||
this.registerTestPreparer(this.config_.onPrepare); | ||
if (config.chromeOnly) { | ||
var service = new chrome.ServiceBuilder(config.chromeDriver).build(); | ||
driver = chrome.createDriver( | ||
new webdriver.Capabilities(config.capabilities), service); | ||
} else { | ||
driver = new webdriver.Builder(). | ||
usingServer(config.seleniumAddress). | ||
withCapabilities(config.capabilities).build(); | ||
} | ||
// 1) Setup environment | ||
return this.driverprovider_.setupEnv().then(function() { | ||
driver = self.driverprovider_.getDriver(); | ||
return q.fcall(function() {}); | ||
driver.getSession().then(function(session) { | ||
driver.manage().timeouts().setScriptTimeout(config.allScriptsTimeout); | ||
// 2) Execute test cases | ||
}).then(function() { | ||
sessionId = session.getId(); | ||
var deferred = q.defer(); | ||
driver.manage().timeouts().setScriptTimeout(self.config_.allScriptsTimeout); | ||
self.setupGlobals_.bind(self)(driver); | ||
var browser = protractor.wrapDriver( | ||
driver, | ||
config.baseUrl, | ||
config.rootElement); | ||
browser.params = config.params; | ||
protractor.setInstance(browser); | ||
// Export protractor to the global namespace to be used in tests. | ||
global.protractor = protractor; | ||
global.browser = browser; | ||
global.$ = browser.$; | ||
global.$$ = browser.$$; | ||
global.element = browser.element; | ||
global.by = global.By = protractor.By; | ||
// Do the framework setup here so that jasmine and mocha globals are | ||
// available to the onPrepare function. | ||
var minijn, mocha; | ||
if (config.framework === 'jasmine') { | ||
minijn = require('minijasminenode'); | ||
require('../jasminewd'); | ||
minijn.addSpecs(resolvedSpecs); | ||
} else if (config.framework === 'mocha') { | ||
var Mocha = require('mocha'); | ||
mocha = new Mocha(config.mochaOpts); | ||
resolvedSpecs.forEach(function(file) { | ||
mocha.addFile(file); | ||
if (self.config_.framework === 'jasmine') { | ||
self.runJasmine_.bind(self)(specs, function(result) { | ||
deferred.resolve(result); | ||
}); | ||
// Mocha doesn't set up the ui until the pre-require event, so | ||
// wait until then to load mocha-webdriver adapters as well. | ||
mocha.suite.on('pre-require', function() { | ||
var mochaAdapters = require('selenium-webdriver/testing'); | ||
global.after = mochaAdapters.after; | ||
global.afterEach = mochaAdapters.afterEach; | ||
global.before = mochaAdapters.before; | ||
global.beforeEach = mochaAdapters.beforeEach; | ||
global.it = mochaAdapters.it; | ||
global.it.only = global.iit = mochaAdapters.it.only; | ||
global.it.skip = global.xit = mochaAdapters.xit; | ||
} else if (self.config_.framework === 'mocha') { | ||
self.runMocha_.bind(self)(specs, function(result) { | ||
deferred.resolve(result); | ||
}); | ||
mocha.loadFiles(); | ||
} else if (self.config_.framework === 'cucumber') { | ||
self.runCucumber_.bind(self)(specs, function(result) { | ||
deferred.resolve(result); | ||
}); | ||
} else if (self.config_.framework === 'simpleprint') { | ||
console.log('Resolved spec files ' + util.inspect(specs)); | ||
deferred.resolve({failedCount: 0}); | ||
} else { | ||
throw 'config.framework ' + config.framework + | ||
' is not a valid framework.'; | ||
throw new Error('config.framework (' + self.config_.framework + | ||
') is not a valid framework.'); | ||
} | ||
return deferred.promise; | ||
// Let the configuration configure the protractor instance before running | ||
// the tests. | ||
webdriver.promise.controlFlow().execute(function() { | ||
if (config.onPrepare) { | ||
if (typeof config.onPrepare === 'function') { | ||
config.onPrepare(); | ||
} else if (typeof config.onPrepare === 'string') { | ||
require(path.resolve(config.configDir, config.onPrepare)); | ||
} else { | ||
throw 'config.onPrepare must be a string or function'; | ||
} | ||
} | ||
}).then(function() { | ||
var options = config.jasmineNodeOpts; | ||
var originalOnComplete = options.onComplete; | ||
options.onComplete = function(runner, log) { | ||
if (originalOnComplete) { | ||
originalOnComplete(runner, log); | ||
} | ||
driver.quit().then(function() { | ||
runDeferred.fulfill(runner); | ||
}); | ||
}; | ||
if (config.framework === 'jasmine') { | ||
minijn.executeSpecs(options); | ||
} else if (config.framework === 'mocha') { | ||
mocha.run(function(failures) { | ||
// Warning: hack to make it have the same signature as Jasmine 1.3.1. | ||
if (originalOnComplete) { | ||
originalOnComplete(); | ||
} | ||
driver.quit().then(function() { | ||
runDeferred.fulfill({ | ||
results: function() { | ||
return { | ||
failedCount: failures | ||
}; | ||
} | ||
}); | ||
// 3) Teardown | ||
}).then(function(result) { | ||
testResult = result; | ||
if (self.driverprovider_.updateJob) { | ||
return self.driverprovider_.updateJob({ | ||
'passed': testResult.failedCount === 0 | ||
}).then(function() { | ||
return self.driverprovider_.teardownEnv(); | ||
}); | ||
}); | ||
} | ||
}); | ||
} else { | ||
return self.driverprovider_.teardownEnv(); | ||
} | ||
// 4) Exit process | ||
}).then(function() { | ||
var passed = testResult.failedCount === 0; | ||
var exitCode = passed ? 0 : 1; | ||
self.exit_(exitCode); | ||
return exitCode; | ||
}); | ||
return runDeferred.promise; | ||
}; | ||
/** | ||
* Run Protractor once. | ||
* Creates and returns a Runner instance | ||
* | ||
* @public | ||
* @param {Object} config | ||
*/ | ||
var runOnce = function() { | ||
var specs = config.specs; | ||
if (!specs || specs.length === 0) { | ||
util.puts('No spec files found'); | ||
process.exit(0); | ||
} | ||
return setUpSelenium().then(function() { | ||
// cleanUp must be registered directly onto runTests, not onto | ||
// the chained promise, so that cleanUp is still called in case of a | ||
// timeout error. Timeout errors need to clear the control flow, which | ||
// would mess up chaining promises. | ||
return runTests().then(cleanUp); | ||
}); | ||
}; | ||
exports.addConfig = addConfig; | ||
exports.runOnce = runOnce; | ||
module.exports = Runner; |
@@ -20,3 +20,4 @@ { | ||
"adm-zip": ">=0.4.2", | ||
"optimist": "~0.6.0" | ||
"optimist": "~0.6.0", | ||
"q": "1.0.0" | ||
}, | ||
@@ -29,2 +30,3 @@ "devDependencies": { | ||
"mocha": "~1.16.0", | ||
"cucumber": "~0.3.3", | ||
"express": "~3.3.4", | ||
@@ -43,11 +45,11 @@ "mustache": "~0.7.2" | ||
"scripts": { | ||
"test": "node lib/cli.js spec/basicConf.js; node lib/cli.js spec/altRootConf.js; node lib/cli.js spec/onPrepareConf.js; node lib/cli.js spec/mochaConf.js; node lib/cli.js spec/withLoginConf.js; node_modules/.bin/minijasminenode jasminewd/spec/adapterSpec.js" | ||
"test": "node lib/cli.js spec/basicConf.js; node lib/cli.js spec/multiConf.js; node lib/cli.js spec/altRootConf.js; node lib/cli.js spec/onPrepareConf.js; node lib/cli.js spec/mochaConf.js; node lib/cli.js spec/cucumberConf.js; node lib/cli.js spec/withLoginConf.js; node_modules/.bin/minijasminenode jasminewd/spec/adapterSpec.js spec/unit/*.js" | ||
}, | ||
"license" : "MIT", | ||
"version": "0.18.1", | ||
"version": "0.19.0", | ||
"webdriverVersions": { | ||
"selenium": "2.39.0", | ||
"chromedriver": "2.8", | ||
"chromedriver": "2.9", | ||
"iedriver": "2.39.0" | ||
} | ||
} |
@@ -72,2 +72,7 @@ // A reference configuration file. | ||
// If you would like to run more than one instance of webdriver on the same | ||
// tests, use multiCapabilities, which takes an array of capabilities. | ||
// If this is specified, capabilities will be ignored. | ||
multiCapabilities: [], | ||
// ----- More information for your tests ---- | ||
@@ -108,3 +113,3 @@ // | ||
// | ||
// Jasmine is fully supported as a test and assertion framework. | ||
// Jasmine and Cucumber are fully supported as a test and assertion framework. | ||
// Mocha has limited beta support. You will need to include your own | ||
@@ -138,2 +143,12 @@ // assertion framework if working with mocha. | ||
// ----- Options to be passed to cucumber ----- | ||
cucumberOpts: { | ||
// Require files before executing the features. | ||
require: 'cucumber/stepDefinitions.js', | ||
// Only execute the features or scenarios with tags matching @dev. | ||
tags: '@dev', | ||
// How to format features (default: progress) | ||
format: 'summary' | ||
}, | ||
// ----- The cleanup step ----- | ||
@@ -140,0 +155,0 @@ // |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance 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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
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
208999
41
3352
7
8
11
2