UNPKG

nightwatch

Version:

Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.

536 lines (434 loc) 14.6 kB
const path = require('path'); const minimist = require('minimist'); const DefaultSettings = require('../../settings/defaults.js'); const {Logger} = require('../../utils'); module.exports = new (function () { /** * Based on https://github.com/substack/node-optimist by * James Halliday (mail@substack.net), which has been deprecated */ class ArgvSetup { get argv() { const argv = minimist(this.__processArgs, this.options); argv.$0 = this.$0; if (this.demanded._ && argv._.length < this.demanded._) { this.fail(`Not enough non-option arguments: got ${argv._.length}, need at least ${this.demanded._}`); } const missing = []; Object.keys(this.demanded).forEach(function (key) { if (!argv[key]) { missing.push(key); } }); if (missing.length) { this.fail('Missing required arguments: ' + missing.join(', ')); } return argv; } constructor(processArgs, cwd = process.cwd()) { this.options = { alias: {}, default: {} }; this.__processArgs = processArgs; this.usage = null; this.demanded = {}; this.descriptions = {}; this.$0 = process.argv.slice(0, 2) .map(function (x) { const b = rebase(cwd, x); return x.match(/^\//) && b.length < x.length ? b : x; }).join(' '); if (process.env._ !== undefined && process.argv[1] === process.env._) { this.$0 = process.env._.replace(path.dirname(process.execPath) + '/', ''); } } addDefault(key, value) { if (typeof key === 'object') { Object.keys(key).forEach((k) => { this.addDefault(k, key[k]); }); } else { this.options.default[key] = value; } return this; } isDefault(option, value) { return this.options.default[option] && this.options.default[option] === value; } getDefault(option) { return this.options.default[option]; } alias(x, y) { if (typeof x === 'object') { Object.keys(x).forEach((key) => { this.alias(key, x[key]); }); } else { this.options.alias[x] = (this.options.alias[x] || []).concat(y); } return this; } demand(keys) { if (typeof keys == 'number') { if (!this.demanded._) { this.demanded._ = 0; } this.demanded._ += keys; } else if (Array.isArray(keys)) { keys.forEach((key) => { this.demand(key); }); } else { this.demanded[keys] = true; } return this; } showUsage(msg) { this.usage = msg; return this; } describe(key, desc, groupName) { if (typeof key === 'object') { Object.keys(key).forEach((k) => { this.describe(k, key[k], groupName); }); } else { this.descriptions[key] = {desc, groupName}; } return this; } option(key, opt) { if (typeof key === 'object') { Object.keys(key).forEach((k) => { this.option(k, key[k]); }); } else { if (opt.alias) { this.alias(key, opt.alias); } if (opt.demand) { this.demand(key); } if (typeof opt.defaults !== 'undefined') { this.addDefault(key, opt.defaults); } const desc = opt.describe || opt.description || opt.desc; if (desc) { this.describe(key, desc, opt.group); } } return this; } showHelp(fn) { if (!fn) { fn = console.error; } fn(this.help()); } help() { const keys = Object.keys(this.descriptions); const groups = Object.keys(this.descriptions).reduce((prev, key) => { const group = this.descriptions[key].groupName || 'Main options'; prev[group] = prev[group] || []; prev[group].push(key); return prev; }, {}); const help = []; if (this.usage) { help.unshift(this.usage.replace(/\$0/g, this.$0), ''); } const switches = keys.reduce((acc, key) => { acc[key] = [key].concat(this.options.alias[key] || []) .map(function (sw) { return (sw.length > 1 ? '--' : '-') + sw; }) .join(', '); return acc; }, {}); const switchlen = longest(Object.keys(switches).map(function (s) { return switches[s] || ''; })); const desclen = longest(Object.keys(this.descriptions).map((d) => { return this.descriptions[d].desc || ''; })); Object.keys(groups).forEach((groupName, index) => { const content = ['\n']; content.push(Logger.colors.brown(groupName + ':')); groups[groupName].forEach(key => { const kswitch = switches[key]; let desc = this.descriptions[key].desc || ''; const spadding = new Array(Math.max(switchlen - kswitch.length + 3, 0)).join('.'); const dpadding = new Array(Math.max(desclen - desc.length + 1, 0)).join(' '); if (dpadding.length > 0) { desc += dpadding; } const prelude = ' ' + (kswitch) + ' ' + Logger.colors.stack_trace(spadding); const extra = [ this.demanded[key] ? '[required]' : null, this.options.default[key] !== undefined ? '[default: ' + JSON.stringify(this.options.default[key]) + ']' : null ].filter(Boolean).join(' '); const body = [desc, extra].filter(Boolean).join(' '); content.push(prelude + ' ' + Logger.colors.stack_trace(body)); }); help.push(content.join('\n')); }); help.push('\n'); return help.join(''); } fail(msg) { if (msg) { console.error(Logger.colors.red(msg) + '\n'); } this.showHelp(); process.exit(1); } setup() { // CLI definitions this.option('source', { string: true }); // $ nightwatch -e // $ nightwatch --env saucelabs this.option('env', { description: 'Specify the testing environment to use.', alias: 'e', defaults: 'default' }); // $ nightwatch -c // $ nightwatch --config this.option('config', { demand: true, description: 'Path to configuration file; nightwatch.conf.js or nightwatch.json are read by default if present.', alias: 'c', defaults: './nightwatch.json' }); // $ nightwatch -t // $ nightwatch --test this.option('test', { description: 'Runs a single test.', alias: 't' }); // $ nightwatch --testcase this.option('testcase', { description: 'Used only together with --test. Runs the specified testcase from the current suite/module.' }); // $ nightwatch --mocha this.option('mocha', { description: 'Set the test runner to use Mocha.' }); /* // $ nightwatch --chrome this.option('chrome', { group: 'Browsers', description: 'Run tests in Google Chrome browser' }); // $ nightwatch --firefox this.option('firefox', { group: 'Browsers', description: 'Run tests in Mozilla Firefox browser' }); // $ nightwatch --safari this.option('safari', { group: 'Browsers', description: 'Run tests in Apple Safari' }); // $ nightwatch --edge this.option('edge', { group: 'Browsers', description: 'Run tests in Microsoft Edge' }); */ // $ nightwatch -g // $ nightwatch --group this.option('group', { group: 'Tags & filtering', description: 'Runs a group of tests (i.e. a folder)', alias: 'g' }); // $ nightwatch -s // $ nightwatch --skipgroup this.option('skipgroup', { group: 'Tags & filtering', description: 'Skips one or several (comma separated) group of tests.', alias: 's' }); // $ nightwatch -f // $ nightwatch --filter this.option('filter', { group: 'Tags & filtering', description: 'Specify a filter (glob expression) as the file name format to use when loading the files.', alias: 'f' }); // $ nightwatch -a // $ nightwatch --tag this.option('tag', { group: 'Tags & filtering', description: 'Only run tests with the given tag.', alias: 'a' }); // $ nightwatch --skiptags this.option('skiptags', { group: 'Tags & filtering', description: 'Skips tests that have the specified tag or tags (comma separated).' }); // $ nightwatch --grep this.option('grep', { group: 'Test Filters – Mocha only', description: 'Only run tests matching this string or regexp.' }); // $ nightwatch --fgrep this.option('fgrep', { group: 'Test Filters – Mocha only', description: 'Only run tests containing this string.' }); // $ nightwatch --invert this.option('invert', { group: 'Test Filters – Mocha only', description: 'Inverts --grep and --fgrep matches.' }); // $ nightwatch --retries this.option('retries', { group: 'Retrying', description: 'Retries failed or errored testcases up <n> times.' }); // $ nightwatch --suiteRetries this.option('suiteRetries', { group: 'Retrying', description: 'Retries failed or errored testsuites up <n> times.' }); // $ nightwatch --timeout this.option('timeout', { description: 'Set the global timeout for assertion retries before an assertion fails.' }); // $ nightwatch -o // $ nightwatch --output this.option('output', { group: 'Reporting', description: 'Where to save the (JUnit XML) test reports.', alias: 'o' }); // $ nightwatch -r // $ nightwatch --reporter this.option('reporter', { group: 'Reporting', description: 'Name of a predefined reporter (e.g. junit) or path to a custom reporter file to use.', alias: 'r', defaults: DefaultSettings.default_reporter }); // $ nightwatch --trace this.option('trace', { group: 'Reporting', descriptions: 'Record trace information during nightwatch execution.' }); // $ nightwatch --open this.option('open', { group: 'Reporting', description: 'Opens the HTML report generated in the default browser at the end of test run' }); // $ nightwatch --reuse-browser this.option('reuse-browser', { description: 'Use the same browser session to run the individual test suites' }); // $ nightwatch --parallel // this.option('parallel', { // description: 'Enable running the tests in parallel mode, via test workers.' // }); // $ nightwatch --workers=5 this.option('workers', { description: 'Max number of test files running at the same time (default: CPU cores; e.g. workers=4)' }); // $ nightwatch --sequential this.option('serial', { description: 'Executes tests serially (disables parallel mode)' }); // $ nightwatch --headless this.option('headless', { description: 'Launch the browser (Chrome, Edge or Firefox) in headless mode.' }); // $ nightwatch --devtools this.option('devtools', { description: 'Automatically open devtools when launching the browser (Chrome, Edge, or Safari).' }); // $ nightwatch --debug this.option('debug', { group: 'Component Tests', description: 'Automatically pause the test execution after mounting the component and open the Nightwatch debug REPL interface.' }); // $ nightwatch --story this.option('story', { group: 'Component Tests', description: 'Allows to specify which story to run from the current file (when using Storybook or JSX written in component story format)' }); // $ nightwatch --preview this.option('preview', { group: 'Component Tests', description: 'Used to preview a component story/test; automatically pause the test execution after mounting the component.' }); // $ nightwatch --verbose this.option('verbose', { description: 'Displays extended HTTP command logging during the test run.' }); // $ nightwatch --fail-fast this.option('fail-fast', { description: 'Run in "fail-fast" mode: if a test suite cannot be started, the rest will be aborted' }); // $ nightwatch -h // $ nightwatch --help this.option('help', { description: 'Shows this help (pass COLORS=0 env variable to disable colors).', group: 'Info & help', alias: 'h' }); // $ nightwatch --info this.option('info', { description: 'Shows environment info, i.e. OS, cpu, Node.js and installed browsers.', group: 'Info & help' }); // $ nightwatch -v // $ nightwatch --version this.option('version', { alias: 'v', group: 'Info & help', description: 'Shows version information.' }); // $ nightwatch --list-files this.option('list-files', { description: 'Shows list of files present in the project.' }); this.option('deviceId', { description: 'Set the udid of real iOS device' }); this.option('rerun-failed', { description: 'Rerun failed test suites' }); return this; } } function longest(xs) { return Math.max.apply(null, xs.map(x => x.length)); } function rebase(base, dir) { const ds = path.normalize(dir).split('/').slice(1); const bs = path.normalize(base).split('/').slice(1); for (var i = 0; ds[i] && ds[i] === bs[i]; i++) { ; } ds.splice(0, i); bs.splice(0, i); const p = path.normalize(bs.map(function () { return '..'; }).concat(ds).join('/')).replace(/\/$/, '').replace(/^$/, '.'); return p.match(/^[./]/) ? p : './' + p; } let __instance__; if (!__instance__) { __instance__ = new ArgvSetup(process.argv.slice(2)); __instance__.showUsage(`Usage: ${Logger.colors.cyan('$0 [source] [options]')}`).setup(); } return __instance__; })();