nightwatch
Version:
Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.
494 lines (421 loc) • 11.5 kB
JavaScript
const {By, Key, Capabilities} = require('selenium-webdriver');
const lodashMerge = require('lodash/merge');
const Utils = require('./utils');
const Settings = require('./settings/settings.js');
const ElementGlobal = require('./api/_loaders/element-global.js');
const NightwatchClient = require('./core/client.js');
const namespacedApi = require('./core/namespaced-api.js');
const {NightwatchEventHub} = require('./runner/eventHub');
const HttpRequest = require('./http/request.js');
const cdp = require('./transport/selenium-webdriver/cdp.js');
const {Logger} = Utils;
const Nightwatch = module.exports = {};
/**
* New programmatic api added in v2
*
* @param {Boolean} headless
* @param {Boolean} silent
* @param {Boolean} output
* @param {Boolean} useAsync
* @param {String} env
* @param {String} browserName
* @param {String} config
* @param {number} timeout
* @param {Boolean} parallel
* @param {Object} globals
* @param {Boolean} enable_global_apis
* @param {Object} reporter
*
* @returns {{browser}}
*/
module.exports.createClient = function({
headless = false,
silent = true,
output = true,
useAsync = true,
env = null,
timeout = null,
parallel = false,
reporter = null,
browserName = null,
globals = {},
devtools = false,
debug = false,
enable_global_apis = false,
config = './nightwatch.json',
disable_process_listener = false,
test_settings
} = {}) {
if (browserName && !env) {
switch (browserName) {
case 'firefox':
env = 'firefox';
break;
case 'chrome':
env = 'chrome';
break;
case 'safari':
env = 'safari';
break;
case 'edge':
case 'MicrosoftEdge':
env = 'edge';
break;
}
}
const cliRunner = Nightwatch.CliRunner({
config,
headless,
env,
timeout,
devtools,
debug,
parallel,
disable_process_listener
});
const settings = arguments[0] || {};
const allSettings = Object.keys(settings).reduce((prev, key) => {
if (![
'headless',
'useAsync',
'env',
'timeout',
'parallel',
'reporter',
'devtools',
'debug',
'browserName',
'config'
].includes(key)) {
prev[key] = settings[key];
}
return prev;
}, {});
cliRunner.setup({
...allSettings,
silent,
output,
globals,
always_async_commands: useAsync
});
cliRunner.test_settings.disable_global_apis = cliRunner.test_settings.disable_global_apis || !enable_global_apis;
//merge settings recieved from testsuite to cli runner settings (this might have changed in hooks)
lodashMerge(cliRunner.test_settings, test_settings);
const client = Nightwatch.client(cliRunner.test_settings, reporter, cliRunner.argv, true);
// TODO: Do we need this (global `element` api will only be available on
// Nightwatch after `createClient` is called)? new `element` api is currently
// available on Nightwatch as named-export, but it won't be once we migrate
// to TypeScript, because then named-exports will be exported directly
// instead of first adding them to Nightwatch (default export).
// Some context: https://github.com/nightwatchjs/nightwatch/pull/3671
Object.defineProperty(Nightwatch, 'element', {
configurable: true,
value: function(locator) {
return ElementGlobal.element({locator, client});
}
});
const exported = {
updateCapabilities(value) {
return client.mergeCapabilities(value);
},
runGlobalBeforeHook() {
return cliRunner.globals.runGlobalHook('before', [client.settings]);
},
runGlobalAfterHook() {
return cliRunner.globals.runGlobalHook('after', [client.settings]);
},
launchBrowser({loadNightwatchApis = true} = {}) {
const {argv} = cliRunner;
return client.initialize(loadNightwatchApis)
.then(() => {
return client.createSession({argv});
})
.then((data) => {
if (this.settings.test_runner?.type === 'cucumber' && NightwatchEventHub.isAvailable) {
const NightwatchFormatter = require('./runner/test-runners/cucumber/nightwatch-format');
NightwatchFormatter.setCapabilities(data);
}
return client.api;
})
.catch((error) => {
if (this.settings.test_runner?.type === 'cucumber' && NightwatchEventHub.isAvailable) {
const NightwatchFormatter = require('./runner/test-runners/cucumber/nightwatch-format');
NightwatchFormatter.setCapabilities({
error: error
});
}
throw error;
});
},
async cleanup() {
await client.queue.waitForCompletion();
client.queue.empty();
client.queue.reset();
client.queue.removeAllListeners();
Logger.reset();
}
};
Object.defineProperties(exported, {
settings: {
configurable: true,
get: function() {
return client.settings;
}
},
transport: {
get: function() {
return client.transport;
}
},
nightwatch_client: {
configurable: true,
get: function() {
return client;
}
}
});
return exported;
};
/**
* @param settings
* @param reporter
* @param argv
* @returns {NightwatchClient}
*/
Nightwatch.client = function(settings, reporter = null, argv = {}, skipInit = true) {
const client = NightwatchClient.create(settings, argv);
if (reporter === null) {
const SimplifiedReporter = require('./reporter/simplified.js');
reporter = new SimplifiedReporter(settings);
}
client
.createTransport()
.setReporter(reporter);
if (skipInit) {
return client;
}
return client.initialize();
};
Nightwatch.cli = function(callback) {
const ArgvSetup = require('./runner/cli/argv-setup.js');
const {argv} = ArgvSetup;
if (argv['list-files']) {
argv._source = argv['_'].slice(0);
const runner = Nightwatch.CliRunner(argv);
return runner.setupAsync()
.then(() => runner.getTestsFiles())
// eslint-disable-next-line no-console
.then((result) => console.log(JSON.stringify(result)));
}
if (argv.help) {
ArgvSetup.showHelp();
} else if (argv.info) {
// eslint-disable-next-line no-console
console.log(' Environment Info:');
require('envinfo').run(
{
System: ['OS', 'CPU'],
Binaries: ['Node', 'Yarn', 'npm'],
Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari']
},
{
showNotFound: true,
duplicates: true,
fullTree: true
}// eslint-disable-next-line no-console
).then(console.log);
} else if (argv.version) {
Utils.printVersionInfo();
} else {
if (!Utils.isFunction(callback)) {
throw new Error('Supplied callback argument needs to be a function.');
}
callback(argv);
}
};
/**
*
* @param [testSource]
* @param [settings]
*/
Nightwatch.runTests = async function(testSource, settings) {
let argv;
if (arguments.length <= 1) {
settings = arguments[0] || {};
argv = {};
} else if (Array.isArray(testSource)) {
argv = {
_source: testSource
};
} else if (Utils.isObject(testSource)) {
argv = testSource;
} else if (Utils.isString(testSource)) {
argv = {
_source: [testSource]
};
}
if (argv.source) {
argv._source = argv.source;
}
argv.reporter = argv.reporter || Settings.DEFAULTS.default_reporter;
if (!Array.isArray(argv.reporter)) {
argv.reporter = [argv.reporter];
}
try {
const runner = Nightwatch.CliRunner(argv);
await runner.setupAsync(settings);
return runner.runTests();
} catch (err) {
return Promise.reject(err);
}
};
Nightwatch.CliRunner = function(argv = {}) {
const CliRunner = require('./runner/cli/cli.js');
return new CliRunner(argv);
};
/**
* @param opts
* @return {*}
*/
Nightwatch.initClient = function(opts = {}) {
const cliRunner = Nightwatch.CliRunner();
cliRunner.initTestSettings(opts);
return Nightwatch.client(cliRunner.settings);
};
/**
* @deprecated
* @param argv
* @param done
* @param settings
* @return {*|CliRunner}
*/
Nightwatch.runner = function(argv = {}, done = function() {}, settings = {}) {
if (argv.source) {
argv._source = argv.source;
}
argv.reporter = Settings.DEFAULTS.default_reporter;
const runner = Nightwatch.CliRunner(argv);
return runner.setup(settings)
.runTests()
.catch(err => {
runner.processListener.setExitCode(10);
return err;
})
.then(err => {
return done(err);
});
};
Object.defineProperty(Nightwatch, 'Logger', {
configurable: true,
get() {
const {logMessage, colors, underline, error, enable, disable, disableColors, isEnabled, inspectObject, request} = Logger;
return {
spinner(message) {
const ora = require('ora');
return ora(message).start();
},
log(message, ...args) {
logMessage('LOG', message, args, true);
},
info(message, ...args) {
logMessage('INFO', message, args, true);
},
warn(message, ...args) {
logMessage('WARN', message, args, true);
},
colors,
error,
underline,
enable,
disable,
disableColors,
isEnabled,
inspectObject,
request
};
}
});
Object.defineProperty(Nightwatch, 'by', {
configurable: true,
get() {
return By;
}
});
function throwIfBrowserNotAvailable() {
if (!global.browser) {
const err = new TypeError('Nightwatch client is not yet available.');
err.addDetailedErr = true;
throw err;
}
}
Object.defineProperty(Nightwatch, 'Key', {
configurable: true,
get() {
return Key;
}
});
Object.defineProperty(Nightwatch, 'Capabilities', {
configurable: true,
get() {
return Capabilities;
}
});
// Named exports
const {
browser, app, appium, alerts, cookies, document, window,
chrome, firefox, assert, verify, expect, element
} = namespacedApi;
// browser and app are overwritten below using `Object.defineProperty()`.
module.exports.browser = browser;
module.exports.app = app;
module.exports.appium = appium;
module.exports.alerts = alerts;
module.exports.cookies = cookies;
module.exports.document = document;
module.exports.window = window;
module.exports.chrome = chrome;
module.exports.firefox = firefox;
module.exports.assert = new Proxy(assert, {
get(_, name) {
return namespacedApi.assert[name];
}
});
module.exports.verify = new Proxy(verify, {
get(_, name) {
return namespacedApi.verify[name];
}
});
module.exports.expect = new Proxy(expect, {
apply(_, __, args) {
throwIfBrowserNotAvailable();
return global.browser.expect(...args);
},
get(_, name) {
throwIfBrowserNotAvailable();
return global.browser.expect[name];
}
});
module.exports.element = new Proxy(element, {
apply(_, __, args) {
throwIfBrowserNotAvailable();
return global.browser.element(...args);
},
get(_, name) {
throwIfBrowserNotAvailable();
return global.browser.element[name];
}
});
const globalBrowserDescriptor = {
configurable: true,
get() {
throwIfBrowserNotAvailable();
return global.browser;
}
};
Object.defineProperty(Nightwatch, 'browser', globalBrowserDescriptor);
Object.defineProperty(Nightwatch, 'app', globalBrowserDescriptor);
// expose some internal modules for direct use
module.exports.utils = {
HttpRequest,
cdp
};