Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions packages/allure-codeceptjs/test/spec/runtime/modern/globals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { expect, it } from "vitest";
import { runCodeceptJsInlineTest } from "../../../utils.js";

it("writes globals payload from scenario body", async () => {
const before = Date.now();
const { globals, attachments } = await runCodeceptJsInlineTest({
"login.test.js": `
const { globalAttachment, globalError } = require("allure-js-commons");

Feature("sample-feature");
Scenario("sample-scenario", async () => {
await globalAttachment("global-log", "hello", { contentType: "text/plain" });
await globalError({ message: "global setup failed", trace: "stack" });
});
`,
});
const after = Date.now();

const globalsEntries = Object.entries(globals ?? {});
expect(globalsEntries.length).toBeGreaterThan(0);
globalsEntries.forEach(([globalsFileName]) => {
expect(globalsFileName).toMatch(/.+-globals\.json/);
});

const allErrors = globalsEntries.flatMap(([, info]) => info.errors);
const allAttachments = globalsEntries.flatMap(([, info]) => info.attachments);
expect(allErrors).toEqual(
expect.arrayContaining([
expect.objectContaining({
message: "global setup failed",
trace: "stack",
timestamp: expect.any(Number),
}),
]),
);
allErrors.forEach((error) => {
expect(error.timestamp).toBeGreaterThanOrEqual(before);
expect(error.timestamp).toBeLessThanOrEqual(after);
});
allAttachments.forEach((attachment) => {
expect(attachment.timestamp).toBeGreaterThanOrEqual(before);
expect(attachment.timestamp).toBeLessThanOrEqual(after);
});
expect(allErrors.filter((error) => error.message === "global setup failed")).toHaveLength(1);
expect(allAttachments.filter((attachment) => attachment.name === "global-log")).toHaveLength(1);

const scenarioAttachment = allAttachments.find((a) => a.name === "global-log");
expect(scenarioAttachment?.type).toBe("text/plain");
const encodedScenarioAttachment = attachments[scenarioAttachment!.source] as string;
expect(Buffer.from(encodedScenarioAttachment, "base64").toString("utf-8")).toBe("hello");
});

it("writes globals payload from suite hooks", async () => {
const before = Date.now();
const { globals, attachments } = await runCodeceptJsInlineTest({
"login.test.js": `
const { globalAttachment, globalError } = require("allure-js-commons");

Feature("sample-feature");
BeforeSuite(async () => {
await globalAttachment("before-suite-log", "before", { contentType: "text/plain" });
});

AfterSuite(async () => {
await globalError({ message: "after suite error" });
});

Scenario("sample-scenario", async () => {});
`,
});
const after = Date.now();

const globalsEntries = Object.entries(globals ?? {});
expect(globalsEntries.length).toBeGreaterThan(0);

const allErrors = globalsEntries.flatMap(([, info]) => info.errors);
const allAttachments = globalsEntries.flatMap(([, info]) => info.attachments);
expect(allErrors).toEqual(
expect.arrayContaining([
expect.objectContaining({
message: "after suite error",
timestamp: expect.any(Number),
}),
]),
);
allErrors.forEach((error) => {
expect(error.timestamp).toBeGreaterThanOrEqual(before);
expect(error.timestamp).toBeLessThanOrEqual(after);
});
allAttachments.forEach((attachment) => {
expect(attachment.timestamp).toBeGreaterThanOrEqual(before);
expect(attachment.timestamp).toBeLessThanOrEqual(after);
});

const beforeSuiteAttachment = allAttachments.find((a) => a.name === "before-suite-log");
expect(beforeSuiteAttachment?.type).toBe("text/plain");
const encodedBeforeSuiteAttachment = attachments[beforeSuiteAttachment!.source] as string;
expect(Buffer.from(encodedBeforeSuiteAttachment, "base64").toString("utf-8")).toBe("before");
});

