UNPKG

codeceptjs

Version:

Modern Era Acceptance Testing Framework for NodeJS

240 lines (211 loc) 5.76 kB
const store = require('./store'); const Secret = require('./secret'); const STACK_LINE = 4; // using support objetcs for metastep detection // deprecated let support; /** * Each command in test executed through `I.` object is wrapped in Step. * Step allows logging executed commands and triggers hook before and after step execution. * @param {CodeceptJS.Helper} helper * @param {string} name */ class Step { constructor(helper, name) { /** @member {string} */ this.actor = 'I'; // I = actor /** @member {CodeceptJS.Helper} */ this.helper = helper; // corresponding helper /** @member {string} */ this.name = name; // name of a step console /** @member {string} */ this.helperMethod = name; // helper method /** @member {string} */ this.status = 'pending'; /** * @member {string} suffix * @memberof CodeceptJS.Step# */ /** @member {string} */ this.prefix = this.suffix = ''; /** @member {string} */ this.comment = ''; /** @member {Array<*>} */ this.args = []; /** @member {string} */ this.stack = ''; this.setTrace(); } /** @function */ setTrace() { Error.captureStackTrace(this); /** @member {MetaStep} */ this.metaStep = detectMetaStep(this.stack.split('\n')); } /** @param {Array<*>} args */ setArguments(args) { this.args = args; } /** * @param {...any} args * @return {*} */ run() { this.args = Array.prototype.slice.call(arguments); if (store.dryRun) { this.setStatus('success'); return Promise.resolve(new Proxy({}, dryRunResolver())); } let result; try { result = this.helper[this.helperMethod].apply(this.helper, this.args); this.setStatus('success'); } catch (err) { this.setStatus('failed'); throw err; } return result; } /** @param {string} status */ setStatus(status) { this.status = status; if (this.metaStep) this.metaStep.setStatus(status); } /** @return {string} */ humanize() { return this.name // insert a space before all caps .replace(/([A-Z])/g, ' $1') // _ chars to spaces .replace('_', ' ') // uppercase the first character .replace(/^(.)|\s(.)/g, $1 => $1.toLowerCase()); } /** @return {string} */ humanizeArgs() { return this.args.map((arg) => { if (typeof arg === 'string') { return `"${arg}"`; } else if (Array.isArray(arg)) { try { const res = JSON.stringify(arg); return res; } catch (err) { return `[${arg.toString()}]`; } } else if (typeof arg === 'function') { return arg.toString(); } else if (typeof arg === 'undefined') { return `${arg}`; } else if (arg instanceof Secret) { return '*****'; } else if (arg.toString && arg.toString() !== '[object Object]') { return arg.toString(); } else if (typeof arg === 'object') { return JSON.stringify(arg); } return arg; }).join(', '); } /** @return {string} */ line() { const lines = this.stack.split('\n'); if (lines[STACK_LINE]) return lines[STACK_LINE].trim(); return ''; } /** @return {string} */ toString() { return `${this.prefix}${this.actor} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`; } /** @return {string} */ toCode() { return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`; } isMetaStep() { return this.constructor.name === 'MetaStep'; } /** @return {boolean} */ hasBDDAncestor() { let hasBDD = false; let processingStep; processingStep = this; while (processingStep.metaStep) { if (processingStep.metaStep.actor.match(/^(Given|When|Then|And)/)) { hasBDD = true; break; } else { processingStep = processingStep.metaStep; } } return hasBDD; } } /** @extends Step */ class MetaStep extends Step { constructor(obj, method) { super(null, method); this.actor = obj; } humanize() { return this.name; } setTrace() { } /** @return {void} */ run() { } } /** @type {Class<MetaStep>} */ Step.MetaStep = MetaStep; module.exports = Step; function detectMetaStep(stack) { if (store.debugMode) return; // no detection in debug if (!support) loadSupportObjects(); // deprecated for (let i = STACK_LINE; i < stack.length; i++) { const line = stack[i].trim(); if (isTest(line) || isBDD(line)) break; const fnName = line.match(/^at (\w+)\.(\w+)\s\(/); if (!fnName) continue; if (fnName[1] === 'Generator' || fnName[1] === 'recorder' || fnName[1] === 'Runner' ) { return; } // don't track meta steps inside generators if (fnName[1] === 'Object') { // detect PO name from includes for (const name in support) { const file = support[name]; if (line.indexOf(file) > -1) { return new MetaStep(`${name}:`, fnName[2]); } } } return new MetaStep(`${fnName[1]}:`, fnName[2]); } } function loadSupportObjects() { const config = require('./config'); const path = require('path'); support = { ...config.get('include', {}), }; if (support) { for (const name in support) { const file = support[name]; support[name] = path.join(global.codecept_dir, file); } } } function isTest(line) { return line.trim().match(/^at Test\.Scenario/); } function isBDD(line) { return line.trim().match(/^at (Given|When|Then)/); } function dryRunResolver() { return { get(target, prop) { if (prop === 'toString') return () => '<VALUE>'; return new Proxy({}, dryRunResolver()); }, }; }