diff --git a/packages/allure-js-commons/src/sdk/index.ts b/packages/allure-js-commons/src/sdk/index.ts index 991a68fea..8491418ef 100644 --- a/packages/allure-js-commons/src/sdk/index.ts +++ b/packages/allure-js-commons/src/sdk/index.ts @@ -17,6 +17,7 @@ export { getStatusFromError, getMessageAndTraceFromError, isMetadataTag, + getMetadataLabel, extractMetadataFromString, isAllStepsEnded, isAnyStepFailed, diff --git a/packages/allure-js-commons/src/sdk/utils.ts b/packages/allure-js-commons/src/sdk/utils.ts index 15754f741..d9fcd8660 100644 --- a/packages/allure-js-commons/src/sdk/utils.ts +++ b/packages/allure-js-commons/src/sdk/utils.ts @@ -73,6 +73,7 @@ type AllureTitleMetadataMatch = RegExpMatchArray & { }; }; +export const allureMetadataRegexp = /(?:^|\s)@?allure\.(?\S+)$/; export const allureTitleMetadataRegexp = /(?:^|\s)@?allure\.(?\S+)[:=]("[^"]+"|'[^']+'|`[^`]+`|\S+)/; export const allureTitleMetadataRegexpGlobal = new RegExp(allureTitleMetadataRegexp, "g"); export const allureIdRegexp = /(?:^|\s)@?allure\.id[:=](?\S+)/; @@ -96,7 +97,23 @@ export const getValueFromAllureTitleMetadataMatch = (match: AllureTitleMetadataM }; export const isMetadataTag = (tag: string) => { - return allureTitleMetadataRegexp.test(tag); + return allureMetadataRegexp.test(tag); +}; + +export const getMetadataLabel = (tag: string, value?: string): Label | undefined => { + const match = tag.match(allureMetadataRegexp); + const type = match?.groups?.type; + + if (!type) { + return undefined; + } + + const [subtype, name] = type.split("."); + + return { + name: subtype === "id" ? LabelName.ALLURE_ID : name, + value: value ?? "", + }; }; export const extractMetadataFromString = ( diff --git a/packages/allure-playwright/src/index.ts b/packages/allure-playwright/src/index.ts index a618c88a5..87ff34639 100644 --- a/packages/allure-playwright/src/index.ts +++ b/packages/allure-playwright/src/index.ts @@ -15,17 +15,25 @@ import { type ImageDiffAttachment, type Label, LabelName, + LinkType, Stage, Status, type TestResult, } from "allure-js-commons"; import type { RuntimeMessage, TestPlanV1Test } from "allure-js-commons/sdk"; -import { extractMetadataFromString, getMessageAndTraceFromError, hasLabel, stripAnsi } from "allure-js-commons/sdk"; +import { + extractMetadataFromString, + getMessageAndTraceFromError, + getMetadataLabel, + hasLabel, + stripAnsi, +} from "allure-js-commons/sdk"; import { ALLURE_RUNTIME_MESSAGE_CONTENT_TYPE, ReporterRuntime, createDefaultWriter, escapeRegExp, + formatLink, getEnvironmentLabels, getFrameworkLabel, getHostLabel, @@ -179,6 +187,7 @@ export class AllureReporter implements ReporterV2 { labels: [...titleMetadata.labels, ...getEnvironmentLabels()], links: [], parameters: [], + steps: [], testCaseId: md5(testCaseIdBase), fullName: `${relativeFile}:${test.location.line}:${test.location.column}`, }; @@ -197,6 +206,56 @@ export class AllureReporter implements ReporterV2 { result.labels!.push(...tags); } + if ("annotations" in test) { + for (const annotation of test.annotations) { + if (annotation.type === "skip" || annotation.type === "fixme") { + continue; + } + + if (annotation.type === "issue") { + result.links!.push( + formatLink(this.options.links ?? {}, { + type: LinkType.ISSUE, + url: annotation.description!, + }), + ); + continue; + } + + if (annotation.type === "tms" || annotation.type === "test_key") { + result.links!.push( + formatLink(this.options.links ?? {}, { + type: LinkType.TMS, + url: annotation.description!, + }), + ); + continue; + } + + if (annotation.type === "description") { + result.description = annotation.description; + continue; + } + + const annotationLabel = getMetadataLabel(annotation.type, annotation.description); + + if (annotationLabel) { + result.labels!.push(annotationLabel); + continue; + } + + result.steps!.push({ + name: `${annotation.type}: ${annotation.description!}`, + status: Status.PASSED, + stage: Stage.FINISHED, + parameters: [], + steps: [], + attachments: [], + statusDetails: {}, + }); + } + } + if (project?.name) { result.parameters!.push({ name: "Project", value: project.name }); } diff --git a/packages/allure-playwright/test/spec/annotations.spec.ts b/packages/allure-playwright/test/spec/annotations.spec.ts index a202879be..404c65e66 100644 --- a/packages/allure-playwright/test/spec/annotations.spec.ts +++ b/packages/allure-playwright/test/spec/annotations.spec.ts @@ -1,4 +1,5 @@ import { expect, it } from "vitest"; +import { LabelName, LinkType } from "allure-js-commons"; import { runPlaywrightInlineTest } from "../utils.js"; it("should support skip annotation", async () => { @@ -56,3 +57,143 @@ it("should support fixme annotation", async () => { ]), ); }); + +it("should support allure metadata in playwright annotation", async () => { + const { tests } = await runPlaywrightInlineTest({ + "sample.test.js": ` + import { test } from '@playwright/test'; + import { LabelName } from 'allure-js-commons'; + test('test full report', { + annotation: [ + { type: "skip", description: "skipped via skip annotation" }, + { type: "fixme", description: "skipped via fixme annotation" }, + { type: "@allure.id", description: "foo" }, + { type: "@allure.label.foo", description: "bar" }, + { type: "allure.label.epic", description: "baz" }, + { type: "issue", description: "anything 2" }, + { type: "tms", description: "anything 3" }, + { type: "test_key", description: "anything 4" }, + { type: "description", description: "new test description" }, + ], + }, async () => { + }); + `, + }); + + expect(tests).toHaveLength(1); + expect(tests[0].description).toBe("new test description"); + expect(tests[0].labels).not.toContainEqual({ name: "description", value: "new test description" }); + expect(tests[0].labels).not.toContainEqual({ name: "skip", value: "skipped via skip annotation" }); + expect(tests[0].labels).not.toContainEqual({ + name: "fixme", + value: "skipped via fixme annotation", + }); + expect(tests[0].labels).toContainEqual({ name: LabelName.ALLURE_ID, value: "foo" }); + expect(tests[0].labels).toContainEqual({ name: LabelName.EPIC, value: "baz" }); + expect(tests[0].labels).toContainEqual({ name: "foo", value: "bar" }); + expect(tests[0].links).toContainEqual({ type: LinkType.ISSUE, url: "anything 2" }); + expect(tests[0].links).toContainEqual({ type: LinkType.TMS, url: "anything 3" }); + expect(tests[0].links).toContainEqual({ type: LinkType.TMS, url: "anything 4" }); +}); + +it("should append unknown playwright annotation to the test description", async () => { + const { tests } = await runPlaywrightInlineTest({ + "sample.test.js": ` + import { test } from '@playwright/test'; + import { LabelName } from 'allure-js-commons'; + test('test full report', { + annotation: [ + { type: "skip", description: "skipped via skip annotation" }, + { type: "fixme", description: "skipped via fixme annotation" }, + { type: "@allure.id", description: "foo" }, + { type: "@allure.label.foo", description: "bar" }, + { type: "allure.label.epic", description: "baz" }, + { type: "issue", description: "anything 2" }, + { type: "tms", description: "anything 3" }, + { type: "test_key", description: "anything 4" }, + { type: "description", description: "new test description" }, + { type: "unknown", description: "unknown annotation" }, + ], + }, async () => { + }); + `, + }); + + expect(tests).toHaveLength(1); + expect(tests[0].description).toBe("new test description"); + expect(tests[0].labels).not.toContainEqual({ name: "description", value: "new test description" }); + expect(tests[0].labels).not.toContainEqual({ name: "skip", value: "skipped via skip annotation" }); + expect(tests[0].labels).not.toContainEqual({ + name: "fixme", + value: "skipped via fixme annotation", + }); + expect(tests[0].labels).toContainEqual({ name: LabelName.ALLURE_ID, value: "foo" }); + expect(tests[0].labels).toContainEqual({ name: LabelName.EPIC, value: "baz" }); + expect(tests[0].labels).toContainEqual({ name: "foo", value: "bar" }); + expect(tests[0].links).toContainEqual({ type: LinkType.ISSUE, url: "anything 2" }); + expect(tests[0].links).toContainEqual({ type: LinkType.TMS, url: "anything 3" }); + expect(tests[0].links).toContainEqual({ type: LinkType.TMS, url: "anything 4" }); + expect(tests[0].steps).toContainEqual(expect.objectContaining({ name: "unknown: unknown annotation" })); +}); + +it("should support allure metadata in playwright group annotation", async () => { + const { tests } = await runPlaywrightInlineTest({ + "sample.test.js": ` + import { test } from '@playwright/test'; + import { LabelName } from 'allure-js-commons'; + test.describe( + 'nested', + { + annotation: [ + { type: "skip", description: "skipped via skip annotation" }, + { type: "fixme", description: "skipped via fixme annotation" }, + { type: "@allure.id", description: "foo" }, + { type: "@allure.label.foo", description: "bar" }, + { type: "allure.label.epic", description: "baz" }, + { type: "issue", description: "anything 2" }, + { type: "tms", description: "anything 3" }, + { type: "test_key", description: "anything 4" }, + { type: "description", description: "new test description" }, + { type: "unknown", description: "unknown annotation" }, + ], + }, + () => { + test('test full report 1', async () => { + }); + test('test full report 2', async () => { + }); + }, + ); + `, + }); + + expect(tests).toHaveLength(2); + expect(tests[0].description).toBe("new test description"); + expect(tests[0].labels).not.toContainEqual({ name: "description", value: "new test description" }); + expect(tests[0].labels).not.toContainEqual({ name: "skip", value: "skipped via skip annotation" }); + expect(tests[0].labels).not.toContainEqual({ + name: "fixme", + value: "skipped via fixme annotation", + }); + expect(tests[0].labels).toContainEqual({ name: LabelName.ALLURE_ID, value: "foo" }); + expect(tests[0].labels).toContainEqual({ name: LabelName.EPIC, value: "baz" }); + expect(tests[0].labels).toContainEqual({ name: "foo", value: "bar" }); + expect(tests[0].links).toContainEqual({ type: LinkType.ISSUE, url: "anything 2" }); + expect(tests[0].links).toContainEqual({ type: LinkType.TMS, url: "anything 3" }); + expect(tests[0].links).toContainEqual({ type: LinkType.TMS, url: "anything 4" }); + expect(tests[0].steps).toContainEqual(expect.objectContaining({ name: "unknown: unknown annotation" })); + expect(tests[1].description).toBe("new test description"); + expect(tests[1].labels).not.toContainEqual({ name: "description", value: "new test description" }); + expect(tests[1].labels).not.toContainEqual({ name: "skip", value: "skipped via skip annotation" }); + expect(tests[1].labels).not.toContainEqual({ + name: "fixme", + value: "skipped via fixme annotation", + }); + expect(tests[1].labels).toContainEqual({ name: LabelName.ALLURE_ID, value: "foo" }); + expect(tests[1].labels).toContainEqual({ name: LabelName.EPIC, value: "baz" }); + expect(tests[1].labels).toContainEqual({ name: "foo", value: "bar" }); + expect(tests[1].links).toContainEqual({ type: LinkType.ISSUE, url: "anything 2" }); + expect(tests[1].links).toContainEqual({ type: LinkType.TMS, url: "anything 3" }); + expect(tests[1].links).toContainEqual({ type: LinkType.TMS, url: "anything 4" }); + expect(tests[1].steps).toContainEqual(expect.objectContaining({ name: "unknown: unknown annotation" })); +}); diff --git a/packages/allure-playwright/test/spec/runtime/legacy/tags.spec.ts b/packages/allure-playwright/test/spec/runtime/legacy/tags.spec.ts index facced14b..de4640e00 100644 --- a/packages/allure-playwright/test/spec/runtime/legacy/tags.spec.ts +++ b/packages/allure-playwright/test/spec/runtime/legacy/tags.spec.ts @@ -24,7 +24,7 @@ it("sets multiply tags", async () => { { name: LabelName.TAG, value: "TestInfo" }, { name: LabelName.TAG, value: "some" }, { name: LabelName.TAG, value: "other" }, - { name: LabelName.TAG, value: "other" }, + { name: LabelName.TAG, value: "tags" }, ]), }), ]); diff --git a/packages/allure-playwright/test/spec/runtime/modern/tags.spec.ts b/packages/allure-playwright/test/spec/runtime/modern/tags.spec.ts index 0e9f114cf..2e1b84dd3 100644 --- a/packages/allure-playwright/test/spec/runtime/modern/tags.spec.ts +++ b/packages/allure-playwright/test/spec/runtime/modern/tags.spec.ts @@ -25,7 +25,7 @@ it("sets multiply tags", async () => { { name: LabelName.TAG, value: "TestInfo" }, { name: LabelName.TAG, value: "some" }, { name: LabelName.TAG, value: "other" }, - { name: LabelName.TAG, value: "other" }, + { name: LabelName.TAG, value: "tags" }, ]), }), ]);