From 08eea54bf84c58a1f09b776432ecee45da4b6302 Mon Sep 17 00:00:00 2001 From: Konstantin Epishev Date: Tue, 2 Jul 2024 14:15:06 +0200 Subject: [PATCH] cypress test plan support (via #1037) --- packages/allure-cypress/src/index.ts | 38 +++++++-- packages/allure-cypress/src/model.ts | 2 +- packages/allure-cypress/src/reporter.ts | 5 +- packages/allure-cypress/src/utils.ts | 34 +++++--- .../allure-cypress/test/spec/testplan.test.ts | 83 +++++++++++++++++++ packages/allure-cypress/test/utils.ts | 6 +- 6 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 packages/allure-cypress/test/spec/testplan.test.ts diff --git a/packages/allure-cypress/src/index.ts b/packages/allure-cypress/src/index.ts index f45c29c9d..8cb506c32 100644 --- a/packages/allure-cypress/src/index.ts +++ b/packages/allure-cypress/src/index.ts @@ -1,6 +1,6 @@ import type { AttachmentOptions, Label, Link, ParameterMode, ParameterOptions, StatusDetails } from "allure-js-commons"; import { ContentType, Status } from "allure-js-commons"; -import type { RuntimeMessage } from "allure-js-commons/sdk"; +import type { RuntimeMessage, TestPlanV1 } from "allure-js-commons/sdk"; import { getUnfinishedStepsMessages, hasStepMessage } from "allure-js-commons/sdk"; import type { TestRuntime } from "allure-js-commons/sdk/runtime"; import { getGlobalTestRuntime, setGlobalTestRuntime } from "allure-js-commons/sdk/runtime"; @@ -13,12 +13,13 @@ import type { CypressTest, CypressTestStartMessage, } from "./model.js"; -import { ALLURE_REPORT_SHUTDOWN_HOOK, ALLURE_REPORT_STEP_COMMAND } from "./model.js"; +import { ALLURE_REPORT_STEP_COMMAND, ALLURE_REPORT_SYSTEM_HOOK } from "./model.js"; import { getHookType, getSuitePath, isCommandShouldBeSkipped, isGlobalHook, + isTestPresentInTestPlan, toReversed, uint8ArrayToBase64, } from "./utils.js"; @@ -231,7 +232,7 @@ const initializeAllure = () => { setGlobalTestRuntime(testRuntime); }) .on(EVENT_HOOK_BEGIN, (hook: CypressHook) => { - if (hook.title.includes(ALLURE_REPORT_SHUTDOWN_HOOK)) { + if (hook.title.includes(ALLURE_REPORT_SYSTEM_HOOK)) { return; } @@ -252,7 +253,7 @@ const initializeAllure = () => { }); }) .on(EVENT_HOOK_END, (hook: CypressHook) => { - if (hook.title.includes(ALLURE_REPORT_SHUTDOWN_HOOK)) { + if (hook.title.includes(ALLURE_REPORT_SYSTEM_HOOK)) { return; } @@ -408,6 +409,11 @@ const initializeAllure = () => { }) .on(EVENT_TEST_PENDING, (test: CypressTest) => { const testRuntime = new AllureCypressTestRuntime(); + const testPlan = Cypress.env("allureTestPlan") as TestPlanV1; + + if (testPlan && !isTestPresentInTestPlan(Cypress.currentTest, Cypress.spec, testPlan)) { + return; + } testRuntime.sendMessageAsync({ type: "cypress_test_start", @@ -516,7 +522,29 @@ const initializeAllure = () => { }); }); - after(ALLURE_REPORT_SHUTDOWN_HOOK, () => { + before(ALLURE_REPORT_SYSTEM_HOOK, () => { + cy.task("readAllureTestPlan", {}, { log: false }).then((testPlan) => { + if (!testPlan) { + return; + } + + Cypress.env("allureTestPlan", testPlan); + }); + }); + + beforeEach(ALLURE_REPORT_SYSTEM_HOOK, function () { + const testPlan = Cypress.env("allureTestPlan") as TestPlanV1; + + if (!testPlan) { + return; + } + + if (!isTestPresentInTestPlan(Cypress.currentTest, Cypress.spec, testPlan)) { + this.skip(); + } + }); + + after(ALLURE_REPORT_SYSTEM_HOOK, () => { const runtimeMessages = Cypress.env("allureRuntimeMessages") as CypressMessage[]; cy.task( diff --git a/packages/allure-cypress/src/model.ts b/packages/allure-cypress/src/model.ts index 62f3ba4cb..fffadcaf4 100644 --- a/packages/allure-cypress/src/model.ts +++ b/packages/allure-cypress/src/model.ts @@ -1,7 +1,7 @@ import type { Status, StatusDetails } from "allure-js-commons"; import type { RuntimeMessage } from "allure-js-commons/sdk"; -export const ALLURE_REPORT_SHUTDOWN_HOOK = "__allure_report_shutdown_hook__"; +export const ALLURE_REPORT_SYSTEM_HOOK = "__allure_report_system_hook__"; export const ALLURE_REPORT_STEP_COMMAND = "__allure_report_step_command__"; diff --git a/packages/allure-cypress/src/reporter.ts b/packages/allure-cypress/src/reporter.ts index 0414cd831..2003bb3d1 100644 --- a/packages/allure-cypress/src/reporter.ts +++ b/packages/allure-cypress/src/reporter.ts @@ -2,7 +2,7 @@ import type Cypress from "cypress"; import { ContentType, LabelName, Stage, Status } from "allure-js-commons"; import type { RuntimeMessage } from "allure-js-commons/sdk"; import { extractMetadataFromString } from "allure-js-commons/sdk"; -import { FileSystemWriter, ReporterRuntime, getSuiteLabels } from "allure-js-commons/sdk/reporter"; +import { FileSystemWriter, ReporterRuntime, getSuiteLabels, parseTestPlan } from "allure-js-commons/sdk/reporter"; import type { Config } from "allure-js-commons/sdk/reporter"; import type { CypressHookEndMessage, @@ -42,6 +42,9 @@ export class AllureCypress { attachToCypress(on: Cypress.PluginEvents) { on("task", { + readAllureTestPlan: () => { + return parseTestPlan() ?? null; + }, allureReportTest: ({ messages, absolutePath }: { messages: CypressMessage[]; absolutePath: string }) => { this.messagesByAbsolutePath.set(absolutePath, messages); diff --git a/packages/allure-cypress/src/utils.ts b/packages/allure-cypress/src/utils.ts index 682c0b500..2e51a5478 100644 --- a/packages/allure-cypress/src/utils.ts +++ b/packages/allure-cypress/src/utils.ts @@ -1,3 +1,6 @@ +import { LabelName } from "allure-js-commons"; +import type { TestPlanV1 } from "allure-js-commons/sdk"; +import { extractMetadataFromString } from "allure-js-commons/sdk"; import type { CypressCommand } from "./model.js"; import { ALLURE_REPORT_STEP_COMMAND } from "./model.js"; @@ -13,17 +16,6 @@ export const uint8ArrayToBase64 = (data: unknown) => { return btoa(String.fromCharCode.apply(null, data as number[])); }; -export const normalizeAttachmentContentEncoding = (data: unknown, encoding: BufferEncoding): BufferEncoding => { - // @ts-ignore - const u8arrayLike = Array.isArray(data) || data.buffer; - - if (u8arrayLike) { - return "base64"; - } - - return encoding; -}; - export const getSuitePath = (test: Mocha.Test): string[] => { const path: string[] = []; let currentSuite: Mocha.Suite | undefined = test.parent; @@ -82,3 +74,23 @@ export const getHookType = (hookName: string) => { export const last = (arr: T[]): T | undefined => { return arr[arr.length - 1]; }; + +export const isTestPresentInTestPlan = ( + test: { + title: string; + titlePath: string[]; + }, + spec: Cypress.Spec, + testPlan: TestPlanV1, +) => { + const testFullName = `${spec.relative}#${test.titlePath.join(" ")}`; + const { labels } = extractMetadataFromString(test.title); + const allureIdLabel = labels.find(({ name }) => name === LabelName.ALLURE_ID); + + return testPlan.tests.some(({ id, selector = "" }) => { + const idMatched = id ? String(id) === allureIdLabel?.value : false; + const selectorMatched = selector === testFullName; + + return idMatched || selectorMatched; + }); +}; diff --git a/packages/allure-cypress/test/spec/testplan.test.ts b/packages/allure-cypress/test/spec/testplan.test.ts new file mode 100644 index 000000000..d690eb3fc --- /dev/null +++ b/packages/allure-cypress/test/spec/testplan.test.ts @@ -0,0 +1,83 @@ +import { join } from "node:path"; +import { expect, it } from "vitest"; +import type { TestPlanV1 } from "allure-js-commons/sdk"; +import { runCypressInlineTest } from "../utils.js"; + +it("respects testplan", async () => { + const exampleTestPlan: TestPlanV1 = { + version: "1.0", + tests: [ + { + id: 1, + selector: "cypress/e2e/nested/super strange nested/super strange name.cy.js#also nested should execute", + }, + { + id: 2, + selector: "cypress/e2e/b.cy.js#should execute", + }, + { + id: 3, + selector: "cypress/e2e/.+.cy.js#+.", + }, + { + id: 4, + selector: "cypress/e2e/notaga.cy.js#a", + }, + ], + }; + const testPlanFilename = "example-testplan.json"; + const { tests } = await runCypressInlineTest( + { + [testPlanFilename]: () => JSON.stringify(exampleTestPlan), + "cypress/e2e/a.test.js": () => ` + it('should not execute', () => { + cy.wrap(1).should("eq", 1); + }); + `, + "cypress/e2e/b.cy.js": () => ` + it('should execute', () => { + cy.wrap(1).should("eq", 1); + }); + `, + "cypress/e2e/nested/super strange nested/super strange name.cy.js": () => ` + describe('also nested', () => { + it('should execute', () => { + cy.wrap(1).should("eq", 1); + }); + }); + `, + "cypress/e2e/.+.cy.js": () => ` + it('+.', () => { + cy.wrap(1).should("eq", 1); + }); + `, + "cypress/e2e/aga.cy.js": () => ` + it('a', () => { + cy.wrap(1).should("eq", 1); + }); + it('aa', () => { + cy.wrap(1).should("eq", 1); + }); + it('selected name @allure.id=5', () => { + cy.wrap(1).should("eq", 1); + }); + `, + "cypress/e2e/notaga.cy.js": () => ` + it('a', () => { + cy.wrap(1).should("eq", 1); + }); + `, + }, + (testDir) => ({ + ALLURE_TESTPLAN_PATH: join(testDir, testPlanFilename), + }), + ); + + expect(tests.map(({ fullName }) => fullName)).toEqual( + expect.arrayContaining([ + "cypress/e2e/b.cy.js#should execute", + "cypress/e2e/notaga.cy.js#a", + "cypress/e2e/nested/super strange nested/super strange name.cy.js#also nested should execute", + ]), + ); +}); diff --git a/packages/allure-cypress/test/utils.ts b/packages/allure-cypress/test/utils.ts index 917679fb9..cfedf4076 100644 --- a/packages/allure-cypress/test/utils.ts +++ b/packages/allure-cypress/test/utils.ts @@ -10,7 +10,10 @@ type CypressTestFiles = Record< (modulesPaths: { allureCommonsModulePath: string; allureCypressModulePath: string }) => string >; -export const runCypressInlineTest = async (testFiles: CypressTestFiles): Promise => { +export const runCypressInlineTest = async ( + testFiles: CypressTestFiles, + env?: (testDir: string) => Record, +): Promise => { const res: AllureResults = { tests: [], groups: [], @@ -70,6 +73,7 @@ export const runCypressInlineTest = async (testFiles: CypressTestFiles): Promise const testProcess = fork(modulePath, args, { env: { ...process.env, + ...env?.(testDir), }, cwd: testDir, stdio: "pipe",