it("does not collect globals from module scope", async () => {
const { globals, stdout } = await runCodeceptJsInlineTest({
"login.test.js": `
const { globalError } = require("allure-js-commons");

void globalError({ message: "module scope error" });

Feature("sample-feature");
Scenario("sample-scenario", async () => {});
`,
});

const globalsEntries = Object.entries(globals ?? {});
expect(globalsEntries).toHaveLength(0);
expect(stdout.join("\n")).toContain("no test runtime is found");
});
34 changes: 21 additions & 13 deletions packages/allure-cucumberjs/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { extname } from "node:path";
import type { Label, Link, TestResult } from "allure-js-commons";
import { ContentType, LabelName, Stage, Status } from "allure-js-commons";
import { getMessageAndTraceFromError, getStatusFromError } from "allure-js-commons/sdk";
import { type RuntimeMessage, getMessageAndTraceFromError, getStatusFromError } from "allure-js-commons/sdk";
import {
ALLURE_RUNTIME_MESSAGE_CONTENT_TYPE,
ReporterRuntime,
Expand Down Expand Up @@ -500,16 +500,9 @@ export default class AllureCucumberReporter extends Formatter {
}

private onAttachment(message: messages.Attachment): void {
if (!message.testCaseStartedId) {
return;
}

const fixtureUuid = this.fixtureUuids.get(message.testCaseStartedId);
const testUuid = this.testResultUuids.get(message.testCaseStartedId);
const fixtureUuid = message.testCaseStartedId ? this.fixtureUuids.get(message.testCaseStartedId) : undefined;
const testUuid = message.testCaseStartedId ? this.testResultUuids.get(message.testCaseStartedId) : undefined;
const rootUuid = fixtureUuid ?? testUuid;
if (!rootUuid) {
return;
}

if (message.mediaType === "application/vnd.allure.skipcucumber+json") {
if (testUuid) {
Expand All @@ -521,10 +514,11 @@ export default class AllureCucumberReporter extends Formatter {
}

if (message.mediaType === ALLURE_RUNTIME_MESSAGE_CONTENT_TYPE) {
const parsedMessage = JSON.parse(message.body);
this.applyRuntimeAttachmentMessages(rootUuid, message.body);
return;
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.allureRuntime.applyRuntimeMessages(rootUuid, Array.isArray(parsedMessage) ? parsedMessage : [parsedMessage]);
if (!rootUuid) {
return;
}

Expand All @@ -545,6 +539,20 @@ export default class AllureCucumberReporter extends Formatter {
]);
}

private parseRuntimeMessages(messageBody: string): RuntimeMessage[] {
const parsedMessage = JSON.parse(messageBody) as RuntimeMessage | RuntimeMessage[];
return Array.isArray(parsedMessage) ? parsedMessage : [parsedMessage];
}

private applyRuntimeAttachmentMessages(rootUuid: string | undefined, messageBody: string): void {
const runtimeMessages = this.parseRuntimeMessages(messageBody);
if (rootUuid) {
this.allureRuntime.applyRuntimeMessages(rootUuid, runtimeMessages);
} else {
this.allureRuntime.applyGlobalRuntimeMessages(runtimeMessages);
}
}

private onTestRunFinished() {
this.allureRuntime.writeCategoriesDefinitions();
this.allureRuntime.writeEnvironmentInfo();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { Given } = require("@cucumber/cucumber");
const { globalAttachment, globalError } = require("allure-js-commons");

Given("a passed step", async () => {
await globalAttachment("global-log", "hello", { contentType: "text/plain" });
await globalError({ message: "global setup failed", trace: "stack" });
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { Before, After } = require("@cucumber/cucumber");
const { globalAttachment, globalError } = require("allure-js-commons");

void globalError({ message: "module scope error" });

Before(async () => {
await globalAttachment("before-log", "before", { contentType: "text/plain" });
});

After(async () => {
await globalError({ message: "after hook error" });
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { expect, it } from "vitest";
import { runCucumberInlineTest } from "../../../utils.js";

it("writes globals payload from runtime API calls", async () => {
const { globals, attachments } = await runCucumberInlineTest(["hooks"], ["runtime/modern/globals"], {
parallel: false,
});

const globalsEntries = Object.entries(globals ?? {});
expect(globalsEntries.length).toBeGreaterThan(0);
globalsEntries.forEach(([globalsFileName]) => {
expect(globalsFileName).toMatch(/.+-globals\.json/);
});

const allErrors = globalsEntries.flatMap(([, info]) => info.errors);
const allAttachments = globalsEntries.flatMap(([, info]) => info.attachments);
expect(allErrors).toEqual(
expect.arrayContaining([
expect.objectContaining({
message: "global setup failed",
trace: "stack",
timestamp: expect.any(Number),
}),
]),
);
allErrors.forEach((error) => {
expect(error.timestamp).toEqual(expect.any(Number));
});
allAttachments.forEach((attachment) => {
expect(attachment.timestamp).toEqual(expect.any(Number));
});
expect(allErrors.filter((error) => error.message === "global setup failed")).toHaveLength(1);
expect(allAttachments.filter((attachment) => attachment.name === "global-log")).toHaveLength(1);

const globalAttachmentRef = allAttachments.find((attachment) => attachment.name === "global-log");
expect(globalAttachmentRef).toBeDefined();
expect(globalAttachmentRef!.name).toBe("global-log");
expect(globalAttachmentRef!.type).toBe("text/plain");

const encodedAttachment = attachments[globalAttachmentRef!.source] as string;
expect(Buffer.from(encodedAttachment, "base64").toString("utf-8")).toBe("hello");
});

it("supports globals from hooks", async () => {
const { globals, attachments } = await runCucumberInlineTest(["hooks"], ["hooks", "runtime/modern/globalsContexts"], {
parallel: false,
});

const globalsEntries = Object.entries(globals ?? {});
expect(globalsEntries.length).toBeGreaterThan(0);

const allErrors = globalsEntries.flatMap(([, info]) => info.errors);
const allAttachments = globalsEntries.flatMap(([, info]) => info.attachments);
expect(allErrors).toEqual(
expect.arrayContaining([
expect.objectContaining({
message: "after hook error",
timestamp: expect.any(Number),
}),
]),
);
allErrors.forEach((error) => {
expect(error.timestamp).toEqual(expect.any(Number));
});
allAttachments.forEach((attachment) => {
expect(attachment.timestamp).toEqual(expect.any(Number));
});

const beforeAttachmentRef = allAttachments.find((a) => a.name === "before-log");
expect(beforeAttachmentRef?.type).toBe("text/plain");
const encodedAttachment = attachments[beforeAttachmentRef!.source] as string;
expect(Buffer.from(encodedAttachment, "base64").toString("utf-8")).toBe("before");
});

it("does not collect globals from support module scope", async () => {
const { globals } = await runCucumberInlineTest(["hooks"], ["hooks", "runtime/modern/globalsContexts"], {
parallel: false,
});

const globalsEntries = Object.entries(globals ?? {});
expect(globalsEntries.length).toBeGreaterThan(0);

const allErrors = globalsEntries.flatMap(([, info]) => info.errors);
expect(allErrors).not.toEqual(expect.arrayContaining([{ message: "module scope error" }]));
});
Loading
Loading