From 52972d4f4ad9a0a0780f78495e494fcdaab7306a Mon Sep 17 00:00:00 2001 From: epszaw Date: Mon, 23 Dec 2024 15:18:13 +0100 Subject: [PATCH] make utility to create trees based on labels more abstract add retry flag to the awesome test result fix retry awesome filter --- packages/e2e/test/allure-awesome/tree.test.ts | 80 ++++++++++++ packages/plugin-api/src/utils/tree.ts | 54 +++++--- packages/plugin-api/test/tree.test.ts | 15 ++- packages/plugin-awesome/src/converters.ts | 1 + packages/plugin-awesome/src/generators.ts | 29 ++++- packages/plugin-awesome/src/plugin.ts | 8 +- packages/sandbox/package.json | 4 +- .../src/components/app/ReportBody/Filters.tsx | 4 + .../src/components/commons/Toggle/index.tsx | 3 +- packages/web-awesome/src/utils/treeFilters.ts | 7 +- .../test/utils/treeFilters.test.ts | 120 +++++++++--------- packages/web-awesome/types.d.ts | 5 +- 12 files changed, 233 insertions(+), 97 deletions(-) diff --git a/packages/e2e/test/allure-awesome/tree.test.ts b/packages/e2e/test/allure-awesome/tree.test.ts index 83e125d..e59ad85 100644 --- a/packages/e2e/test/allure-awesome/tree.test.ts +++ b/packages/e2e/test/allure-awesome/tree.test.ts @@ -147,6 +147,86 @@ test.describe("commons", () => { }); }); +test.describe("filters", () => { + test.describe("retry", () => { + test.beforeAll(async () => { + bootstrap = await boostrapReport({ + reportConfig: { + name: "Sample allure report", + appendHistory: false, + history: undefined, + historyPath: undefined, + knownIssuesPath: undefined, + }, + testResults: [ + { + name: "0 sample test", + fullName: "sample.js#0 sample test", + historyId: "foo", + status: Status.FAILED, + stage: Stage.FINISHED, + start: 0, + statusDetails: { + message: "Assertion error: Expected 1 to be 2", + trace: "failed test trace", + }, + }, + { + name: "0 sample test", + fullName: "sample.js#0 sample test", + historyId: "foo", + status: Status.FAILED, + stage: Stage.FINISHED, + start: 1000, + statusDetails: { + message: "Assertion error: Expected 1 to be 2", + trace: "failed test trace", + }, + }, + { + name: "0 sample test", + fullName: "sample.js#0 sample test", + historyId: "foo", + status: Status.PASSED, + stage: Stage.FINISHED, + start: 2000, + }, + { + name: "1 sample test", + fullName: "sample.js#1 sample test", + historyId: "bar", + status: Status.PASSED, + stage: Stage.FINISHED, + start: 3000, + }, + { + name: "2 sample test", + fullName: "sample.js#2 sample test", + historyId: "baz", + status: Status.PASSED, + stage: Stage.FINISHED, + start: 4000, + }, + ], + }); + }); + + test("shows only tests with retries", async ({ page }) => { + const treeLeaves = page.getByTestId("tree-leaf"); + + await expect(treeLeaves).toHaveCount(3); + await page.getByTestId("filters-button").click(); + await page.getByTestId("retry-filter").click(); + + await expect(treeLeaves).toHaveCount(1); + + await page.getByTestId("retry-filter").click(); + + await expect(treeLeaves).toHaveCount(3); + }); + }); +}); + test.describe("suites", () => { test.beforeAll(async () => { bootstrap = await boostrapReport({ diff --git a/packages/plugin-api/src/utils/tree.ts b/packages/plugin-api/src/utils/tree.ts index 5cde804..add9a81 100644 --- a/packages/plugin-api/src/utils/tree.ts +++ b/packages/plugin-api/src/utils/tree.ts @@ -9,7 +9,7 @@ import { type WithChildren, findByLabelName, } from "@allurereport/core-api"; -import { emptyStatistic, incrementStatistic } from "@allurereport/core-api"; +import { emptyStatistic } from "@allurereport/core-api"; import { md5 } from "./misc.js"; const addLeaf = (node: WithChildren, nodeId: string) => { @@ -108,26 +108,40 @@ export const filterTreeLabels = (data: TestResult[], labelNames: string[]) => { .reverse(); }; -export const createTreeByLabels = (data: TestResult[], labelNames: string[]) => { - return createTree( +export const createTreeByLabels = ( + data: T[], + labelNames: string[], + leafFactory?: (item: T) => TreeLeaf, + groupFactory?: (parentGroup: string | undefined, groupClassifier: string) => TreeGroup, + addLeafToGroup: (group: TreeGroup, leaf: TreeLeaf) => void = () => {}, +) => { + const leafFactoryFn = + leafFactory ?? + ((tr: T) => { + const { id, name, status, duration } = tr as TestResult; + + return { + nodeId: id, + name, + status, + duration, + } as unknown as TreeLeaf; + }); + const groupFactoryFn = + groupFactory ?? + ((parentId, groupClassifier) => + ({ + nodeId: md5((parentId ? `${parentId}.` : "") + groupClassifier), + name: groupClassifier, + statistic: emptyStatistic(), + }) as unknown as TreeGroup); + + return createTree( data, - (item) => byLabels(item, labelNames), - ({ id, name, status, duration, flaky, start }) => ({ - nodeId: id, - name, - status, - duration, - flaky, - start, - }), - (parentId, groupClassifier) => ({ - nodeId: md5((parentId ? `${parentId}.` : "") + groupClassifier), - name: groupClassifier, - statistic: emptyStatistic(), - }), - (group, leaf) => { - incrementStatistic(group.statistic, leaf.status); - }, + (item) => byLabels(item as TestResult, labelNames), + leafFactoryFn, + groupFactoryFn, + addLeafToGroup, ); }; diff --git a/packages/plugin-api/test/tree.test.ts b/packages/plugin-api/test/tree.test.ts index ae7f1a4..170649e 100644 --- a/packages/plugin-api/test/tree.test.ts +++ b/packages/plugin-api/test/tree.test.ts @@ -1,4 +1,4 @@ -import { TestResult, TreeData, compareBy, nullsLast, ordinal } from "@allurereport/core-api"; +import { type TestResult, type TreeData, compareBy, nullsLast, ordinal } from "@allurereport/core-api"; import { randomUUID } from "node:crypto"; import { describe, expect, it } from "vitest"; import { createTreeByLabels, filterTree, filterTreeLabels, sortTree, transformTree } from "../src/index.js"; @@ -39,6 +39,13 @@ const sampleTree = { g2: { nodeId: "g2", name: "2", groups: [], leaves: ["l5", "l6"] }, }, }; +const sampleLeafFactory = (tr: TestResult) => ({ + nodeId: tr.id, + name: tr.name, + status: tr.status, + duration: tr.duration, + flaky: tr.flaky, +}); describe("tree builder", () => { it("should create empty tree", async () => { @@ -54,7 +61,7 @@ describe("tree builder", () => { it("should create tree without groups", async () => { const tr1 = itResult({ name: "first" }); const tr2 = itResult({ name: "second" }); - const treeByLabels = createTreeByLabels([tr1, tr2], []); + const treeByLabels = createTreeByLabels([tr1, tr2], [], sampleLeafFactory); expect(treeByLabels.root.groups).toHaveLength(0); expect(treeByLabels.root.leaves).toContain(tr1.id); @@ -92,7 +99,7 @@ describe("tree builder", () => { { name: "story", value: "A" }, ], }); - const treeByLabels = createTreeByLabels([tr1, tr2, tr3], ["feature"]); + const treeByLabels = createTreeByLabels([tr1, tr2, tr3], ["feature"], sampleLeafFactory); expect(treeByLabels.root.groups).toHaveLength(2); const rootGroup1 = treeByLabels.root.groups![0]; @@ -152,7 +159,7 @@ describe("tree builder", () => { { name: "story", value: "A" }, ], }); - const treeByLabels = createTreeByLabels([tr1, tr2, tr3], ["feature"]); + const treeByLabels = createTreeByLabels([tr1, tr2, tr3], ["feature"], sampleLeafFactory); expect(treeByLabels.root.leaves).toHaveLength(1); expect(treeByLabels.root.leaves).toContain(tr2.id); diff --git a/packages/plugin-awesome/src/converters.ts b/packages/plugin-awesome/src/converters.ts index c04d21c..e1bfa0e 100644 --- a/packages/plugin-awesome/src/converters.ts +++ b/packages/plugin-awesome/src/converters.ts @@ -43,6 +43,7 @@ export const convertTestResult = (tr: TestResult): AllureAwesomeTestResult => { history: [], retries: [], breadcrumbs: [], + retry: false, }; }; diff --git a/packages/plugin-awesome/src/generators.ts b/packages/plugin-awesome/src/generators.ts index 6b86f5c..4a42cd9 100644 --- a/packages/plugin-awesome/src/generators.ts +++ b/packages/plugin-awesome/src/generators.ts @@ -2,8 +2,8 @@ import { type AttachmentLink, type EnvironmentItem, type Statistic, - type TestResult, compareBy, + incrementStatistic, nullsLast, ordinal, } from "@allurereport/core-api"; @@ -13,6 +13,8 @@ import type { AllureAwesomeFixtureResult, AllureAwesomeReportOptions, AllureAwesomeTestResult, + AllureAwesomeTreeGroup, + AllureAwesomeTreeLeaf, } from "@allurereport/web-awesome"; import { createBaseUrlScript, @@ -118,6 +120,7 @@ export const generateTestResults = async (writer: AllureAwesomeDataWriter, store convertedTr.history = await store.historyByTrId(tr.id); convertedTr.retries = await store.retriesByTrId(tr.id); + convertedTr.retry = convertedTr.retries.length > 0; convertedTr.setup = convertedTrFixtures.filter((f) => f.type === "before"); convertedTr.teardown = convertedTrFixtures.filter((f) => f.type === "after"); // FIXME: the type is correct, but typescript still shows an error @@ -144,16 +147,36 @@ export const generateTestResults = async (writer: AllureAwesomeDataWriter, store "nav.json", convertedTrs.filter(({ hidden }) => !hidden).map(({ id }) => id), ); + + return convertedTrs; }; export const generateTree = async ( writer: AllureAwesomeDataWriter, treeName: string, labels: string[], - tests: TestResult[], + tests: AllureAwesomeTestResult[], ) => { const visibleTests = tests.filter((test) => !test.hidden); - const tree = createTreeByLabels(visibleTests, labels); + const tree = createTreeByLabels( + visibleTests, + labels, + ({ id, name, status, duration, flaky, start, retries }) => { + return { + nodeId: id, + retry: !!retries?.length, + name, + status, + duration, + flaky, + start, + }; + }, + undefined, + (group, leaf) => { + incrementStatistic(group.statistic, leaf.status); + }, + ); // @ts-ignore filterTree(tree, (leaf) => !leaf.hidden); diff --git a/packages/plugin-awesome/src/plugin.ts b/packages/plugin-awesome/src/plugin.ts index 7a40c04..abaff0a 100644 --- a/packages/plugin-awesome/src/plugin.ts +++ b/packages/plugin-awesome/src/plugin.ts @@ -22,19 +22,19 @@ export class AllureAwesomePlugin implements Plugin { const { singleFile, groupBy } = this.options ?? {}; const environmentItems = await store.metadataByKey("allure_environment"); const statistic = await store.testsStatistic(); - const allTr = await store.allTestResults({ includeHidden: true }); const attachments = await store.allAttachments(); await generateStatistic(this.#writer!, statistic); await generatePieChart(this.#writer!, statistic); + + const convertedTrs = await generateTestResults(this.#writer!, store); + await generateTree( this.#writer!, "tree", groupBy?.length ? groupBy : ["parentSuite", "suite", "subSuite"], - allTr, + convertedTrs, ); - - await generateTestResults(this.#writer!, store); await generateHistoryDataPoints(this.#writer!, store); if (environmentItems?.length) { diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json index 1a367b0..01f4e91 100644 --- a/packages/sandbox/package.json +++ b/packages/sandbox/package.json @@ -9,9 +9,7 @@ "type": "module", "scripts": { "pret": "rimraf ./allure-results", - "t": "vitest run", - "prereport": "rimraf ./allure-report", - "report": "yarn allure generate ./allure-results" + "test": "yarn allure run -- vitest run" }, "devDependencies": { "@allurereport/plugin-csv": "workspace:*", diff --git a/packages/web-awesome/src/components/app/ReportBody/Filters.tsx b/packages/web-awesome/src/components/app/ReportBody/Filters.tsx index ac003c7..a35ee84 100644 --- a/packages/web-awesome/src/components/app/ReportBody/Filters.tsx +++ b/packages/web-awesome/src/components/app/ReportBody/Filters.tsx @@ -24,6 +24,7 @@ export const Filters = () => { size="m" style="outline" isActive={isOpened} + data-testid="filters-button" onClick={onClick} /> @@ -43,6 +44,7 @@ export const Filters = () => { focusable={false} value={flaky} label={t("enable-filter", { filter: t("flaky") })} + data-testid="flaky-filter" onChange={(value) => setTreeFilter("flaky", value)} /> @@ -61,6 +63,7 @@ export const Filters = () => { focusable={false} value={retry} label={t("enable-filter", { filter: t("retry") })} + data-testid="retry-filter" onChange={(value) => setTreeFilter("retry", value)} /> @@ -79,6 +82,7 @@ export const Filters = () => { focusable={false} value={isNew} label={t("enable-filter", { filter: t("new") })} + data-testid="new-filter" onChange={(value) => setTreeFilter("new", value)} /> diff --git a/packages/web-awesome/src/components/commons/Toggle/index.tsx b/packages/web-awesome/src/components/commons/Toggle/index.tsx index a98303a..2b44f33 100644 --- a/packages/web-awesome/src/components/commons/Toggle/index.tsx +++ b/packages/web-awesome/src/components/commons/Toggle/index.tsx @@ -8,7 +8,7 @@ type Props = { }; export const Toggle = (props: Props) => { - const { value, label, onChange, focusable = true } = props; + const { value, label, onChange, focusable = true, ...rest } = props; const handleChange = (e: Event) => { const newValue = !(e.target as HTMLInputElement).checked; @@ -17,6 +17,7 @@ export const Toggle = (props: Props) => { return ( 0; + const retryMatched = !filterOptions?.filter?.retry || leaf.retry; // TODO: at this moment we don't have a new field implementation even in the generator // const newMatched = !filterOptions?.filter?.new || leaf.new; @@ -66,10 +66,13 @@ export const createRecursiveTree = (payload: { filterOptions?: TreeFiltersState; }): AllureAwesomeRecursiveTree => { const { group, groupsById, leavesById, filterOptions } = payload; + const groupLeaves = group.leaves ?? []; return { ...group, - leaves: filterLeaves(group.leaves, leavesById, filterOptions), + // FIXME: don't have any idea, why eslint marks next line as unsafe because it actually has a correct type + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + leaves: filterLeaves(groupLeaves, leavesById, filterOptions), trees: group?.groups ?.filter((groupId) => { const subGroup = groupsById[groupId]; diff --git a/packages/web-awesome/test/utils/treeFilters.test.ts b/packages/web-awesome/test/utils/treeFilters.test.ts index 8b93345..f9cd862 100644 --- a/packages/web-awesome/test/utils/treeFilters.test.ts +++ b/packages/web-awesome/test/utils/treeFilters.test.ts @@ -1,4 +1,3 @@ -import { type TestResult } from "@allurereport/core-api"; import { describe, expect, it } from "vitest"; import { createRecursiveTree, filterLeaves } from "../../src/utils/treeFilters.js"; import type { AllureAwesomeTestResult } from "../../types.js"; @@ -25,9 +24,9 @@ describe("utils > treeFilters", () => { const result = filterLeaves(leaves, leavesById); expect(result).toEqual([ - expect.objectContaining({ name: "a1", groupOrder: 1 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "c3", groupOrder: 3 }), + expect.objectContaining({ name: "a1" }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "c3" }), ]); }); @@ -55,10 +54,7 @@ describe("utils > treeFilters", () => { status: "passed", }); - expect(result).toEqual([ - expect.objectContaining({ name: "a1", groupOrder: 1 }), - expect.objectContaining({ name: "c3", groupOrder: 2 }), - ]); + expect(result).toEqual([expect.objectContaining({ name: "a1" }), expect.objectContaining({ name: "c3" })]); }); it("returns the flaky leaves", () => { @@ -87,10 +83,7 @@ describe("utils > treeFilters", () => { }, }); - expect(result).toEqual([ - expect.objectContaining({ name: "a1", groupOrder: 1 }), - expect.objectContaining({ name: "c3", groupOrder: 2 }), - ]); + expect(result).toEqual([expect.objectContaining({ name: "a1" }), expect.objectContaining({ name: "c3" })]); }); it("returns leaves which contains retries", () => { @@ -100,16 +93,17 @@ describe("utils > treeFilters", () => { a1: { name: "a1", start: baseDate, - retries: [{}], + retry: true, } as AllureAwesomeTestResult, b2: { name: "b2", start: baseDate + 1000, - retries: [] as TestResult[], + retry: false, } as AllureAwesomeTestResult, c3: { name: "c3", start: baseDate + 2000, + retry: false, } as AllureAwesomeTestResult, }; const result = filterLeaves(leaves, leavesById, { @@ -118,7 +112,7 @@ describe("utils > treeFilters", () => { }, }); - expect(result).toEqual([expect.objectContaining({ name: "a1", groupOrder: 1 })]); + expect(result).toEqual([expect.objectContaining({ name: "a1" })]); }); it("sorts leave by duration in ascending order", () => { @@ -143,9 +137,9 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "a1", groupOrder: 1 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "c3", groupOrder: 3 }), + expect.objectContaining({ name: "a1" }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "c3" }), ]); }); @@ -171,9 +165,9 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "c3", groupOrder: 3 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "a1", groupOrder: 1 }), + expect.objectContaining({ name: "c3" }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "a1" }), ]); }); @@ -196,9 +190,9 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "a1", groupOrder: 1 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "c3", groupOrder: 3 }), + expect.objectContaining({ name: "a1" }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "c3" }), ]); }); @@ -221,9 +215,9 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "c3", groupOrder: 3 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "a1", groupOrder: 1 }), + expect.objectContaining({ name: "c3" }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "a1" }), ]); }); @@ -257,11 +251,11 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "c3", groupOrder: 3 }), - expect.objectContaining({ name: "a1", groupOrder: 1 }), - expect.objectContaining({ name: "e5", groupOrder: 5 }), - expect.objectContaining({ name: "d4", groupOrder: 4 }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "c3" }), + expect.objectContaining({ name: "a1" }), + expect.objectContaining({ name: "e5" }), + expect.objectContaining({ name: "d4" }), ]); }); @@ -295,11 +289,11 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "d4", groupOrder: 4 }), - expect.objectContaining({ name: "e5", groupOrder: 5 }), - expect.objectContaining({ name: "a1", groupOrder: 1 }), - expect.objectContaining({ name: "c3", groupOrder: 3 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), + expect.objectContaining({ name: "d4" }), + expect.objectContaining({ name: "e5" }), + expect.objectContaining({ name: "a1" }), + expect.objectContaining({ name: "c3" }), + expect.objectContaining({ name: "b2" }), ]); }); @@ -310,14 +304,17 @@ describe("utils > treeFilters", () => { a1: { name: "a1", start: baseDate + 2000, + groupOrder: 3, } as AllureAwesomeTestResult, b2: { name: "b2", start: baseDate + 1000, + groupOrder: 2, } as AllureAwesomeTestResult, c3: { name: "c3", start: baseDate, + groupOrder: 1, } as AllureAwesomeTestResult, }; const result = filterLeaves(leaves, leavesById, { @@ -326,9 +323,9 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "c3", groupOrder: 1 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "a1", groupOrder: 3 }), + expect.objectContaining({ name: "c3" }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "a1" }), ]); }); @@ -339,14 +336,17 @@ describe("utils > treeFilters", () => { a1: { name: "a1", start: baseDate + 2000, + groupOrder: 3, } as AllureAwesomeTestResult, b2: { name: "b2", start: baseDate + 1000, + groupOrder: 2, } as AllureAwesomeTestResult, c3: { name: "c3", start: baseDate, + groupOrder: 1, } as AllureAwesomeTestResult, }; const result = filterLeaves(leaves, leavesById, { @@ -355,15 +355,15 @@ describe("utils > treeFilters", () => { }); expect(result).toEqual([ - expect.objectContaining({ name: "a1", groupOrder: 3 }), - expect.objectContaining({ name: "b2", groupOrder: 2 }), - expect.objectContaining({ name: "c3", groupOrder: 1 }), + expect.objectContaining({ name: "a1" }), + expect.objectContaining({ name: "b2" }), + expect.objectContaining({ name: "c3" }), ]); }); }); - describe("fillGroup", () => { - it("replaces the group leaves IDs by filtered and sorted leaves objects for all nested groups", () => { + describe("createRecursiveTree", () => { + it("creates recursive tree with filtered and sorted leaves objects", () => { const baseDate = Date.now(); const group = { leaves: ["a1"], @@ -403,20 +403,22 @@ describe("utils > treeFilters", () => { }, }); - expect(result).toEqual({ - leaves: [expect.objectContaining({ name: "a1", groupOrder: 1 })], - groups: [ - { - leaves: [expect.objectContaining({ name: "b2", groupOrder: 1 })], - groups: [ - { - leaves: [expect.objectContaining({ name: "c3", groupOrder: 1 })], - groups: [], - }, - ], - }, - ], - }); + expect(result).toEqual( + expect.objectContaining({ + leaves: [expect.objectContaining({ name: "a1" })], + trees: [ + expect.objectContaining({ + leaves: [expect.objectContaining({ name: "b2" })], + trees: [ + expect.objectContaining({ + leaves: [expect.objectContaining({ name: "c3" })], + trees: [], + }), + ], + }), + ], + }), + ); }); }); }); diff --git a/packages/web-awesome/types.d.ts b/packages/web-awesome/types.d.ts index e3c5f1c..4f6565b 100644 --- a/packages/web-awesome/types.d.ts +++ b/packages/web-awesome/types.d.ts @@ -53,9 +53,12 @@ export type AllureAwesomeTestResult = Omit< breadcrumbs: AllureAwesomeBreadcrumbItem[]; order?: number; groupOrder?: number; + retry: boolean; }; -export type AllureAwesomeTreeLeaf = AllureAwesomeTestResult & { nodeId: string }; +export type AllureAwesomeTreeLeaf = Pick & { + nodeId: string +}; export type AllureAwesomeTreeGroup = WithChildren & DefaultTreeGroup & { nodeId: string };