UNPKG

nightwatch

Version:

Easy to use Node.js based End-to-End testing solution for browser based apps and websites, using the W3C WebDriver API.

293 lines (242 loc) 7.14 kB
const Mocha = require('mocha'); const Nightwatch = require('../../index.js'); const TestSuite = require('../../testsuite'); const {Context} = TestSuite; class CustomRunnable { static runHook(fn) { return CustomRunnable.run.call(this, fn, true); } static run(fn, isHook = false) { let self = this; let start = new Date(); let ctx = this.ctx; let finished; let emitted; if (!isHook) { this._enableTimeouts = false; } // Sometimes the ctx exists, but it is not runnable if (ctx && ctx.runnable) { ctx.runnable(this); } // called multiple times function multiple(err) { if (emitted) { return; } emitted = true; let msg = 'done() called multiple times'; if (err && err.message) { err.message += ` (and Mocha's ${msg})`; self.emit('error', err); } else { self.emit('error', new Error(msg)); } } // finished function done (err) { let ms = self.timeout(); if (self.timedOut) { return; } if (finished) { return multiple(err); } self.clearTimeout(); self.duration = new Date() - start; finished = true; if (!err && self.duration > ms && self._enableTimeouts) { err = new Error('Timeout of ' + ms + 'ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.'); } fn(err); } this.callback = done; this.resetTimeout(); try { const args = [this.parent.client.api]; let queueFinished = false; let userCalled = false; const onFinished = function(err) { if (err instanceof Error) { return done(err); } if (err) { if (Object.prototype.toString.call(err) === '[object Object]') { return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err))); } return done(new Error('done() invoked with non-Error: ' + err)); } done(); }; if (isHook) { args.push(function(err) { userCalled = true; onFinished(err); }); } this.parent.nightwatchSuite.createSession() .then(_ => { this.parent.nightwatchSuite.createRunnable(this.title, () => { this.fn.apply(ctx, args); }).then(err => { queueFinished = true; if (!isHook) { done(); } }).catch(err => { queueFinished = true; this.parent.nightwatchSuite.terminate('FAILED') .then(_ => done(err)).catch(err => done(err)); }); }); } catch (err) { emitted = true; done(err); } } } class Extensions { static createClient(settings) { return Nightwatch.client(settings); } static __adaptRunnables(parent) { if (parent.parent && !parent.client) { parent.client = parent.parent.client; } Extensions.__adaptHooks(parent, ['_afterAll', '_afterEach', '_beforeAll', '_beforeEach']); parent.tests = parent.tests.map(function(test) { test.run = CustomRunnable.run.bind(test); return test; }); if (parent.suites && parent.suites.length > 0) { parent.suites.forEach(function(item) { Extensions.__adaptRunnables(item); }); } } static __adaptHooks(suite, hooks) { hooks.forEach(function(hook) { suite[hook].forEach(hookInstance => { hookInstance.run = CustomRunnable.runHook.bind(hookInstance); }); }); } } class CustomRunner extends Mocha.Runner { runSuite(suite, fn) { let isMainSuite = suite.parent && suite.parent.root; let createSession = new Promise(function(resolve, reject) { if (isMainSuite) { try { const {settings} = suite.client; const modulePath = suite.file; const argv = require('../cli/argv-setup.js').argv; const modules = []; const testSuite = new TestSuite({modulePath, modules, settings, argv}); const context = new Context({modulePath, settings, argv}); context.init(); suite.nightwatchSuite = testSuite; testSuite.createClient(suite.client); testSuite.createContext({context}); testSuite.createSession() .then(data => resolve(data)) .catch(err => reject(err)); } catch (err) { reject(err); } } else { if (!suite.root) { suite.nightwatchSuite = suite.parent.nightwatchSuite; } resolve(false); } }); // create the nightwatch session createSession.then(sessionInfo => { // run the mocha suite super.runSuite(suite, fn); }).catch(err => { // an error occurred while trying to create the session this.failures = err; super.runSuite(suite, function(suiteErr) { suiteErr = suiteErr || err; fn(suiteErr); }); }); } run(fn) { Extensions.__adaptRunnables(this.suite); super.run(fn); } } class MochaNightwatch extends Mocha { constructor(mochaOpts, nightwatchSettings) { super(mochaOpts); this.nightwatchSettings = nightwatchSettings; } run(fn) { Mocha.Runner = CustomRunner; this.suite.client = Extensions.createClient(this.nightwatchSettings); return super.run(fn); } } class MochaRunner { get supportsConcurrency() { return false; } closeOpenSessions() { const mochaSuite = this.mocha.suite; const client = mochaSuite && mochaSuite.client; if (client && client.sessionId) { let request = client.transport.createHttpRequest({ path : `/session/${client.sessionId}` }); return new Promise(function(resolve, reject) { request.delete() .on('error', function(err) { console.warn(err.stack); resolve(); }) .on('success', function() { resolve(); }); }); } return Promise.resolve(); } constructor(settings, argv, addtOpts) { this.startTime = new Date().getTime(); this.settings = settings; this.argvOpts = argv; this.addtOpts = addtOpts; this.publishReport = false; let mochaOpts = settings.test_runner.options || {}; mochaOpts.timeout = settings.globals.asyncHookTimeout; this.mocha = new MochaNightwatch(mochaOpts, settings); } /** * Main entry-point of the runner * * @return {Promise} */ run(modules) { modules.forEach(module => this.mocha.addFile(module)); return new Promise((resolve, reject) => { this.mocha.run(failures => { this.closeOpenSessions() .catch(err => { console.error(err.stack); }) .then(_ => { if (failures) { return reject(failures instanceof Error ? failures : new Error('Mocha reported test failures.')); } resolve(); }); }); }); } } module.exports = MochaRunner;