Skip to content

Commit b6c7c27

Browse files
authored
fix(allure-playwright): fix crash when using test.step in hooks with … (#1395)
1 parent 241b2f1 commit b6c7c27

File tree

2 files changed

+183
-9
lines changed

2 files changed

+183
-9
lines changed

packages/allure-playwright/src/index.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,27 @@ export class AllureReporter implements ReporterV2 {
308308
}
309309

310310
if (isHookStep) {
311-
const stack = isBeforeHookDescendant
312-
? this.beforeHooksStepsStack.get(test.id)!
313-
: this.afterHooksStepsStack.get(test.id)!;
311+
// Lazily initialize the hook stack if it doesn't exist (e.g., when detail: false and root hook was ignored)
312+
let stack = isBeforeHookDescendant
313+
? this.beforeHooksStepsStack.get(test.id)
314+
: this.afterHooksStepsStack.get(test.id);
315+
316+
if (!stack) {
317+
stack = new ShallowStepsStack();
318+
const rootHookStep: StepResult = {
319+
...createStepResult(),
320+
name: isBeforeHookDescendant ? BEFORE_HOOKS_ROOT_STEP_TITLE : AFTER_HOOKS_ROOT_STEP_TITLE,
321+
start: step.startTime.getTime(),
322+
stage: Stage.RUNNING,
323+
uuid: randomUuid(),
324+
};
325+
stack.startStep(rootHookStep);
326+
if (isBeforeHookDescendant) {
327+
this.beforeHooksStepsStack.set(test.id, stack);
328+
} else {
329+
this.afterHooksStepsStack.set(test.id, stack);
330+
}
331+
}
314332

315333
if (["test.attach", "attach"].includes(step.category)) {
316334
let hookStepWithUuid: AttachStack | undefined;
@@ -356,6 +374,31 @@ export class AllureReporter implements ReporterV2 {
356374
}
357375

358376
onStepEnd(test: TestCase, _result: PlaywrightTestResult, step: TestStep): void {
377+
const isRootBeforeHook = step.title === BEFORE_HOOKS_ROOT_STEP_TITLE;
378+
const isRootAfterHook = step.title === AFTER_HOOKS_ROOT_STEP_TITLE;
379+
const isRootHook = isRootBeforeHook || isRootAfterHook;
380+
381+
// For root hooks, check if we have a lazily created stack that needs to be finalized
382+
if (isRootHook) {
383+
const stack = isRootAfterHook ? this.afterHooksStepsStack.get(test.id) : this.beforeHooksStepsStack.get(test.id);
384+
385+
// If stack exists (was lazily created), finalize the root hook step
386+
if (stack) {
387+
stack.updateStep((stepResult) => {
388+
const { status = Status.PASSED } = getWorstTestStepResult(stepResult.steps) ?? {};
389+
stepResult.status = step.error ? Status.FAILED : status;
390+
stepResult.stage = Stage.FINISHED;
391+
if (step.error) {
392+
stepResult.statusDetails = { ...getMessageAndTraceFromError(step.error) };
393+
}
394+
});
395+
stack.stopStep({
396+
duration: step.duration,
397+
});
398+
}
399+
return;
400+
}
401+
359402
if (this.#shouldIgnoreStep(step)) {
360403
return;
361404
}
@@ -364,15 +407,18 @@ export class AllureReporter implements ReporterV2 {
364407
return;
365408
}
366409
const testUuid = this.allureResultsUuids.get(test.id)!;
367-
const isRootBeforeHook = step.title === BEFORE_HOOKS_ROOT_STEP_TITLE;
368-
const isRootAfterHook = step.title === AFTER_HOOKS_ROOT_STEP_TITLE;
369410
const isBeforeHookDescendant = isBeforeHookStep(step);
370411
const isAfterHookDescendant = isAfterHookStep(step);
371-
const isAfterHook = isRootAfterHook || isAfterHookDescendant;
372-
const isHook = isRootBeforeHook || isRootAfterHook || isBeforeHookDescendant || isAfterHookDescendant;
412+
const isAfterHook = isAfterHookDescendant;
413+
const isHook = isBeforeHookDescendant || isAfterHookDescendant;
373414

374415
if (isHook) {
375-
const stack = isAfterHook ? this.afterHooksStepsStack.get(test.id)! : this.beforeHooksStepsStack.get(test.id)!;
416+
const stack = isAfterHook ? this.afterHooksStepsStack.get(test.id) : this.beforeHooksStepsStack.get(test.id);
417+
418+
// Stack might not exist if root hook was ignored (e.g., detail: false) and no test.step was called inside it
419+
if (!stack) {
420+
return;
421+
}
376422

377423
stack.updateStep((stepResult) => {
378424
const { status = Status.PASSED } = getWorstTestStepResult(stepResult.steps) ?? {};

packages/allure-playwright/test/spec/hooks.spec.ts

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, it } from "vitest";
2-
import { Status } from "allure-js-commons";
2+
import { Stage, Status } from "allure-js-commons";
33
import { runPlaywrightInlineTest } from "../utils.js";
44

55
it("handles before hooks", async () => {
@@ -367,3 +367,131 @@ it("should not loose tests metadata when when there are hooks in the test", asyn
367367
]),
368368
);
369369
});
370+
371+
it("handles test.step inside beforeEach when detail: false", async () => {
372+
const { tests } = await runPlaywrightInlineTest({
373+
"sample.test.js": `
374+
import { test } from '@playwright/test';
375+
376+
test.describe('test detail = false', () => {
377+
test.beforeEach('Before each test', async () => {
378+
await test.step('Before - test demo 1', () => {
379+
});
380+
});
381+
382+
test('Demo Test 1', async () => {
383+
await test.step('Test - Demo Test 1 - Step A', () => {
384+
});
385+
});
386+
});
387+
`,
388+
"playwright.config.js": `
389+
module.exports = {
390+
reporter: [
391+
[
392+
require.resolve("allure-playwright"),
393+
{
394+
resultsDir: "./allure-results",
395+
detail: false,
396+
},
397+
],
398+
["dot"],
399+
],
400+
projects: [
401+
{
402+
name: "project",
403+
},
404+
],
405+
};
406+
`,
407+
});
408+
409+
expect(tests).toHaveLength(1);
410+
expect(tests[0]).toMatchObject({
411+
name: "Demo Test 1",
412+
status: Status.PASSED,
413+
stage: Stage.FINISHED,
414+
});
415+
expect(tests[0].steps).toEqual(
416+
expect.arrayContaining([
417+
expect.objectContaining({
418+
name: "Before Hooks",
419+
status: Status.PASSED,
420+
steps: expect.arrayContaining([
421+
expect.objectContaining({
422+
name: "Before - test demo 1",
423+
status: Status.PASSED,
424+
}),
425+
]),
426+
}),
427+
expect.objectContaining({
428+
name: "Test - Demo Test 1 - Step A",
429+
status: Status.PASSED,
430+
}),
431+
]),
432+
);
433+
});
434+
435+
it("handles test.step inside afterEach when detail: false", async () => {
436+
const { tests } = await runPlaywrightInlineTest({
437+
"sample.test.js": `
438+
import { test } from '@playwright/test';
439+
440+
test.describe('test detail = false', () => {
441+
test.afterEach('After each test', async () => {
442+
await test.step('After - test demo 1', () => {
443+
});
444+
});
445+
446+
test('Demo Test 1', async () => {
447+
await test.step('Test - Demo Test 1 - Step A', () => {
448+
});
449+
});
450+
});
451+
`,
452+
"playwright.config.js": `
453+
module.exports = {
454+
reporter: [
455+
[
456+
require.resolve("allure-playwright"),
457+
{
458+
resultsDir: "./allure-results",
459+
detail: false,
460+
},
461+
],
462+
["dot"],
463+
],
464+
projects: [
465+
{
466+
name: "project",
467+
},
468+
],
469+
};
470+
`,
471+
});
472+
473+
expect(tests).toHaveLength(1);
474+
expect(tests[0]).toMatchObject({
475+
name: "Demo Test 1",
476+
status: Status.PASSED,
477+
stage: Stage.FINISHED,
478+
});
479+
expect(tests[0].steps).toEqual(
480+
expect.arrayContaining([
481+
expect.objectContaining({
482+
name: "Test - Demo Test 1 - Step A",
483+
status: Status.PASSED,
484+
}),
485+
expect.objectContaining({
486+
name: "After Hooks",
487+
status: Status.PASSED,
488+
steps: expect.arrayContaining([
489+
expect.objectContaining({
490+
name: "After - test demo 1",
491+
status: Status.PASSED,
492+
}),
493+
]),
494+
}),
495+
]),
496+
);
497+
});

0 commit comments

Comments
 (0)