Skip to content

Commit 244cc18

Browse files
authored
Fix meta step nesting in CodeceptJS reporter (#1390)
1 parent b6c7c27 commit 244cc18

File tree

3 files changed

+115
-55
lines changed

3 files changed

+115
-55
lines changed

packages/allure-codeceptjs/src/reporter.ts

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const MAX_META_STEP_NESTING = 10;
1616
export class AllureCodeceptJsReporter extends AllureMochaReporter {
1717
protected currentBddStep?: string;
1818
protected metaStepStack: MetaStep[] = [];
19+
protected currentLeafStep?: string;
1920

2021
constructor(runner: Mocha.Runner, opts: Mocha.MochaOptions, isInWorker: boolean) {
2122
super(runner, opts, isInWorker);
@@ -103,6 +104,7 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
103104
}
104105

105106
this.metaStepStack = [];
107+
this.currentLeafStep = undefined;
106108
}
107109

108110
stepStarted(step: CodeceptStep) {
@@ -114,7 +116,7 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
114116
const stepPath: string[] = [];
115117
let current = step.metaStep;
116118
while (current && !current.isBDD() && stepPath.length < MAX_META_STEP_NESTING) {
117-
stepPath.push(stripAnsi(current.toString() ?? "").trim());
119+
stepPath.unshift(stripAnsi(current.toString() ?? "").trim());
118120
current = current.metaStep;
119121
}
120122

@@ -126,26 +128,29 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
126128
index++;
127129
}
128130

129-
for (let i = index; i < this.metaStepStack.length; i++) {
130-
const id = this.metaStepStack[i].id;
131-
this.runtime.updateStep(id, (result) => {
132-
result.status = Status.PASSED;
133-
result.stage = Stage.FINISHED;
131+
for (let i = this.metaStepStack.length - 1; i >= index; i--) {
132+
const { id } = this.metaStepStack[i];
133+
this.runtime.updateStep(id, (s) => {
134+
s.status = Status.PASSED;
135+
s.stage = Stage.FINISHED;
134136
});
135137
this.runtime.stopStep(id);
136138
}
139+
this.metaStepStack = this.metaStepStack.slice(0, index);
137140

138141
for (let i = index; i < stepPath.length; i++) {
139142
const name = stepPath[i];
140-
const id = this.runtime.startStep(root, undefined, {
143+
const parentId = this.metaStepStack[i - 1]?.id ?? root;
144+
const id = this.runtime.startStep(parentId, undefined, {
141145
name,
142146
});
143147
if (id) {
144148
this.metaStepStack.push({ name, id });
145149
}
146150
}
147151

148-
this.runtime.startStep(root, undefined, {
152+
const parent = this.metaStepStack[this.metaStepStack.length - 1]?.id ?? root;
153+
this.currentLeafStep = this.runtime.startStep(parent, undefined, {
149154
name: step.toString().trim(),
150155
});
151156
}
@@ -155,6 +160,13 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
155160
if (!root) {
156161
return;
157162
}
163+
if (this.currentBddStep) {
164+
this.runtime.updateStep(this.currentBddStep, (result) => {
165+
result.status = Status.PASSED;
166+
result.stage = Stage.FINISHED;
167+
});
168+
this.runtime.stopStep(this.currentBddStep);
169+
}
158170
this.currentBddStep = this.runtime.startStep(root, undefined, {
159171
name: step.keyword + step.text,
160172
});
@@ -163,15 +175,24 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
163175
// according to the docs, codeceptjs supposed to report the error,
164176
// but actually it's never reported
165177
stepFailed(_: CodeceptJS.Step, error?: CodeceptError) {
166-
this.stopCurrentStep((result) => {
178+
if (!this.currentLeafStep) {
179+
return;
180+
}
181+
182+
this.runtime.updateStep(this.currentLeafStep, (result) => {
167183
result.stage = Stage.FINISHED;
168184
if (error) {
169-
result.status = getStatusFromError({ message: error.message } as Error);
170-
result.statusDetails = getMessageAndTraceFromError(error);
185+
if (!error.message && typeof error.inspect === "function") {
186+
error.message = error.inspect();
187+
}
188+
result.status = getStatusFromError(error as unknown as Error);
189+
result.statusDetails = getMessageAndTraceFromError(error as unknown as Error);
171190
} else {
172191
result.status = env.TRY_TO === "true" ? Status.BROKEN : Status.FAILED;
173192
}
174193
});
194+
this.runtime.stopStep(this.currentLeafStep);
195+
this.currentLeafStep = undefined;
175196
}
176197

177198
stepComment() {
@@ -182,10 +203,26 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
182203
}
183204

184205
stepPassed() {
185-
this.stopCurrentStep((result) => {
206+
if (this.currentBddStep && !this.currentLeafStep) {
207+
this.runtime.updateStep(this.currentBddStep, (result) => {
208+
result.status = Status.PASSED;
209+
result.stage = Stage.FINISHED;
210+
});
211+
this.runtime.stopStep(this.currentBddStep);
212+
this.currentBddStep = undefined;
213+
return;
214+
}
215+
216+
if (!this.currentLeafStep) {
217+
return;
218+
}
219+
220+
this.runtime.updateStep(this.currentLeafStep, (result) => {
186221
result.status = Status.PASSED;
187222
result.stage = Stage.FINISHED;
188223
});
224+
this.runtime.stopStep(this.currentLeafStep);
225+
this.currentLeafStep = undefined;
189226
}
190227

191228
stopCurrentStep(updateFunc: (result: StepResult) => void) {

packages/allure-codeceptjs/test/spec/pageObject.test.ts

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ it("should support failed steps in page objects", async () => {
9696
const { container } = require('codeceptjs')
9797
9898
Feature("login-feature");
99-
Scenario("login-scenario1", async ({ I, login }) => {
100-
await I.pass();
101-
await login.onMainPage();
99+
Scenario("login-scenario1", ({ I, login }) => {
100+
I.pass();
101+
login.onMainPage();
102102
});
103103
`,
104104
"codecept.conf.js": `
@@ -131,12 +131,10 @@ it("should support failed steps in page objects", async () => {
131131
132132
class CustomHelper extends Helper {
133133
134-
async pass() {
135-
await Promise.resolve();
136-
}
134+
pass() {}
137135
138-
async fail() {
139-
await Promise.reject(new Error("an error"));
136+
fail() {
137+
throw new Error("an error");
140138
}
141139
142140
}
@@ -147,10 +145,10 @@ it("should support failed steps in page objects", async () => {
147145
const { I } = inject();
148146
149147
module.exports = {
150-
async onMainPage() {
151-
await I.pass();
152-
await I.fail();
153-
await I.pass();
148+
onMainPage() {
149+
I.pass();
150+
I.fail();
151+
I.pass();
154152
}
155153
}
156154
`,
@@ -180,20 +178,20 @@ it("should support failed steps in page objects", async () => {
180178
trace: expect.stringContaining("CustomHelper.fail"),
181179
},
182180
steps: [
183-
expect.objectContaining({
181+
{
184182
name: "I pass",
185183
status: Status.PASSED,
186-
}),
184+
},
185+
{
186+
name: "I fail",
187+
status: Status.BROKEN,
188+
statusDetails: {
189+
message: expect.stringContaining("an error"),
190+
trace: expect.stringContaining("CustomHelper.fail"),
191+
},
192+
},
187193
],
188194
},
189-
{
190-
name: "I fail",
191-
status: Status.BROKEN,
192-
statusDetails: {
193-
message: expect.stringContaining("an error"),
194-
trace: expect.stringContaining("CustomHelper.fail"),
195-
},
196-
},
197195
],
198196
});
199197
});
@@ -394,15 +392,16 @@ it("should support failed actor steps", async () => {
394392
});
395393
});
396394

397-
it("should support nexted page object steps", async () => {
395+
it("should support nested page object steps", async () => {
398396
const { tests } = await runCodeceptJsInlineTest({
399397
"nested/login.test.js": `
400398
const { container } = require('codeceptjs')
401399
402400
Feature("login-feature");
403-
Scenario("login-scenario1", async ({ I, page1 }) => {
404-
await I.pass();
405-
await page1.fewNestedSteps();
401+
Scenario("login-scenario1", ({ I, page1, page2 }) => {
402+
I.pass();
403+
page1.fewNestedSteps();
404+
page2.onNextPage();
406405
});
407406
`,
408407
"codecept.conf.js": `
@@ -436,13 +435,9 @@ it("should support nexted page object steps", async () => {
436435
437436
class CustomHelper extends Helper {
438437
439-
async pass() {
440-
await Promise.resolve();
441-
}
438+
pass() {}
442439
443-
async next() {
444-
await Promise.resolve();
445-
}
440+
next() {}
446441
447442
}
448443
@@ -452,19 +447,20 @@ it("should support nexted page object steps", async () => {
452447
const { I, page2 } = inject();
453448
454449
module.exports = {
455-
async fewNestedSteps() {
456-
await I.pass();
457-
await I.next();
458-
await page2.onNextPage();
450+
fewNestedSteps() {
451+
I.pass();
452+
I.next();
453+
page2.onNextPage();
459454
}
460455
}
461456
`,
462457
"pages/page2.js": `
463458
const { I } = inject();
464459
465460
module.exports = {
466-
async onNextPage() {
467-
await I.next();
461+
onNextPage() {
462+
I.pass();
463+
I.next();
468464
}
469465
}
470466
`,
@@ -475,7 +471,6 @@ it("should support nexted page object steps", async () => {
475471
const [tr] = tests;
476472

477473
expect(tr).toMatchObject({
478-
status: Status.PASSED,
479474
name: "login-scenario1",
480475
steps: [
481476
{
@@ -485,14 +480,38 @@ it("should support nexted page object steps", async () => {
485480
{
486481
name: "On page1: few nested steps",
487482
status: Status.PASSED,
488-
},
489-
{
490-
name: "I next",
491-
status: Status.PASSED,
483+
steps: [
484+
{
485+
name: "I pass",
486+
status: Status.PASSED,
487+
},
488+
{
489+
name: "I next",
490+
status: Status.PASSED,
491+
},
492+
{
493+
name: "I pass",
494+
status: Status.PASSED,
495+
},
496+
{
497+
name: "I next",
498+
status: Status.PASSED,
499+
},
500+
],
492501
},
493502
{
494503
name: "On page2: on next page",
495504
status: Status.PASSED,
505+
steps: [
506+
{
507+
name: "I pass",
508+
status: Status.PASSED,
509+
},
510+
{
511+
name: "I next",
512+
status: Status.PASSED,
513+
},
514+
],
496515
},
497516
],
498517
});

packages/allure-js-commons/src/sdk/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ export const getStatusFromError = (error: Partial<Error>): Status => {
99
* `jest` throws `JestAssertionError` instance
1010
* `jasmine` throws `ExpectationFailed` instance
1111
* `vitest` throws `Error` for extended assertions, so we look into stack
12+
* `codeceptjs-expect` errors have actual/expected properties or inspect method
1213
*/
1314
case /assert/gi.test(error.constructor.name):
1415
case /expectation/gi.test(error.constructor.name):
1516
case error.name && /assert/gi.test(error.name):
1617
case error.message && /assert/gi.test(error.message):
1718
case error.stack && /@vitest\/expect/gi.test(error.stack):
1819
case error.stack && /playwright\/lib\/matchers\/expect\.js/gi.test(error.stack):
20+
case error.stack && /codeceptjs-expect/gi.test(error.stack):
1921
case "matcherResult" in error:
2022
case "inspect" in error && typeof error.inspect === "function":
23+
case "actual" in error:
24+
case "expected" in error:
2125
return Status.FAILED;
2226
default:
2327
return Status.BROKEN;

0 commit comments

Comments
 (0)