UNPKG

nightwatch

Version:

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

408 lines (325 loc) 11 kB
const path = require('path'); const ArgvSetup = require('./argv-setup.js'); const Settings = require('../../settings/settings.js'); const Globals = require('../../testsuite/globals.js'); const Concurrency = require('../concurrency'); const Utils = require('../../utils'); const Runner = require('../runner.js'); const ProcessListener = require('../process-listener.js'); const {Logger, singleSourceFile} = Utils; class CliRunner { static get CONFIG_FILE_JS() { return './nightwatch.conf.js'; } static get CONFIG_FILE_CJS() { return './nightwatch.conf.cjs'; } static createDefaultConfig(destFileName) { // eslint-disable-next-line no-console console.log(Logger.colors.cyan('No config file found in the current working directory, creating nightwatch.conf.js in the current folder...')); const templateFile = path.join(__dirname, 'nightwatch.conf.ejs'); const os = require('os'); const fs = require('fs'); const ejs = require('ejs'); const tplData = fs.readFileSync(templateFile).toString(); let launch_url = 'https://nightwatchjs.org'; const availablePlugins = []; const autoLoadPlugins = [{ 'vite-plugin-nightwatch': { launch_url: 'http://localhost:3000' } }]; autoLoadPlugins.forEach(plugin => { try { const pluginName = Object.keys(plugin)[0]; const pluginPath = Utils.getPluginPath(pluginName); availablePlugins.push(pluginName); if (plugin[pluginName].launch_url) { launch_url = plugin[pluginName].launch_url; } } catch (err) { // plugin is not installed } }); let rendered = ejs.render(tplData, { plugins: (availablePlugins.length > 0) ? JSON.stringify(availablePlugins) : false, launch_url, isMacOS: os.platform() === 'darwin' }); rendered = Utils.stripControlChars(rendered); try { fs.writeFileSync(destFileName, rendered, {encoding: 'utf-8'}); return true; } catch (err) { Logger.error(`Failed to save nightwatch.conf.js config file to ${destFileName}. You need to manually create either a nightwatch.json or nightwatch.conf.js configuration file.`); Logger.error(err); return false; } } constructor(argv = {}) { if (argv.source && !argv._source) { argv._source = argv.source; delete argv.source; } if (argv._source && Utils.isString(argv._source)) { argv._source = [argv._source]; } this.argv = argv; this.testRunner = null; this.globals = null; this.testEnv = null; this.testEnvArray = []; this.processListener = new ProcessListener(); } initTestSettings(userSettings = {}, baseSettings = null, argv = null, testEnv = '') { this.test_settings = Settings.parse(userSettings, baseSettings, argv, testEnv); this.setLoggingOptions(); this.setupGlobalHooks(); if (argv.timeout) { const timeout = parseInt(argv.timeout, 10); if (!isNaN(timeout)) { this.test_settings.globals.waitForConditionTimeout = timeout; this.test_settings.globals.retryAssertionTimeout = timeout; } } return this; } setupGlobalHooks() { this.globals = new Globals(this.test_settings, this.argv, this.testEnv); return this; } /** * backwards compatibility * @readonly * @deprecated * @return {*} */ get settings() { return this.baseSettings; } setCurrentTestEnv() { if (!this.argv.env) { return this; } this.testEnv = Utils.isString(this.argv.env) ? this.argv.env : Settings.DEFAULT_ENV; this.testEnvArray = this.testEnv.split(','); this.availableTestEnvs = Object.keys(this.baseSettings.test_settings).filter(key => { return Utils.isObject(this.baseSettings.test_settings[key]); }); this.validateTestEnvironments(); return this; } setLoggingOptions() { Logger.setOptions(this.test_settings); return this; } /** * @param {object} [settings] * @return {CliRunner} */ async setupAsync(settings) { this.baseSettings = await this.loadConfig(); this.commonSetup(settings); return this; } /** * Backwords compatibility for runner * @param {object} [settings] * @return {CliRunner} */ setup(settings) { this.baseSettings = this.loadConfig(); this.commonSetup(settings); return this; } commonSetup(settings) { this.validateConfig(); this.setCurrentTestEnv(); this.parseTestSettings(settings); this.createTestRunner(); this.setupConcurrency(); return this; } isConfigDefault(configFile, localJsValue = CliRunner.CONFIG_FILE_JS) { return ArgvSetup.isDefault('config', configFile) || path.resolve(configFile) === localJsValue; } getLocalConfigFileName() { const packageInfo = require(path.resolve('package.json')); const usingESM = packageInfo.type === 'module'; return path.resolve(usingESM ? CliRunner.CONFIG_FILE_CJS : CliRunner.CONFIG_FILE_JS); } loadConfig() { if (!this.argv.config) { return null; } const localJsValue = this.getLocalConfigFileName(); // use default nightwatch.json file if we haven't received another value if (this.isConfigDefault(this.argv.config, localJsValue)) { let newConfigCreated = false; const defaultValue = ArgvSetup.getDefault('config'); const hasJsConfig = Utils.fileExistsSync(localJsValue); const hasJsonConfig = Utils.fileExistsSync(defaultValue); if (!hasJsConfig && !hasJsonConfig) { newConfigCreated = CliRunner.createDefaultConfig(localJsValue); } if (hasJsConfig || newConfigCreated) { this.argv.config = localJsValue; } else if (hasJsonConfig) { this.argv.config = path.join(path.resolve('./'), this.argv.config); } } else { this.argv.config = path.resolve(this.argv.config); } return require(this.argv.config); } validateConfig() { // checking if the env passed is valid if (this.baseSettings && !this.baseSettings.test_settings) { this.baseSettings.test_settings = { default: {} }; } return this; } /** * Validates and parses the test settings * @param {object} [settings] * @returns {CliRunner} */ parseTestSettings(settings = {}) { this.initTestSettings(settings, this.baseSettings, this.argv, this.testEnv); return this; } runGlobalHook(key) { if (!Concurrency.isChildProcess()) { return this.globals.hooks[key].run(); } return Promise.resolve(); } validateTestEnvironments() { for (let i = 0; i < this.testEnvArray.length; i++) { if (!(this.testEnvArray[i] in this.baseSettings.test_settings)) { const error = new Error(`Invalid testing environment specified: ${this.testEnvArray[i]}. \n\n ${Logger.colors.light_cyan('Available environments are:')}\n ${Logger.inspectObject(this.availableTestEnvs)}`); error.showTrace = false; throw error; } } return this; } /////////////////////////////////////////////////////////////////////////////////////////////////////////// // Concurrency related /////////////////////////////////////////////////////////////////////////////////////////////////////////// get testWorkersMode() { return this.isTestWorkersEnabled() && !this.testRunner.isTestWorker(); } isTestWorkersEnabled() { return this.test_settings.testWorkersEnabled && !singleSourceFile(this.argv); } parallelMode(modules = null) { if (this.testWorkersMode && Array.isArray(modules) && modules.length <= 1) { return false; } return this.testEnvArray.length > 1 || this.testWorkersMode; } setupConcurrency() { this.concurrency = new Concurrency(this.test_settings, this.argv); return this; } isConcurrencyEnabled(modules) { return this.testRunner.supportsConcurrency && this.parallelMode(modules); } /////////////////////////////////////////////////////////////////////////////////////////////////////////// // Test runner related /////////////////////////////////////////////////////////////////////////////////////////////////////////// executeTestRunner(modules) { return this.testRunner.run(modules); } createTestRunner() { this.testRunner = Runner.create(this.test_settings, this.argv, { globalHooks: this.globals ? this.globals.hooks : null }); this.processListener.setTestRunner(this.testRunner); return this; } /** * * @param [done] * @return {*} */ runTests(done = null) { return this.runGlobalHook('before') .then(_ => { return Runner.readTestSource(this.test_settings, this.argv); }) .then(modules => { if (!this.testRunner) { const error = new Error(`Test runner '${this.test_settings.test_runner.type}' is not known.`); error.showTrace = false; error.detailedErr = `\n Verify "test_runner" settings: \n ${Logger.inspectObject(this.test_settings.test_runner)}`; throw error; } if (this.isConcurrencyEnabled(modules)) { return this.testRunner.runConcurrent(this.testEnvArray, modules) .then(exitCode => { if (exitCode > 0) { this.processListener.setExitCode(exitCode); } }); } return this.executeTestRunner(modules); }) .catch(err => { if (err.detailedErr) { err.data = err.detailedErr; } if (!err.sessionCreate && !err.displayed) { Logger.error(err); if (err.data) { Logger.warn(' ' + err.data); } // eslint-disable-next-line no-console console.log(''); } err.displayed = true; return err; }) .then(errorOrFailed => { if (errorOrFailed instanceof Error || errorOrFailed === true) { try { this.processListener.setExitCode(5); } catch (e) { // eslint-disable-next-line no-console console.error(e); } } return this.runGlobalHook('after') .then(result => { return errorOrFailed; }); }) .catch(err => { // eslint-disable-next-line no-console console.log(''); try { this.processListener.setExitCode(5); } catch (e) { // eslint-disable-next-line no-console console.error(e); } return err; }) .then(errorOrFailed => { if (typeof done == 'function' && !Concurrency.isChildProcess()) { if (errorOrFailed instanceof Error) { return done(errorOrFailed); } return done(); } if (errorOrFailed instanceof Error) { throw errorOrFailed; } return errorOrFailed; }); } } module.exports = CliRunner;