Skip to content

Commit

Permalink
Add retry filter (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
epszaw authored Dec 23, 2024
1 parent 3e0745c commit 1d41117
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 97 deletions.
80 changes: 80 additions & 0 deletions packages/e2e/test/allure-awesome/tree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
54 changes: 34 additions & 20 deletions packages/plugin-api/src/utils/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -108,26 +108,40 @@ export const filterTreeLabels = (data: TestResult[], labelNames: string[]) => {
.reverse();
};

export const createTreeByLabels = (data: TestResult[], labelNames: string[]) => {
return createTree<TestResult, DefaultTreeLeaf, DefaultTreeGroup>(
export const createTreeByLabels = <T = TestResult, L = DefaultTreeLeaf, G = DefaultTreeGroup>(
data: T[],
labelNames: string[],
leafFactory?: (item: T) => TreeLeaf<L>,
groupFactory?: (parentGroup: string | undefined, groupClassifier: string) => TreeGroup<G>,
addLeafToGroup: (group: TreeGroup<G>, leaf: TreeLeaf<L>) => void = () => {},
) => {
const leafFactoryFn =
leafFactory ??
((tr: T) => {
const { id, name, status, duration } = tr as TestResult;

return {
nodeId: id,
name,
status,
duration,
} as unknown as TreeLeaf<L>;
});
const groupFactoryFn =
groupFactory ??
((parentId, groupClassifier) =>
({
nodeId: md5((parentId ? `${parentId}.` : "") + groupClassifier),
name: groupClassifier,
statistic: emptyStatistic(),
}) as unknown as TreeGroup<G>);

return createTree<T, L, G>(
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,
);
};

Expand Down
15 changes: 11 additions & 4 deletions packages/plugin-api/test/tree.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-awesome/src/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const convertTestResult = (tr: TestResult): AllureAwesomeTestResult => {
history: [],
retries: [],
breadcrumbs: [],
retry: false,
};
};

Expand Down
29 changes: 26 additions & 3 deletions packages/plugin-awesome/src/generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import {
type AttachmentLink,
type EnvironmentItem,
type Statistic,
type TestResult,
compareBy,
incrementStatistic,
nullsLast,
ordinal,
} from "@allurereport/core-api";
Expand All @@ -13,6 +13,8 @@ import type {
AllureAwesomeFixtureResult,
AllureAwesomeReportOptions,
AllureAwesomeTestResult,
AllureAwesomeTreeGroup,
AllureAwesomeTreeLeaf,
} from "@allurereport/web-awesome";
import {
createBaseUrlScript,
Expand Down Expand Up @@ -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
Expand All @@ -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<AllureAwesomeTestResult, AllureAwesomeTreeLeaf, AllureAwesomeTreeGroup>(
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);
Expand Down
8 changes: 4 additions & 4 deletions packages/plugin-awesome/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ export class AllureAwesomePlugin implements Plugin {
const { singleFile, groupBy } = this.options ?? {};
const environmentItems = await store.metadataByKey<EnvironmentItem[]>("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) {
Expand Down
4 changes: 1 addition & 3 deletions packages/sandbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const Filters = () => {
size="m"
style="outline"
isActive={isOpened}
data-testid="filters-button"
onClick={onClick}
/>
</div>
Expand All @@ -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)}
/>
</div>
Expand All @@ -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)}
/>
</div>
Expand All @@ -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)}
/>
</div>
Expand Down
3 changes: 2 additions & 1 deletion packages/web-awesome/src/components/commons/Toggle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +17,7 @@ export const Toggle = (props: Props) => {

return (
<input
{...rest}
tabIndex={focusable ? 0 : -1}
className={styles.toggle}
role="switch"
Expand Down
7 changes: 5 additions & 2 deletions packages/web-awesome/src/utils/treeFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const filterLeaves = (
const statusMatched =
!filterOptions?.status || filterOptions?.status === "total" || leaf.status === filterOptions.status;
const flakyMatched = !filterOptions?.filter?.flaky || leaf.flaky;
const retryMatched = !filterOptions?.filter?.retry || leaf?.retries?.length > 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;

Expand Down Expand Up @@ -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];
Expand Down
Loading

0 comments on commit 1d41117

Please sign in to comment.