UNPKG

@kronoslive/codeceptjs

Version:

Supercharged End 2 End Testing Framework for NodeJS

252 lines (217 loc) 7.35 kB
const { reporters: { Base } } = require('mocha'); const ms = require('ms'); const event = require('./event'); const AssertionFailedError = require('./assert/error'); const output = require('./output'); const { getHistory } = require('./cliHistory'); const cursor = Base.cursor; let currentMetaStep = []; class Cli extends Base { constructor(runner, opts) { super(runner); let level = 0; let reverse = true; let truncate = true; this.loadedTests = []; opts = opts.reporterOptions || opts; if (opts.steps) level = 1; if (opts.debug) level = 2; if (opts.verbose) level = 3; if (opts.noreverse) reverse = false; if (opts.notruncate) truncate = false; output.level(level); output.reverse(reverse); output.trunc(truncate); output.style(opts.outputStyle); output.print(`CodeceptJS v${require('./codecept').version()}`); output.print(`Using test root "${global.codecept_dir}"`); const showSteps = level >= 1; if (level >= 2) { const Containter = require('./container'); output.print(output.styles.debug(`Helpers: ${Object.keys(Containter.helpers()).join(', ')}`)); output.print(output.styles.debug(`Plugins: ${Object.keys(Containter.plugins()).join(', ')}`)); } runner.on('start', () => { console.log(); }); runner.on('suite', (suite) => { output.suite.started(suite); }); runner.on('fail', (test) => { if (test.ctx.currentTest) { this.loadedTests.push(test.ctx.currentTest.id); } if (showSteps && test.steps) { return output.scenario.failed(test); } cursor.CR(); output.test.failed(test); }); runner.on('pending', (test) => { if (test.parent && test.parent.pending) { const suite = test.parent; const skipInfo = suite.opts.skipInfo || {}; skipTestConfig(test, skipInfo.message); } else { skipTestConfig(test, null); } this.loadedTests.push(test.id); cursor.CR(); output.test.skipped(test); }); runner.on('pass', (test) => { if (showSteps && test.steps) { return output.scenario.passed(test); } cursor.CR(); output.test.passed(test); }); if (showSteps) { runner.on('test', (test) => { currentMetaStep = []; if (test.steps) { output.test.started(test); } }); event.dispatcher.on(event.step.started, (step) => { output.stepShift = 3; const printMetaStep = (currentStep, history) => { let tree = currentStep.callTree; if (currentStep.callTree.length > 1) { tree = currentStep.callTree.slice(0, currentStep.callTree.length - 1); } if (output.level() < 2) { tree = currentStep.callTree.slice(0, 1); } tree.forEach((call) => { if (!history[call.id]) { currentMetaStep.shift(); } else if (currentMetaStep.indexOf(call.id) === -1) { currentMetaStep.unshift(call.id); if (history[call.id]) { output.step({ ...history[call.id], sessionPrefix: step.sessionPrefix }); } } else { output.stepShift += 2; } history = history[call.id].children; }); }; const printDefaultMetaStep = (metaStep) => { if (!metaStep) return currentMetaStep.shift(); if (currentMetaStep.indexOf(metaStep.toString()) >= 0) return; // step is the same if (metaStep.metaStep) { printDefaultMetaStep(metaStep.metaStep); } currentMetaStep.unshift(metaStep.toString()); output.step(metaStep, true); }; if (step.metaStep && step.metaStep.actor === 'Within') { printDefaultMetaStep(step.metaStep); } const history = getHistory(); if (output.style() !== 'actor') { printMetaStep(step, history); } output.step(step); }); event.dispatcher.on(event.step.finished, () => { output.stepShift = 0; }); } runner.on('suite end', suite => { let skippedCount = 0; const grep = runner._grep; for (const test of suite.tests) { if (!test.state && !this.loadedTests.includes(test.id)) { if (matchTest(grep, test.title)) { if (!test.opts) { test.opts = {}; } if (!test.opts.skipInfo) { test.opts.skipInfo = {}; } skipTestConfig(test, 'Skipped due to failure in \'before\' hook'); output.test.skipped(test); skippedCount += 1; } } } this.stats.pending += skippedCount; this.stats.tests += skippedCount; }); runner.on('end', this.result.bind(this)); } result() { const stats = this.stats; console.log(); // passes if (stats.failures) { output.print(output.styles.bold('-- FAILURES:')); } // failures if (stats.failures) { // append step traces this.failures.map((test) => { const err = test.err; let log = ''; if (err instanceof AssertionFailedError) { err.message = err.inspect(); } const steps = test.steps || (test.ctx && test.ctx.test.steps); if (steps && steps.length) { let scenarioTrace = ''; steps.reverse().forEach((step) => { const line = `- ${step.toCode()} ${step.line()}`; // if (step.status === 'failed') line = '' + line; scenarioTrace += `\n${line}`; }); log += `${output.styles.bold('Scenario Steps')}:${scenarioTrace}\n`; } // display artifacts in debug mode if (test.artifacts && Object.keys(test.artifacts).length) { log += `\n${output.styles.bold('Artifacts:')}`; for (const artifact of Object.keys(test.artifacts)) { log += `\n- ${artifact}: ${test.artifacts[artifact]}`; } } let stack = err.stack ? err.stack.split('\n') : []; if (stack[0] && stack[0].includes(err.message)) { stack.shift(); } if (output.level() < 3) { stack = stack.slice(0, 3); } err.stack = `${stack.join('\n')}\n\n${output.colors.blue(log)}`; // clone err object so stack trace adjustments won't affect test other reports test.err = err; return test; }); Base.list(this.failures); console.log(); } output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration)); if (stats.failures && output.level() < 3) { output.print(output.styles.debug('Run with --verbose flag to see complete NodeJS stacktrace')); } } } function matchTest(grep, test) { if (grep) { return grep.test(test); } return true; } function skipTestConfig(test, message) { if (!test.opts) { test.opts = {}; } if (!test.opts.skipInfo) { test.opts.skipInfo = {}; } test.opts.skipInfo.message = test.opts.skipInfo.message || message; test.opts.skipInfo.isFastSkipped = true; event.emit(event.test.skipped, test); test.state = 'skipped'; } module.exports = function (runner, opts) { return new Cli(runner, opts); };