UNPKG

nightwatch

Version:

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

300 lines (241 loc) 7.17 kB
const path = require('path'); const lodashCloneDeep = require('lodash/cloneDeep'); const TestHooks = require('./hooks.js'); const Context = require('./context.js'); const Utils = require('../utils'); const {Logger} = Utils; const PluginLoader = require('../api/_loaders/plugin.js'); class GlobalsContext extends Context { throwError(err) { err.detailedErr = err.message; err.message = `cannot read external global file using "${this.settings.globals_path}"`; err.showTrace = false; throw err; } get isTestHook() { return true; } constructor(settings, argv, currentEnv) { super({modulePath: settings.globals_path, settings, argv}); this.currentEnv = currentEnv; this.argv = argv; } get testsuite() { return this.module; } init(asyncLoading) { return this.initModule(() => { if (asyncLoading) { return this.loadModule(); } return this.loadModuleSync(); }, () => { this.externalGlobals = Object.assign({}, this.__module); this.__module = this.settings.globals; this.mergeGlobalsOntoSettings(); this.setCurrentEnv(); }, asyncLoading); } initModule(loadFn, callback, asyncLoading) { if (!this.settings.globals_path) { this.__module = this.settings.globals; this.setCurrentEnv(); if (asyncLoading) { return Promise.resolve(); } return; } const result = loadFn.call(this); if (result instanceof Promise) { return result.then(() => { callback.call(this); }); } callback.call(this); } async loadModule(isRetry = false) { try { this.__module = await Utils.requireModule(this.modulePath); if (!this.__module) { throw new Error(`Empty module provided in: "${this.modulePath}".`); } } catch (err) { if (err.code === 'MODULE_NOT_FOUND' && !isRetry) { this.modulePath = path.join(Utils.getConfigFolder(this.argv), this.settings.globals_path); await this.loadModule(true); return this; } this.throwError(err); } } loadModuleSync(isRetry = false) { try { this.__module = Utils.requireModule(this.modulePath); if (!this.__module) { throw new Error(`Empty module provided in: "${this.modulePath}".`); } } catch (err) { if (err.code === 'MODULE_NOT_FOUND' && !isRetry) { this.modulePath = path.join(Utils.getConfigFolder(this.argv), this.settings.globals_path); this.loadModuleSync(true); return this; } this.throwError(err); } return this; } /** * Merges the contents of the external globals back to the settings object in order for it to be available between * test runs; * * This can be either as it is, so that any changes on the globals are maintained, or as a deep copy * * @return {GlobalsContext} */ mergeGlobalsOntoSettings() { let externalGlobals; if (this.settings.persist_globals) { externalGlobals = this.externalGlobals; } else { // if we already have globals, make a copy of them externalGlobals = lodashCloneDeep(this.externalGlobals); } Object.assign(this.__module, externalGlobals); return this; } extendContextWithApi(context, api) { if (('client' in context) && this.modulePath && context.client && !('currentTest' in context.client)) { throw new Error('There is already a .client property defined in: ' + this.modulePath); } Object.defineProperty(context, 'client', { enumerable: false, configurable: false, writable: true, value: api }); } callAsync({fnName, api, expectedArgs = 2, isTestHook = false, isES6Async, done = function() {}}) { if (isES6Async && isTestHook && expectedArgs === 1) { return this.module[fnName].call(this.module, api) .then(function(result) { return done(result); }) .catch(function(err) { Logger.error(err); return done(err); }); } const fnAsync = Utils.makeFnAsync(expectedArgs, this.module[fnName], this.module); const args = this.getHookFnArgs(done, api, expectedArgs); return fnAsync.apply(this.module, args); } /** * * @param {string} fnName * @param args */ call(fnName, ...args) { const client = args[0]; if (client) { client.isES6AsyncTestcase = this.isES6Async(fnName); args[0] = client.api; } return this.module[fnName].apply(this.module, args); } hasHook(key) { return typeof this.moduleCopy[key] == 'function'; } getKey(key) { return this.moduleCopy[key]; } /** * * @param {function} done * @param {Object} api * @param {Number} expectedArgs * @return [] */ getHookFnArgs(done, api, expectedArgs) { return expectedArgs === 1 ? [done] : [api, done]; } setCurrentEnv() { this.moduleCopy = lodashCloneDeep(this.module); // select globals from the current environment if (this.currentEnv) { /*eslint no-prototype-builtins: 'warn'*/ if (this.currentEnv && this.__module.hasOwnProperty(this.currentEnv)) { Object.assign(this.__module, this.module[this.currentEnv]); } } } } class Globals { constructor(settings, argv, currentEnv = '') { this.settings = settings; this.argv = argv; this.currentEnv = currentEnv; } get globals() { return this.context.module; } loadPlugins() { const definition = this.settings.plugins || []; this.plugins = definition.reduce((prev, pluginName) => { const plugin = PluginLoader.load(pluginName); if (plugin.globals) { prev.push(plugin); } return prev; }, []); } init() { this.mergeWithExisting(); this.setupGlobalHooks(); this.loadPlugins(); } readExternal(asyncLoading) { this.context = new GlobalsContext(this.settings, this.argv, this.currentEnv); return this.context.init(asyncLoading); } /** * Shallow merge with existing globals on the settings object */ mergeWithExisting() { const settingsCopy = lodashCloneDeep(this.settings); delete settingsCopy.globals; try { Object.defineProperty(this.globals, 'settings', { enumerable: false, configurable: false, get() { return settingsCopy; } }); // eslint-disable-next-line } catch (err) {} this.settings.globals = this.globals; } setupGlobalHooks() { this.hooks = new TestHooks(this.context, { isGlobal: true, asyncHookTimeout: this.settings.globals.asyncHookTimeout }); } async runPluginHook(hookName, args) { if (this.plugins.length === 0) { return; } await Promise.all(this.plugins.map(plugin => { if (Utils.isFunction(plugin.globals[hookName])) { return plugin.globals[hookName].apply(this.settings.globals, args); } return Promise.resolve(); })); } async runGlobalHook(key, args = []) { await this.hooks[key].run(); await this.runPluginHook(key, args); } } module.exports = Globals;