UNPKG

nightwatch

Version:

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

200 lines (164 loc) 5.41 kB
const Utils = require('../../utils'); const {TimedCallback} = Utils; class BaseHook { static get beforeAll() { return 'before'; } static get beforeEach() { return 'beforeEach'; } static get beforeChildProcess() { return 'beforeChildProcess'; } static get afterAll() { return 'after'; } static get afterEach() { return 'afterEach'; } static get afterChildProcess() { return 'afterChildProcess'; } get skipTestcasesOnError() { return false; } constructor(hookName, context, addtOpts = {}) { this.context = context; this.addtOpts = addtOpts; this.key = hookName; this.hookTimeoutId = null; } get isGlobal() { return this.addtOpts.isGlobal || false; } get isUnitTest() { return false; } /** * * @param {Object} client The nightwatch main instance * @param {function} [originalFn] * @return {*} */ run(client = null, originalFn = null) { originalFn = originalFn || this.verifyMethod(); if (originalFn) { const argsCount = originalFn.length; let expectedCount; let runnableDone; return new Promise((resolve, reject) => { try { const timedCallback = this.createCallbackWrapper(resolve, reject, err => { err.help = ['You can increase the hooks timeout by setting "asyncHookTimeout" in the globals config to the required value.']; err.skipTestCases = this.skipTestcasesOnError; runnableDone && runnableDone(err); reject(err); }); const doneFn = timedCallback.getWrapper(); if (!this.isGlobal) { expectedCount = 2; } if (this.context.currentRunnable && argsCount === 2) { // Set the runnable resolve function to be called from the inside the done callback of the hook // - necessary for the case when the callback starts a different async operation runnableDone = this.context.currentRunnable.setDoneCallback(); this.context.currentRunnable.deffered.promise.catch(err => { if (this.hookTimeoutId) { clearTimeout(this.hookTimeoutId); } runnableDone(err); }); } const invocationResult = this.context.invokeMethod(this.key, client, argsCount, result => { if (result instanceof Error) { result.skipTestCases = this.skipTestcasesOnError; } if (this.context.queue && this.context.queue.inProgress) { this.context.queue.once('queue:finished', _ => { runnableDone && runnableDone(result); doneFn(result); }); } else { runnableDone && runnableDone(result); doneFn(result); } }); if (argsCount <= 1 && (invocationResult instanceof Promise)) { invocationResult.catch(err => { return err; }).then(result => { if (result instanceof Error) { result.skipTestCases = this.skipTestcasesOnError; } runnableDone && runnableDone(result); doneFn(result); return result; }); } else if (this.onlyApiArgPassed(argsCount)) { // For global hooks (and unit tests), when we have only one argument, the argument is the "done" callback: // E.g.: (global) afterEach(done) {} // // For normal test hooks, the 1st argument is the 'client' object and the "done" callback is optional, // and thus the callback is called implicitly if it's not passed explicitly, e.g.: after(client) {} this.implicitlyCallDoneCallback(doneFn); } } catch (err) { if (this.hookTimeoutId) { clearTimeout(this.hookTimeoutId); } runnableDone && runnableDone(err); reject(err); } }); } return Promise.resolve(); } verifyMethod() { if (this.context.hasHook(this.key)) { return this.context.getKey(this.key); } return null; } /** * * @param {Function} resolve * @param {Function} reject * @param {Function} timeoutExpired * @return {TimedCallback} */ createCallbackWrapper(resolve, reject, timeoutExpired) { const timedCallback = new TimedCallback(function doneCallback(err) { if (Utils.isErrorObject(err)) { return reject(err); } resolve(); }, ((this.isGlobal && !this.isUnitTest) ? 'global ' : '') + this.key, this.addtOpts.asyncHookTimeout); timedCallback.onTimeoutExpired = timeoutExpired; timedCallback.onTimerStarted = timeoutId => { this.hookTimeoutId = timeoutId; }; return timedCallback; } onlyApiArgPassed(argsCount) { return argsCount === 1 && !this.isGlobal; } startQueueIfNeeded() { if (!this.context.queueStarted) { this.context.queue.run(); } } implicitlyCallDoneCallback(doneFn) { if (this.hookTimeoutId) { clearTimeout(this.hookTimeoutId); } process.nextTick(() => { if (this.context.queue) { this.context.queue.once('queue:finished', _ => { doneFn(); }); this.startQueueIfNeeded(); } }); } } module.exports = BaseHook;