Skip to content

Commit

Permalink
feat(codeceptjs) add support for bdd steps (fixes #1231, via #1237)
Browse files Browse the repository at this point in the history
  • Loading branch information
baev authored Jan 30, 2025
1 parent e79bb16 commit 79c2bdf
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 28 deletions.
8 changes: 8 additions & 0 deletions packages/allure-codeceptjs/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ export interface CodeceptStep {
metaStep: CodeceptStep;
toString: () => string;
}

export interface CodeceptBddStep {
id: string;
keyword: string;
keywordType: string;
text: string;
startTime: number;
}
32 changes: 31 additions & 1 deletion packages/allure-codeceptjs/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { env } from "node:process";
import { LabelName, Stage, Status, type StepResult } from "allure-js-commons";
import { getMessageAndTraceFromError, getStatusFromError, isMetadataTag } from "allure-js-commons/sdk";
import AllureMochaReporter from "allure-mocha";
import type { CodeceptError, CodeceptStep } from "./model.js";
import type { CodeceptBddStep, CodeceptError, CodeceptStep } from "./model.js";

export class AllureCodeceptJsReporter extends AllureMochaReporter {
protected currentBddStep?: string;

constructor(runner: Mocha.Runner, opts: Mocha.MochaOptions, isInWorker: boolean) {
super(runner, opts, isInWorker);
this.registerEvents();
Expand All @@ -15,11 +17,15 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
registerEvents() {
// Test
event.dispatcher.on(event.test.before, this.testStarted.bind(this));
event.dispatcher.on(event.test.failed, this.testFailed.bind(this));
// Step
event.dispatcher.on(event.step.started, this.stepStarted.bind(this));
event.dispatcher.on(event.step.passed, this.stepPassed.bind(this));
event.dispatcher.on(event.step.failed, this.stepFailed.bind(this));
event.dispatcher.on(event.step.comment, this.stepComment.bind(this));

event.dispatcher.on(event.bddStep.before, this.bddStepStarted.bind(this));
event.dispatcher.on(event.bddStep.after, this.stepPassed.bind(this));
}

testStarted(test: { tags?: string[] }) {
Expand All @@ -40,6 +46,20 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
});
}

testFailed(_: {}, error: Error) {
if (this.currentBddStep) {
this.stopCurrentStep((result) => {
result.stage = Stage.FINISHED;
result.status = Status.BROKEN;
if (error) {
result.status = getStatusFromError({ message: error.message } as Error);
result.statusDetails = getMessageAndTraceFromError(error);
}
});
}
this.currentBddStep = undefined;
}

stepStarted(step: CodeceptStep) {
const root = this.currentHook ?? this.currentTest;
if (!root) {
Expand All @@ -50,6 +70,16 @@ export class AllureCodeceptJsReporter extends AllureMochaReporter {
});
}

bddStepStarted(step: CodeceptBddStep) {
const root = this.currentHook ?? this.currentTest;
if (!root) {
return;
}
this.currentBddStep = this.runtime.startStep(root, undefined, {
name: step.keyword + step.text,
});
}

// according to the docs, codeceptjs supposed to report the error,
// but actually it's never reported
stepFailed(_: CodeceptJS.Step, error?: CodeceptError) {
Expand Down
237 changes: 210 additions & 27 deletions packages/allure-codeceptjs/test/spec/bdd.test.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,220 @@
import { expect, it } from "vitest";
import { LabelName } from "allure-js-commons";
import { Status } from "allure-js-commons";
import { runCodeceptJsInlineTest } from "../utils.js";

it("should add correct bdd labels", async () => {
it("should add bdd steps", async () => {
const { tests } = await runCodeceptJsInlineTest({
"login.test.js": `
Feature("bdd")
Scenario('bdd', () => {
}).tag('@allure.label.epic:WebInterface')
.tag('@allure.label.feature:EssentialFeatures')
.tag('@allure.label.story:Authentication');
"features/basic.feature": `Feature: simple
Scenario: passed
Given a passed step`,
"step_definitions/steps.js": ` Given("a passed step", () => { });`,
"codecept.conf.js": `
const path = require("node:path");
const { setCommonPlugins } = require("@codeceptjs/configure");
setCommonPlugins();
exports.config = {
output: path.resolve(__dirname, "./output"),
plugins: {
allure: {
require: require.resolve("allure-codeceptjs"),
enabled: true,
},
},
gherkin: {
features: "./features/*.feature",
steps: ["./step_definitions/steps.js"],
},
};
`,
});

expect(tests).toHaveLength(1);
expect(tests[0].labels).toEqual(
expect.arrayContaining([
{
name: LabelName.EPIC,
value: "WebInterface",
},
{
name: LabelName.FEATURE,
value: "EssentialFeatures",
},
{
name: LabelName.STORY,
value: "Authentication",
},
]),
);
const [tr] = tests;
expect(tr.steps).toMatchObject([
{
name: "Given a passed step",
status: Status.PASSED,
},
]);
});

it("should support helper steps in bdd steps", async () => {
const { tests } = await runCodeceptJsInlineTest({
"features/basic.feature": `Feature: simple
Scenario: passed
Given a world
When we smile
Then all good`,
"step_definitions/steps.js": `const I = actor();
Given("a world", async () => { await I.pass(); });
When("we smile", async () => { await I.pass(); await I.next(); });
Then("all good", async () => { await I.next(); await I.pass(); await I.next(); await I.pass(); });`,
"codecept.conf.js": `
const path = require("node:path");
const { setCommonPlugins } = require("@codeceptjs/configure");
setCommonPlugins();
exports.config = {
output: path.resolve(__dirname, "./output"),
plugins: {
allure: {
require: require.resolve("allure-codeceptjs"),
enabled: true,
},
},
gherkin: {
features: "./features/*.feature",
steps: ["./step_definitions/steps.js"],
},
helpers: {
CustomHelper: {
require: "./helper.js",
},
ExpectHelper: {},
},
};
`,
});

expect(tests[0].labels.filter((l) => l.name === LabelName.EPIC)).toHaveLength(1);
expect(tests[0].labels.filter((l) => l.name === LabelName.FEATURE)).toHaveLength(1);
expect(tests[0].labels.filter((l) => l.name === LabelName.STORY)).toHaveLength(1);
expect(tests).toHaveLength(1);
const [tr] = tests;
expect(tr.steps).toMatchObject([
{
name: "Given a world",
steps: [
{
name: "I pass",
status: Status.PASSED,
},
],
},
{
name: "When we smile",
steps: [
{
name: "I pass",
status: Status.PASSED,
},
{
name: "I next",
status: Status.PASSED,
},
],
},
{
name: "Then all good",
steps: [
{
name: "I next",
status: Status.PASSED,
},
{
name: "I pass",
status: Status.PASSED,
},
{
name: "I next",
status: Status.PASSED,
},
{
name: "I pass",
status: Status.PASSED,
},
],
},
]);
});

it("should support failed bdd steps", async () => {
const { tests } = await runCodeceptJsInlineTest({
"features/basic.feature": `Feature: simple
Scenario: passed
Given a world
When we smile
Then all good
Then this one is ignored`,
"step_definitions/steps.js": `const I = actor();
Given("a world", async () => { await I.pass(); });
When("we smile", async () => { await I.pass(); await I.next(); });
Then("all good", async () => { await I.next(); await I.fail(); });
Then(" this one is ignored", async () => { });`,
"codecept.conf.js": `
const path = require("node:path");
const { setCommonPlugins } = require("@codeceptjs/configure");
setCommonPlugins();
exports.config = {
output: path.resolve(__dirname, "./output"),
plugins: {
allure: {
require: require.resolve("allure-codeceptjs"),
enabled: true,
},
},
gherkin: {
features: "./features/*.feature",
steps: ["./step_definitions/steps.js"],
},
helpers: {
CustomHelper: {
require: "./helper.js",
},
ExpectHelper: {},
},
};
`,
});

expect(tests).toHaveLength(1);
const [tr] = tests;
expect(tr.steps).toMatchObject([
{
name: "Given a world",
status: Status.PASSED,
steps: [
{
name: "I pass",
status: Status.PASSED,
},
],
},
{
name: "When we smile",
status: Status.PASSED,
steps: [
{
name: "I pass",
status: Status.PASSED,
},
{
name: "I next",
status: Status.PASSED,
},
],
},
{
name: "Then all good",
status: Status.BROKEN,
statusDetails: {
message: expect.stringContaining("an error"),
},
steps: [
{
name: "I next",
status: Status.PASSED,
},
{
name: "I fail",
status: Status.BROKEN,
statusDetails: {
message: expect.stringContaining("an error"),
},
},
],
},
]);
});
35 changes: 35 additions & 0 deletions packages/allure-codeceptjs/test/spec/labels.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, it } from "vitest";
import { LabelName } from "allure-js-commons";
import { runCodeceptJsInlineTest } from "../utils.js";

it("should add host & thread labels", async () => {
Expand Down Expand Up @@ -155,3 +156,37 @@ it("should add labels from env variables", async () => {
]),
);
});

it("should add labels from tags", async () => {
const { tests } = await runCodeceptJsInlineTest({
"login.test.js": `
Feature("bdd")
Scenario('bdd', () => {
}).tag('@allure.label.epic:WebInterface')
.tag('@allure.label.feature:EssentialFeatures')
.tag('@allure.label.story:Authentication');
`,
});

expect(tests).toHaveLength(1);
expect(tests[0].labels).toEqual(
expect.arrayContaining([
{
name: LabelName.EPIC,
value: "WebInterface",
},
{
name: LabelName.FEATURE,
value: "EssentialFeatures",
},
{
name: LabelName.STORY,
value: "Authentication",
},
]),
);

expect(tests[0].labels.filter((l) => l.name === LabelName.EPIC)).toHaveLength(1);
expect(tests[0].labels.filter((l) => l.name === LabelName.FEATURE)).toHaveLength(1);
expect(tests[0].labels.filter((l) => l.name === LabelName.STORY)).toHaveLength(1);
});

0 comments on commit 79c2bdf

Please sign in to comment.