Skip to content

Commit 41453d0

Browse files
author
DavertMik
committed
refactored mocha classes, added class hooks
1 parent 95753e6 commit 41453d0

File tree

12 files changed

+1324
-0
lines changed

12 files changed

+1324
-0
lines changed

lib/listener/steps.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = function () {
2121
currentTest.steps = [];
2222
if (!('retryNum' in currentTest)) currentTest.retryNum = 0;
2323
else currentTest.retryNum += 1;
24+
output.scenario.started(test);
2425
});
2526

2627
event.dispatcher.on(event.test.after, test => {

lib/mocha/bdd.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const { CucumberExpression, ParameterTypeRegistry, ParameterType } = require('@cucumber/cucumber-expressions');
2+
const Config = require('../config');
3+
4+
let steps = {};
5+
6+
const STACK_POSITION = 2;
7+
8+
/**
9+
* @param {*} step
10+
* @param {*} fn
11+
*/
12+
const addStep = (step, fn) => {
13+
const avoidDuplicateSteps = Config.get('gherkin', {}).avoidDuplicateSteps || false;
14+
const stack = new Error().stack;
15+
if (avoidDuplicateSteps && steps[step]) {
16+
throw new Error(`Step '${step}' is already defined`);
17+
}
18+
steps[step] = fn;
19+
fn.line = stack && stack.split('\n')[STACK_POSITION];
20+
if (fn.line) {
21+
fn.line = fn.line
22+
.trim()
23+
.replace(/^at (.*?)\(/, '(')
24+
.replace(codecept_dir, '.');
25+
}
26+
};
27+
28+
const parameterTypeRegistry = new ParameterTypeRegistry();
29+
30+
const matchStep = step => {
31+
for (const stepName in steps) {
32+
if (stepName.indexOf('/') === 0) {
33+
const regExpArr = stepName.match(/^\/(.*?)\/([gimy]*)$/) || [];
34+
const res = step.match(new RegExp(regExpArr[1], regExpArr[2]));
35+
if (res) {
36+
const fn = steps[stepName];
37+
fn.params = res.slice(1);
38+
return fn;
39+
}
40+
continue;
41+
}
42+
const expression = new CucumberExpression(stepName, parameterTypeRegistry);
43+
const res = expression.match(step);
44+
if (res) {
45+
const fn = steps[stepName];
46+
fn.params = res.map(arg => arg.getValue());
47+
return fn;
48+
}
49+
}
50+
throw new Error(`No steps matching "${step.toString()}"`);
51+
};
52+
53+
const clearSteps = () => {
54+
steps = {};
55+
};
56+
57+
const getSteps = () => {
58+
return steps;
59+
};
60+
61+
const defineParameterType = options => {
62+
const parameterType = buildParameterType(options);
63+
parameterTypeRegistry.defineParameterType(parameterType);
64+
};
65+
66+
const buildParameterType = ({ name, regexp, transformer, useForSnippets, preferForRegexpMatch }) => {
67+
if (typeof useForSnippets !== 'boolean') useForSnippets = true;
68+
if (typeof preferForRegexpMatch !== 'boolean') preferForRegexpMatch = false;
69+
return new ParameterType(name, regexp, null, transformer, useForSnippets, preferForRegexpMatch);
70+
};
71+
72+
module.exports = {
73+
Given: addStep,
74+
When: addStep,
75+
Then: addStep,
76+
And: addStep,
77+
matchStep,
78+
getSteps,
79+
clearSteps,
80+
defineParameterType,
81+
};

lib/mocha/cli.js

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
const {
2+
reporters: { Base },
3+
} = require('mocha');
4+
const ms = require('ms');
5+
const event = require('../event');
6+
const AssertionFailedError = require('../assert/error');
7+
const output = require('../output');
8+
9+
const cursor = Base.cursor;
10+
let currentMetaStep = [];
11+
let codeceptjsEventDispatchersRegistered = false;
12+
13+
class Cli extends Base {
14+
constructor(runner, opts) {
15+
super(runner);
16+
let level = 0;
17+
this.loadedTests = [];
18+
opts = opts.reporterOptions || opts;
19+
if (opts.steps) level = 1;
20+
if (opts.debug) level = 2;
21+
if (opts.verbose) level = 3;
22+
output.level(level);
23+
output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`);
24+
output.print(`Using test root "${global.codecept_dir}"`);
25+
26+
const showSteps = level >= 1;
27+
28+
if (level >= 2) {
29+
const Containter = require('../container');
30+
output.print(output.styles.debug(`Helpers: ${Object.keys(Containter.helpers()).join(', ')}`));
31+
output.print(output.styles.debug(`Plugins: ${Object.keys(Containter.plugins()).join(', ')}`));
32+
}
33+
34+
runner.on('start', () => {
35+
console.log();
36+
});
37+
38+
runner.on('suite', suite => {
39+
output.suite.started(suite);
40+
});
41+
42+
runner.on('fail', test => {
43+
if (test.ctx.currentTest) {
44+
this.loadedTests.push(test.ctx.currentTest.uid);
45+
}
46+
if (showSteps && test.steps) {
47+
return output.scenario.failed(test);
48+
}
49+
cursor.CR();
50+
output.test.failed(test);
51+
});
52+
53+
runner.on('pending', test => {
54+
if (test.parent && test.parent.pending) {
55+
const suite = test.parent;
56+
const skipInfo = suite.opts.skipInfo || {};
57+
skipTestConfig(test, skipInfo.message);
58+
} else {
59+
skipTestConfig(test, null);
60+
}
61+
this.loadedTests.push(test.uid);
62+
cursor.CR();
63+
output.test.skipped(test);
64+
});
65+
66+
runner.on('pass', test => {
67+
if (showSteps && test.steps) {
68+
return output.scenario.passed(test);
69+
}
70+
cursor.CR();
71+
output.test.passed(test);
72+
});
73+
74+
if (showSteps) {
75+
runner.on('test', test => {
76+
currentMetaStep = [];
77+
if (test.steps) {
78+
output.test.started(test);
79+
}
80+
});
81+
82+
if (!codeceptjsEventDispatchersRegistered) {
83+
codeceptjsEventDispatchersRegistered = true;
84+
85+
event.dispatcher.on(event.bddStep.started, step => {
86+
output.stepShift = 2;
87+
output.step(step);
88+
});
89+
90+
event.dispatcher.on(event.step.started, step => {
91+
let processingStep = step;
92+
const metaSteps = [];
93+
while (processingStep.metaStep) {
94+
metaSteps.unshift(processingStep.metaStep);
95+
processingStep = processingStep.metaStep;
96+
}
97+
const shift = metaSteps.length;
98+
99+
for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
100+
if (currentMetaStep[i] !== metaSteps[i]) {
101+
output.stepShift = 3 + 2 * i;
102+
if (!metaSteps[i]) continue;
103+
// bdd steps are handled by bddStep.started
104+
if (metaSteps[i].isBDD()) continue;
105+
output.step(metaSteps[i]);
106+
}
107+
}
108+
currentMetaStep = metaSteps;
109+
output.stepShift = 3 + 2 * shift;
110+
if (step.helper.constructor.name !== 'ExpectHelper') {
111+
output.step(step);
112+
}
113+
});
114+
115+
event.dispatcher.on(event.step.finished, () => {
116+
output.stepShift = 0;
117+
});
118+
}
119+
}
120+
121+
runner.on('suite end', suite => {
122+
let skippedCount = 0;
123+
const grep = runner._grep;
124+
for (const test of suite.tests) {
125+
if (!test.state && !this.loadedTests.includes(test.uid)) {
126+
if (matchTest(grep, test.title)) {
127+
if (!test.opts) {
128+
test.opts = {};
129+
}
130+
if (!test.opts.skipInfo) {
131+
test.opts.skipInfo = {};
132+
}
133+
skipTestConfig(test, "Skipped due to failure in 'before' hook");
134+
output.test.skipped(test);
135+
skippedCount += 1;
136+
}
137+
}
138+
}
139+
140+
this.stats.pending += skippedCount;
141+
this.stats.tests += skippedCount;
142+
});
143+
144+
runner.on('end', this.result.bind(this));
145+
}
146+
147+
result() {
148+
const stats = this.stats;
149+
stats.failedHooks = 0;
150+
console.log();
151+
152+
// passes
153+
if (stats.failures) {
154+
output.print(output.styles.bold('-- FAILURES:'));
155+
}
156+
157+
const failuresLog = [];
158+
159+
// failures
160+
if (stats.failures) {
161+
// append step traces
162+
this.failures.map(test => {
163+
const err = test.err;
164+
165+
let log = '';
166+
167+
if (err instanceof AssertionFailedError) {
168+
err.message = err.inspect();
169+
}
170+
171+
const steps = test.steps || (test.ctx && test.ctx.test.steps);
172+
173+
if (steps && steps.length) {
174+
let scenarioTrace = '';
175+
steps.reverse().forEach(step => {
176+
const line = `- ${step.toCode()} ${step.line()}`;
177+
// if (step.status === 'failed') line = '' + line;
178+
scenarioTrace += `\n${line}`;
179+
});
180+
log += `${output.styles.bold('Scenario Steps')}:${scenarioTrace}\n`;
181+
}
182+
183+
// display artifacts in debug mode
184+
if (test?.artifacts && Object.keys(test.artifacts).length) {
185+
log += `\n${output.styles.bold('Artifacts:')}`;
186+
for (const artifact of Object.keys(test.artifacts)) {
187+
log += `\n- ${artifact}: ${test.artifacts[artifact]}`;
188+
}
189+
}
190+
191+
try {
192+
let stack = err.stack ? err.stack.split('\n') : [];
193+
if (stack[0] && stack[0].includes(err.message)) {
194+
stack.shift();
195+
}
196+
197+
if (output.level() < 3) {
198+
stack = stack.slice(0, 3);
199+
}
200+
201+
err.stack = `${stack.join('\n')}\n\n${output.colors.blue(log)}`;
202+
203+
// clone err object so stack trace adjustments won't affect test other reports
204+
test.err = err;
205+
return test;
206+
} catch (e) {
207+
throw Error(e);
208+
}
209+
});
210+
211+
const originalLog = Base.consoleLog;
212+
Base.consoleLog = (...data) => {
213+
failuresLog.push([...data]);
214+
originalLog(...data);
215+
};
216+
Base.list(this.failures);
217+
Base.consoleLog = originalLog;
218+
console.log();
219+
}
220+
221+
this.failures.forEach(failure => {
222+
if (failure.constructor.name === 'Hook') {
223+
stats.failedHooks += 1;
224+
}
225+
});
226+
event.emit(event.all.failures, { failuresLog, stats });
227+
output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration), stats.failedHooks);
228+
229+
if (stats.failures && output.level() < 3) {
230+
output.print(output.styles.debug('Run with --verbose flag to see complete NodeJS stacktrace'));
231+
}
232+
}
233+
}
234+
235+
function matchTest(grep, test) {
236+
if (grep) {
237+
return grep.test(test);
238+
}
239+
return true;
240+
}
241+
242+
function skipTestConfig(test, message) {
243+
if (!test.opts) {
244+
test.opts = {};
245+
}
246+
if (!test.opts.skipInfo) {
247+
test.opts.skipInfo = {};
248+
}
249+
test.opts.skipInfo.message = test.opts.skipInfo.message || message;
250+
test.opts.skipInfo.isFastSkipped = true;
251+
event.emit(event.test.skipped, test);
252+
test.state = 'skipped';
253+
}
254+
255+
module.exports = function (runner, opts) {
256+
return new Cli(runner, opts);
257+
};

0 commit comments

Comments
 (0)