@allurereport/plugin-awesome 3.0.0-beta.16 → 3.0.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,11 +47,12 @@ export default defineConfig({
47
47
 
48
48
  The plugin accepts the following options:
49
49
 
50
- | Option | Description | Type | Default |
51
- |------------------|-------------------------------------------------|--------------------------------------------------------------|-----------------|
52
- | `reportName` | Name of the report | `string` | `Allure Report` |
53
- | `singleFile` | Writes the report as a single `index.html` file | `boolean` | `false` |
54
- | `logo` | Path to the logo image | `string` | `null` |
55
- | `theme` | Default color theme of the report | `light \| dark` | OS theme |
56
- | `reportLanguage` | Default language of the report | `string` | OS language |
57
- | `ci` | CI data which will be rendered in the report | `{ type: "github" \| "jenkins", url: string, name: string }` | `undefined` |
50
+ | Option | Description | Type | Default |
51
+ |------------------|-------------------------------------------------|---------|-------------------------|
52
+ | `reportName` | Name of the report | `string` | `Allure Report` |
53
+ | `singleFile` | Writes the report as a single `index.html` file | `boolean` | `false` |
54
+ | `logo` | Path to the logo image | `string` | `null` |
55
+ | `theme` | Default color theme of the report | `light \| dark` | OS theme |
56
+ | `reportLanguage` | Default language of the report | `string` | OS language |
57
+ | `ci` | CI data which will be rendered in the report | `{ type: "github" \| "jenkins", url: string, name: string }` | `undefined` |
58
+ | `groupBy` | By default, tests are grouped using the `titlePath` provided by the test framework. | `string`| Grouping by `titlepath` |
package/dist/charts.d.ts CHANGED
@@ -1,108 +1,14 @@
1
- import type { HistoryDataPoint, SeverityLevel, Statistic, TestResult, TestStatus } from "@allurereport/core-api";
2
- import type { AllureStore, PluginContext } from "@allurereport/plugin-api";
3
- import { ChartDataType, ChartMode, ChartType } from "@allurereport/web-commons";
4
- import type { PieArcDatum } from "d3-shape";
1
+ import { type HistoryDataPoint, type Statistic, type TestResult } from "@allurereport/core-api";
2
+ import { type GeneratedChartsData, type PluginContext } from "@allurereport/plugin-api";
5
3
  import type { AwesomeOptions } from "./model.js";
6
4
  import type { AwesomeDataWriter } from "./writer.js";
7
- export type BasePieSlice = Pick<PieSlice, "status" | "count">;
8
- export declare const d3Arc: import("d3-shape").Arc<any, PieArcDatum<BasePieSlice>>;
9
- export declare const d3Pie: import("d3-shape").Pie<any, BasePieSlice>;
10
- export declare const getPercentage: (value: number, total: number) => number;
11
- export type ChartId = string;
12
- export type ExecutionIdFn = (executionOrder: number) => string;
13
- export type ExecutionNameFn = (executionOrder: number) => string;
14
- export type TrendMetadataFnOverrides = {
15
- executionIdAccessor?: ExecutionIdFn;
16
- executionNameAccessor?: ExecutionNameFn;
17
- };
18
- export type TrendChartOptions = {
19
- type: ChartType.Trend;
20
- dataType: ChartDataType;
21
- mode?: ChartMode;
22
- title?: string;
23
- limit?: number;
24
- metadata?: TrendMetadataFnOverrides;
25
- };
26
- export type TrendPointId = string;
27
- export type TrendSliceId = string;
28
- export type BaseMetadata = Record<string, unknown>;
29
- export interface BaseTrendSliceMetadata extends Record<string, unknown> {
30
- executionId: string;
31
- executionName: string;
32
- }
33
- export type TrendSliceMetadata<Metadata extends BaseMetadata> = BaseTrendSliceMetadata & Metadata;
34
- export type TrendPoint = {
35
- x: string;
36
- y: number;
37
- };
38
- export type TrendSlice<Metadata extends BaseMetadata> = {
39
- min: number;
40
- max: number;
41
- metadata: TrendSliceMetadata<Metadata>;
42
- };
43
- export type GenericTrendChartData<Metadata extends BaseMetadata, SeriesType extends string> = {
44
- type: ChartType.Trend;
45
- dataType: ChartDataType;
46
- mode: ChartMode;
47
- title?: string;
48
- points: Record<TrendPointId, TrendPoint>;
49
- slices: Record<TrendSliceId, TrendSlice<Metadata>>;
50
- series: Record<SeriesType, TrendPointId[]>;
51
- min: number;
52
- max: number;
53
- };
54
- export interface StatusMetadata extends BaseTrendSliceMetadata {
55
- }
56
- export type StatusTrendSliceMetadata = TrendSliceMetadata<StatusMetadata>;
57
- export type StatusTrendSlice = TrendSlice<StatusTrendSliceMetadata>;
58
- export type StatusTrendChartData = GenericTrendChartData<StatusTrendSliceMetadata, TestStatus>;
59
- export interface SeverityMetadata extends BaseTrendSliceMetadata {
60
- }
61
- export type SeverityTrendSliceMetadata = TrendSliceMetadata<SeverityMetadata>;
62
- export type SeverityTrendSlice = TrendSlice<SeverityTrendSliceMetadata>;
63
- export type SeverityTrendChartData = GenericTrendChartData<SeverityTrendSliceMetadata, SeverityLevel>;
64
- export type TrendChartData = StatusTrendChartData | SeverityTrendChartData;
65
- export type PieChartOptions = {
66
- type: ChartType.Pie;
67
- title?: string;
68
- };
69
- export type PieSlice = {
70
- status: TestStatus;
71
- count: number;
72
- d: string | null;
73
- };
74
- export type PieChartData = {
75
- type: ChartType.Pie;
76
- title?: string;
77
- slices: PieSlice[];
78
- percentage: number;
79
- };
80
- export type GeneratedChartData = TrendChartData | PieChartData;
81
- export type GeneratedChartsData = Record<ChartId, GeneratedChartData>;
82
- export type ChartOptions = TrendChartOptions | PieChartOptions;
83
- export type DashboardOptions = {
84
- reportName?: string;
85
- singleFile?: boolean;
86
- logo?: string;
87
- theme?: "light" | "dark";
88
- reportLanguage?: "en" | "ru";
89
- layout?: ChartOptions[];
90
- filter?: (testResult: TestResult) => boolean;
91
- };
92
- export type TrendDataType = TestStatus | SeverityLevel;
93
- export type TrendCalculationResult<T extends TrendDataType> = {
94
- points: Record<TrendPointId, TrendPoint>;
95
- series: Record<T, TrendPointId[]>;
96
- };
97
- export declare const createEmptyStats: <T extends TrendDataType>(items: readonly T[]) => Record<T, number>;
98
- export declare const createEmptySeries: <T extends TrendDataType>(items: readonly T[]) => Record<T, string[]>;
99
- export declare const normalizeStatistic: <T extends TrendDataType>(statistic: Partial<Record<T, number>>, itemType: readonly T[]) => Record<T, number>;
100
- export declare const mergeTrendDataGeneric: <M extends BaseTrendSliceMetadata, T extends TrendDataType>(trendData: GenericTrendChartData<M, T>, trendDataPart: GenericTrendChartData<M, T>, itemType: readonly T[]) => GenericTrendChartData<M, T>;
101
- export declare const getTrendDataGeneric: <M extends BaseTrendSliceMetadata, T extends TrendDataType>(stats: Record<T, number>, reportName: string, executionOrder: number, itemType: readonly T[], chartOptions: TrendChartOptions) => GenericTrendChartData<M, T>;
102
- export declare const generateCharts: (options: AwesomeOptions, store: AllureStore, context: PluginContext) => Promise<GeneratedChartsData | undefined>;
103
- export declare const generateTrendChart: (options: TrendChartOptions, stores: {
104
- historyDataPoints: HistoryDataPoint[];
5
+ export declare const generateCharts: (options: AwesomeOptions, context: PluginContext, stores: {
6
+ trs: TestResult[];
105
7
  statistic: Statistic;
106
- testResults: TestResult[];
107
- }, context: PluginContext) => TrendChartData | undefined;
108
- export declare const generateAllCharts: (writer: AwesomeDataWriter, store: AllureStore, options: AwesomeOptions, context: PluginContext) => Promise<void>;
8
+ history: HistoryDataPoint[];
9
+ }) => Promise<GeneratedChartsData | undefined>;
10
+ export declare const generateAllCharts: (writer: AwesomeDataWriter, options: AwesomeOptions, context: PluginContext, stores: {
11
+ trs: TestResult[];
12
+ statistic: Statistic;
13
+ history: HistoryDataPoint[];
14
+ }) => Promise<void>;
package/dist/charts.js CHANGED
@@ -1,137 +1,22 @@
1
- import { ChartDataType, ChartMode, ChartType, DEFAULT_CHART_HISTORY_LIMIT, getPieChartDataDashboard, getSeverityTrendData, getStatusTrendData, } from "@allurereport/web-commons";
1
+ import { ChartType } from "@allurereport/core-api";
2
+ import { generateComingSoonChart, generatePieChart, generateTrendChart, } from "@allurereport/plugin-api";
2
3
  import { randomUUID } from "crypto";
3
- import { arc, pie } from "d3-shape";
4
- export const d3Arc = arc().innerRadius(40).outerRadius(50).cornerRadius(2).padAngle(0.03);
5
- export const d3Pie = pie()
6
- .value((d) => d.count)
7
- .padAngle(0.03)
8
- .sortValues((a, b) => a - b);
9
- export const getPercentage = (value, total) => Math.floor((value / total) * 10000) / 100;
10
- export const createEmptyStats = (items) => items.reduce((acc, item) => ({ ...acc, [item]: 0 }), {});
11
- export const createEmptySeries = (items) => items.reduce((acc, item) => ({ ...acc, [item]: [] }), {});
12
- export const normalizeStatistic = (statistic, itemType) => {
13
- return itemType.reduce((acc, item) => {
14
- acc[item] = statistic[item] ?? 0;
15
- return acc;
16
- }, {});
17
- };
18
- const calculateRawValues = (stats, executionId, itemType) => {
19
- const points = {};
20
- const series = createEmptySeries(itemType);
21
- itemType.forEach((item) => {
22
- const pointId = `${executionId}-${item}`;
23
- const value = stats[item] ?? 0;
24
- points[pointId] = {
25
- x: executionId,
26
- y: value,
27
- };
28
- series[item].push(pointId);
29
- });
30
- return { points, series };
31
- };
32
- const calculatePercentValues = (stats, executionId, itemType) => {
33
- const points = {};
34
- const series = createEmptySeries(itemType);
35
- const values = Object.values(stats);
36
- const total = values.reduce((sum, value) => sum + value, 0);
37
- if (total === 0) {
38
- return { points, series };
39
- }
40
- itemType.forEach((item) => {
41
- const pointId = `${executionId}-${item}`;
42
- const value = stats[item] ?? 0;
43
- points[pointId] = {
44
- x: executionId,
45
- y: value / total,
46
- };
47
- series[item].push(pointId);
48
- });
49
- return { points, series };
50
- };
51
- export const mergeTrendDataGeneric = (trendData, trendDataPart, itemType) => {
52
- return {
53
- ...trendData,
54
- points: {
55
- ...trendData.points,
56
- ...trendDataPart.points,
57
- },
58
- slices: {
59
- ...trendData.slices,
60
- ...trendDataPart.slices,
61
- },
62
- series: Object.entries(trendDataPart.series).reduce((series, [group, pointIds]) => {
63
- if (Array.isArray(pointIds)) {
64
- return {
65
- ...series,
66
- [group]: [...(trendData.series?.[group] || []), ...pointIds],
67
- };
68
- }
69
- return series;
70
- }, trendData.series || createEmptySeries(itemType)),
71
- min: Math.min(trendData.min ?? Infinity, trendDataPart.min),
72
- max: Math.max(trendData.max ?? -Infinity, trendDataPart.max),
73
- };
74
- };
75
- export const getTrendDataGeneric = (stats, reportName, executionOrder, itemType, chartOptions) => {
76
- const { type, dataType, title, mode = ChartMode.Raw, metadata = {} } = chartOptions;
77
- const { executionIdAccessor, executionNameAccessor } = metadata;
78
- const executionId = executionIdAccessor ? executionIdAccessor(executionOrder) : `execution-${executionOrder}`;
79
- const { points, series } = mode === ChartMode.Percent
80
- ? calculatePercentValues(stats, executionId, itemType)
81
- : calculateRawValues(stats, executionId, itemType);
82
- const slices = {};
83
- const pointsAsArray = Object.values(points);
84
- const pointsCount = pointsAsArray.length;
85
- const values = pointsAsArray.map((point) => point.y);
86
- const min = pointsCount ? Math.min(...values) : 0;
87
- const max = pointsCount ? Math.max(...values) : 0;
88
- if (pointsCount > 0) {
89
- const executionName = executionNameAccessor ? executionNameAccessor(executionOrder) : reportName;
90
- slices[executionId] = {
91
- min,
92
- max,
93
- metadata: {
94
- executionId,
95
- executionName,
96
- },
97
- };
98
- }
99
- return {
100
- type,
101
- dataType,
102
- mode,
103
- title,
104
- points,
105
- slices,
106
- series,
107
- min,
108
- max,
109
- };
110
- };
111
- const generatePieChart = (options, stores) => {
112
- const { statistic } = stores;
113
- return getPieChartDataDashboard(statistic, options);
114
- };
115
- export const generateCharts = async (options, store, context) => {
4
+ export const generateCharts = async (options, context, stores) => {
116
5
  const { charts } = options;
117
6
  if (!charts) {
118
7
  return undefined;
119
8
  }
120
- const historyDataPoints = await store.allHistoryDataPoints();
121
- const statistic = await store.testsStatistic();
122
- const testResults = await store.allTestResults();
123
9
  return charts.reduce((acc, chartOptions) => {
124
10
  const chartId = randomUUID();
125
11
  let chart;
126
12
  if (chartOptions.type === ChartType.Trend) {
127
- chart = generateTrendChart(chartOptions, {
128
- historyDataPoints,
129
- statistic,
130
- testResults,
131
- }, context);
13
+ chart = generateTrendChart(chartOptions, stores, context);
132
14
  }
133
15
  else if (chartOptions.type === ChartType.Pie) {
134
- chart = generatePieChart(chartOptions, { statistic });
16
+ chart = generatePieChart(chartOptions, stores);
17
+ }
18
+ else if ([ChartType.HeatMap, ChartType.Bar, ChartType.Funnel, ChartType.TreeMap].includes(chartOptions.type)) {
19
+ chart = generateComingSoonChart(chartOptions);
135
20
  }
136
21
  if (chart) {
137
22
  acc[chartId] = chart;
@@ -139,19 +24,8 @@ export const generateCharts = async (options, store, context) => {
139
24
  return acc;
140
25
  }, {});
141
26
  };
142
- export const generateTrendChart = (options, stores, context) => {
143
- const newOptions = { limit: DEFAULT_CHART_HISTORY_LIMIT, ...options };
144
- const { dataType } = newOptions;
145
- const { statistic, historyDataPoints, testResults } = stores;
146
- if (dataType === ChartDataType.Status) {
147
- return getStatusTrendData(statistic, context.reportName, historyDataPoints, newOptions);
148
- }
149
- else if (dataType === ChartDataType.Severity) {
150
- return getSeverityTrendData(testResults, context.reportName, historyDataPoints, newOptions);
151
- }
152
- };
153
- export const generateAllCharts = async (writer, store, options, context) => {
154
- const charts = await generateCharts(options, store, context);
27
+ export const generateAllCharts = async (writer, options, context, stores) => {
28
+ const charts = await generateCharts(options, context, stores);
155
29
  if (charts && Object.keys(charts).length > 0) {
156
30
  await writer.writeWidget("charts.json", charts);
157
31
  }
@@ -36,6 +36,7 @@ export const convertTestResult = (tr) => {
36
36
  breadcrumbs: [],
37
37
  retry: false,
38
38
  transition: tr.transition,
39
+ titlePath: tr.titlePath || [],
39
40
  };
40
41
  };
41
42
  export const convertTestStepResult = (tsr) => {
@@ -1,21 +1,30 @@
1
- import { type AttachmentLink, type EnvironmentItem } from "@allurereport/core-api";
2
- import { type AllureStore, type ReportFiles, type ResultFile, type TestResultFilter } from "@allurereport/plugin-api";
1
+ import { type AttachmentLink, type EnvironmentItem, type Statistic, type TestEnvGroup, type TestError, type TestResult } from "@allurereport/core-api";
2
+ import { type AllureStore, type ExitCode, type ReportFiles, type ResultFile, type TestResultFilter } from "@allurereport/plugin-api";
3
3
  import type { AwesomeTestResult } from "@allurereport/web-awesome";
4
4
  import type { AwesomeOptions, TemplateManifest } from "./model.js";
5
5
  import type { AwesomeDataWriter, ReportFile } from "./writer.js";
6
6
  export declare const readTemplateManifest: (singleFileMode?: boolean) => Promise<TemplateManifest>;
7
- export declare const generateTestResults: (writer: AwesomeDataWriter, store: AllureStore, filter?: TestResultFilter) => Promise<AwesomeTestResult[]>;
7
+ export declare const generateTestResults: (writer: AwesomeDataWriter, store: AllureStore, trs: TestResult[], filter?: TestResultFilter) => Promise<AwesomeTestResult[]>;
8
8
  export declare const generateTestCases: (writer: AwesomeDataWriter, trs: AwesomeTestResult[]) => Promise<void>;
9
- export declare const generateTestEnvGroups: (writer: AwesomeDataWriter, store: AllureStore) => Promise<void>;
9
+ export declare const generateTestEnvGroups: (writer: AwesomeDataWriter, groups: TestEnvGroup[]) => Promise<void>;
10
10
  export declare const generateNav: (writer: AwesomeDataWriter, trs: AwesomeTestResult[], filename?: string) => Promise<void>;
11
11
  export declare const generateTree: (writer: AwesomeDataWriter, treeFilename: string, labels: string[], tests: AwesomeTestResult[]) => Promise<void>;
12
12
  export declare const generateEnvironmentJson: (writer: AwesomeDataWriter, env: EnvironmentItem[]) => Promise<void>;
13
13
  export declare const generateEnvirontmentsList: (writer: AwesomeDataWriter, store: AllureStore) => Promise<void>;
14
14
  export declare const generateVariables: (writer: AwesomeDataWriter, store: AllureStore) => Promise<void>;
15
- export declare const generateStatistic: (writer: AwesomeDataWriter, store: AllureStore, filter?: TestResultFilter) => Promise<void>;
16
- export declare const generatePieChart: (writer: AwesomeDataWriter, store: AllureStore, filter?: TestResultFilter) => Promise<void>;
15
+ export declare const generateStatistic: (writer: AwesomeDataWriter, data: {
16
+ stats: Statistic;
17
+ statsByEnv: Map<string, Statistic>;
18
+ envs: string[];
19
+ }) => Promise<void>;
17
20
  export declare const generateAttachmentsFiles: (writer: AwesomeDataWriter, attachmentLinks: AttachmentLink[], contentFunction: (id: string) => Promise<ResultFile | undefined>) => Promise<Map<string, string> | undefined>;
18
21
  export declare const generateHistoryDataPoints: (writer: AwesomeDataWriter, store: AllureStore) => Promise<Map<string, string>>;
22
+ export declare const generateGlobals: (writer: AwesomeDataWriter, payload: {
23
+ globalExitCode?: ExitCode;
24
+ globalAttachments?: AttachmentLink[];
25
+ globalErrors?: TestError[];
26
+ contentFunction: (id: string) => Promise<ResultFile | undefined>;
27
+ }) => Promise<void>;
19
28
  export declare const generateStaticFiles: (payload: AwesomeOptions & {
20
29
  id: string;
21
30
  allureVersion: string;
@@ -1,13 +1,11 @@
1
- import { compareBy, incrementStatistic, nullsLast, ordinal, } from "@allurereport/core-api";
2
- import { filterTree, } from "@allurereport/plugin-api";
3
- import { createTreeByLabels, sortTree, transformTree } from "@allurereport/plugin-api";
4
- import { createBaseUrlScript, createFontLinkTag, createReportDataScript, createScriptTag, createStylesLinkTag, getPieChartData, } from "@allurereport/web-commons";
1
+ import { compareBy, getPieChartValues, incrementStatistic, nullsLast, ordinal, } from "@allurereport/core-api";
2
+ import { createTreeByLabels, createTreeByTitlePath, filterTree, preciseTreeLabels, sortTree, transformTree, } from "@allurereport/plugin-api";
3
+ import { createBaseUrlScript, createFontLinkTag, createReportDataScript, createScriptTag, createStylesLinkTag, } from "@allurereport/web-commons";
5
4
  import Handlebars from "handlebars";
6
5
  import { readFile } from "node:fs/promises";
7
6
  import { createRequire } from "node:module";
8
7
  import { basename, join } from "node:path";
9
8
  import { convertFixtureResult, convertTestResult } from "./converters.js";
10
- import { filterEnv } from "./environments.js";
11
9
  const require = createRequire(import.meta.url);
12
10
  const template = `<!DOCTYPE html>
13
11
  <html dir="ltr" lang="en">
@@ -73,8 +71,8 @@ const createBreadcrumbs = (convertedTr) => {
73
71
  return acc;
74
72
  }, []);
75
73
  };
76
- export const generateTestResults = async (writer, store, filter) => {
77
- const allTr = (await store.allTestResults({ includeHidden: true })).filter((tr) => (filter ? filter(tr) : true));
74
+ export const generateTestResults = async (writer, store, trs, filter) => {
75
+ const allTr = trs.filter((tr) => (filter ? filter(tr) : true));
78
76
  let convertedTrs = [];
79
77
  for (const tr of allTr) {
80
78
  const trFixtures = await store.fixturesByTrId(tr.id);
@@ -107,8 +105,7 @@ export const generateTestCases = async (writer, trs) => {
107
105
  await writer.writeTestCase(tr);
108
106
  }
109
107
  };
110
- export const generateTestEnvGroups = async (writer, store) => {
111
- const groups = await store.allTestEnvGroups();
108
+ export const generateTestEnvGroups = async (writer, groups) => {
112
109
  for (const group of groups) {
113
110
  const src = join("test-env-groups", `${group.id}.json`);
114
111
  await writer.writeData(src, group);
@@ -119,24 +116,55 @@ export const generateNav = async (writer, trs, filename = "nav.json") => {
119
116
  };
120
117
  export const generateTree = async (writer, treeFilename, labels, tests) => {
121
118
  const visibleTests = tests.filter((test) => !test.hidden);
122
- const tree = createTreeByLabels(visibleTests, labels, ({ id, name, status, duration, flaky, transition, start, retry, retriesCount }) => ({
123
- nodeId: id,
124
- retry,
125
- retriesCount,
126
- name,
127
- status,
128
- start,
129
- duration,
130
- flaky,
131
- transition,
132
- }), undefined, (group, leaf) => {
133
- incrementStatistic(group.statistic, leaf.status);
134
- });
119
+ const tree = labels.length
120
+ ? buildTreeByLabels(visibleTests, labels)
121
+ : buildTreeByTitlePath(visibleTests);
135
122
  filterTree(tree, (leaf) => !leaf.hidden);
136
123
  sortTree(tree, nullsLast(compareBy("start", ordinal())));
137
124
  transformTree(tree, (leaf, idx) => ({ ...leaf, groupOrder: idx + 1 }));
138
125
  await writer.writeWidget(treeFilename, tree);
139
126
  };
127
+ const buildTreeByLabels = (tests, labels) => {
128
+ return createTreeByLabels(tests, labels, leafFactory, undefined, (group, leaf) => incrementStatistic(group.statistic, leaf.status));
129
+ };
130
+ const buildTreeByTitlePath = (tests) => {
131
+ const testsWithTitlePath = [];
132
+ const testsWithoutTitlePath = [];
133
+ for (const test of tests) {
134
+ if (Array.isArray(test.titlePath) && test.titlePath.length > 0) {
135
+ testsWithTitlePath.push(test);
136
+ }
137
+ else {
138
+ testsWithoutTitlePath.push(test);
139
+ }
140
+ }
141
+ const treeByTitlePath = createTreeByTitlePath(testsWithTitlePath, leafFactory, undefined, (group, leaf) => incrementStatistic(group.statistic, leaf.status));
142
+ const defaultLabels = preciseTreeLabels(["parentSuite", "suite", "subSuite"], testsWithoutTitlePath, ({ labels }) => labels.map(({ name }) => name));
143
+ const treeByDefaultLabels = createTreeByLabels(testsWithoutTitlePath, defaultLabels, leafFactory, undefined, (group, leaf) => incrementStatistic(group.statistic, leaf.status));
144
+ const mergedLeavesById = { ...treeByTitlePath.leavesById, ...treeByDefaultLabels.leavesById };
145
+ const mergedGroupsById = { ...treeByTitlePath.groupsById, ...treeByDefaultLabels.groupsById };
146
+ const mergedRootLeaves = Array.from(new Set([...(treeByTitlePath.root.leaves ?? []), ...(treeByDefaultLabels.root.leaves ?? [])]));
147
+ const mergedRootGroups = Array.from(new Set([...(treeByTitlePath.root.groups ?? []), ...(treeByDefaultLabels.root.groups ?? [])]));
148
+ return {
149
+ root: {
150
+ leaves: mergedRootLeaves,
151
+ groups: mergedRootGroups,
152
+ },
153
+ leavesById: mergedLeavesById,
154
+ groupsById: mergedGroupsById,
155
+ };
156
+ };
157
+ const leafFactory = ({ id, name, status, duration, flaky, start, transition, retry, retriesCount, }) => ({
158
+ nodeId: id,
159
+ name,
160
+ status,
161
+ duration,
162
+ flaky,
163
+ start,
164
+ retry,
165
+ retriesCount,
166
+ transition,
167
+ });
140
168
  export const generateEnvironmentJson = async (writer, env) => {
141
169
  await writer.writeWidget("allure_environment.json", env);
142
170
  };
@@ -153,22 +181,17 @@ export const generateVariables = async (writer, store) => {
153
181
  await writer.writeWidget(join(env, "variables.json"), envVariables);
154
182
  }
155
183
  };
156
- export const generateStatistic = async (writer, store, filter) => {
157
- const statistic = await store.testsStatistic(filter);
158
- const environments = await store.allEnvironments();
159
- await writer.writeWidget("statistic.json", statistic);
160
- for (const env of environments) {
161
- const envStatistic = await store.testsStatistic(filterEnv(env, filter));
162
- await writer.writeWidget(join(env, "statistic.json"), envStatistic);
163
- }
164
- };
165
- export const generatePieChart = async (writer, store, filter) => {
166
- const reportStatistic = await store.testsStatistic(filter);
167
- const environments = await store.allEnvironments();
168
- await writer.writeWidget("pie_chart.json", getPieChartData(reportStatistic));
169
- for (const env of environments) {
170
- const envStatistic = await store.testsStatistic(filterEnv(env, filter));
171
- await writer.writeWidget(join(env, "pie_chart.json"), getPieChartData(envStatistic));
184
+ export const generateStatistic = async (writer, data) => {
185
+ const { stats, statsByEnv, envs } = data;
186
+ await writer.writeWidget("statistic.json", stats);
187
+ await writer.writeWidget("pie_chart.json", getPieChartValues(stats));
188
+ for (const env of envs) {
189
+ const envStats = statsByEnv.get(env);
190
+ if (!envStats) {
191
+ continue;
192
+ }
193
+ await writer.writeWidget(join(env, "statistic.json"), envStats);
194
+ await writer.writeWidget(join(env, "pie_chart.json"), envStats);
172
195
  }
173
196
  };
174
197
  export const generateAttachmentsFiles = async (writer, attachmentLinks, contentFunction) => {
@@ -196,8 +219,26 @@ export const generateHistoryDataPoints = async (writer, store) => {
196
219
  }
197
220
  return result;
198
221
  };
222
+ export const generateGlobals = async (writer, payload) => {
223
+ const { globalExitCode = { original: 0 }, globalAttachments = [], globalErrors = [], contentFunction } = payload;
224
+ const globals = {
225
+ exitCode: globalExitCode,
226
+ errors: globalErrors,
227
+ attachments: [],
228
+ };
229
+ for (const attachment of globalAttachments) {
230
+ const src = `${attachment.id}${attachment.ext}`;
231
+ const content = await contentFunction(attachment.id);
232
+ if (!content) {
233
+ continue;
234
+ }
235
+ await writer.writeAttachment(src, content);
236
+ globals.attachments.push(attachment);
237
+ }
238
+ await writer.writeWidget("globals.json", globals);
239
+ };
199
240
  export const generateStaticFiles = async (payload) => {
200
- const { id, reportName = "Allure Report", reportLanguage = "en", singleFile, logo = "", theme = "light", groupBy, reportFiles, reportDataFiles, reportUuid, allureVersion, layout = "base", charts = [], defaultSection = "", } = payload;
241
+ const { id, reportName = "Allure Report", reportLanguage = "en", singleFile, logo = "", theme = "light", groupBy, reportFiles, reportDataFiles, reportUuid, allureVersion, layout = "base", charts = [], defaultSection = "", ci, } = payload;
201
242
  const compile = Handlebars.compile(template);
202
243
  const manifest = await readTemplateManifest(payload.singleFile);
203
244
  const headTags = [];
@@ -241,23 +282,34 @@ export const generateStaticFiles = async (payload) => {
241
282
  reportLanguage,
242
283
  createdAt: now,
243
284
  reportUuid,
244
- groupBy: groupBy?.length ? groupBy : ["parentSuite", "suite", "subSuite"],
285
+ groupBy: groupBy?.length ? groupBy : [],
245
286
  cacheKey: now.toString(),
287
+ ci,
246
288
  layout,
247
289
  allureVersion,
248
290
  sections,
249
291
  defaultSection,
250
292
  };
251
- const html = compile({
252
- headTags: headTags.join("\n"),
253
- bodyTags: bodyTags.join("\n"),
254
- reportFilesScript: createReportDataScript(reportDataFiles),
255
- reportOptions: JSON.stringify(reportOptions),
256
- analyticsEnable: true,
257
- allureVersion,
258
- reportUuid,
259
- reportName,
260
- singleFile: payload.singleFile,
261
- });
262
- await reportFiles.addFile("index.html", Buffer.from(html, "utf8"));
293
+ try {
294
+ const html = compile({
295
+ headTags: headTags.join("\n"),
296
+ bodyTags: bodyTags.join("\n"),
297
+ reportFilesScript: createReportDataScript(reportDataFiles),
298
+ reportOptions: JSON.stringify(reportOptions),
299
+ analyticsEnable: true,
300
+ allureVersion,
301
+ reportUuid,
302
+ reportName,
303
+ singleFile: payload.singleFile,
304
+ });
305
+ await reportFiles.addFile("index.html", Buffer.from(html, "utf8"));
306
+ }
307
+ catch (err) {
308
+ if (err instanceof RangeError) {
309
+ console.error("The report is too large to be generated in the single file mode!");
310
+ process.exit(1);
311
+ return;
312
+ }
313
+ throw err;
314
+ }
263
315
  };
package/dist/model.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { EnvironmentsConfig, TestResult } from "@allurereport/core-api";
2
- import type { ChartOptions } from "./charts.js";
1
+ import type { CiDescriptor, EnvironmentsConfig, TestResult } from "@allurereport/core-api";
2
+ import type { ChartOptions } from "@allurereport/plugin-api";
3
3
  export type AwesomeOptions = {
4
4
  reportName?: string;
5
5
  singleFile?: boolean;
@@ -9,11 +9,7 @@ export type AwesomeOptions = {
9
9
  groupBy?: string[];
10
10
  layout?: "base" | "split";
11
11
  environments?: Record<string, EnvironmentsConfig>;
12
- ci?: {
13
- type: "github" | "jenkins";
14
- url: string;
15
- name: string;
16
- };
12
+ ci?: CiDescriptor;
17
13
  filter?: (testResult: TestResult) => boolean;
18
14
  charts?: ChartOptions[];
19
15
  sections?: string[];
package/dist/plugin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AllureStore, Plugin, PluginContext, PluginSummary } from "@allurereport/plugin-api";
1
+ import { type AllureStore, type Plugin, type PluginContext, type PluginSummary } from "@allurereport/plugin-api";
2
2
  import type { AwesomePluginOptions } from "./model.js";
3
3
  export declare class AwesomePlugin implements Plugin {
4
4
  #private;
package/dist/plugin.js CHANGED
@@ -11,34 +11,56 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (
11
11
  };
12
12
  var _AwesomePlugin_writer, _AwesomePlugin_generate;
13
13
  import { getWorstStatus } from "@allurereport/core-api";
14
+ import { convertToSummaryTestResult, } from "@allurereport/plugin-api";
14
15
  import { preciseTreeLabels } from "@allurereport/plugin-api";
15
16
  import { join } from "node:path";
16
17
  import { generateAllCharts } from "./charts.js";
17
- import { generateAttachmentsFiles, generateEnvironmentJson, generateEnvirontmentsList, generateHistoryDataPoints, generateNav, generatePieChart, generateStaticFiles, generateStatistic, generateTestCases, generateTestEnvGroups, generateTestResults, generateTree, generateVariables, } from "./generators.js";
18
+ import { filterEnv } from "./environments.js";
19
+ import { generateAttachmentsFiles, generateEnvironmentJson, generateEnvirontmentsList, generateGlobals, generateHistoryDataPoints, generateNav, generateStaticFiles, generateStatistic, generateTestCases, generateTestEnvGroups, generateTestResults, generateTree, generateVariables, } from "./generators.js";
18
20
  import { InMemoryReportDataWriter, ReportFileDataWriter } from "./writer.js";
19
21
  export class AwesomePlugin {
20
22
  constructor(options = {}) {
21
23
  this.options = options;
22
24
  _AwesomePlugin_writer.set(this, void 0);
23
25
  _AwesomePlugin_generate.set(this, async (context, store) => {
24
- const { singleFile, groupBy = [] } = this.options ?? {};
26
+ const { singleFile, groupBy = [], filter } = this.options ?? {};
25
27
  const environmentItems = await store.metadataByKey("allure_environment");
26
28
  const reportEnvironments = await store.allEnvironments();
27
29
  const attachments = await store.allAttachments();
28
- await generateStatistic(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, this.options.filter);
29
- await generatePieChart(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, this.options.filter);
30
- await generateAllCharts(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, this.options, context);
31
- const convertedTrs = await generateTestResults(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, this.options.filter);
32
- const treeLabels = preciseTreeLabels(!groupBy.length ? ["parentSuite", "suite", "subSuite"] : groupBy, convertedTrs, ({ labels }) => labels.map(({ name }) => name));
30
+ const allTrs = await store.allTestResults({ includeHidden: true });
31
+ const statistics = await store.testsStatistic(filter);
32
+ const environments = await store.allEnvironments();
33
+ const envStatistics = new Map();
34
+ const allTestEnvGroups = await store.allTestEnvGroups();
35
+ const allHistoryDataPoints = await store.allHistoryDataPoints();
36
+ const globalAttachments = await store.allGlobalAttachments();
37
+ const globalExitCode = await store.globalExitCode();
38
+ const globalErrors = await store.allGlobalErrors();
39
+ for (const env of environments) {
40
+ envStatistics.set(env, await store.testsStatistic(filterEnv(env, filter)));
41
+ }
42
+ await generateStatistic(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), {
43
+ stats: statistics,
44
+ statsByEnv: envStatistics,
45
+ envs: environments,
46
+ });
47
+ await generateAllCharts(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), this.options, context, {
48
+ trs: allTrs,
49
+ statistic: statistics,
50
+ history: allHistoryDataPoints,
51
+ });
52
+ const convertedTrs = await generateTestResults(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store, allTrs, this.options.filter);
53
+ const hasGroupBy = groupBy.length > 0;
54
+ const treeLabels = hasGroupBy
55
+ ? preciseTreeLabels(groupBy, convertedTrs, ({ labels }) => labels.map(({ name }) => name))
56
+ : [];
33
57
  await generateHistoryDataPoints(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store);
34
58
  await generateTestCases(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), convertedTrs);
35
59
  await generateTree(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), "tree.json", treeLabels, convertedTrs);
36
60
  await generateNav(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), convertedTrs, "nav.json");
37
- await generateTestEnvGroups(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), store);
61
+ await generateTestEnvGroups(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), allTestEnvGroups);
38
62
  for (const reportEnvironment of reportEnvironments) {
39
- const envTrs = await store.testResultsByEnvironment(reportEnvironment);
40
- const envTrsIds = envTrs.map(({ id }) => id);
41
- const envConvertedTrs = convertedTrs.filter(({ id }) => envTrsIds.includes(id));
63
+ const envConvertedTrs = convertedTrs.filter(({ environment }) => environment === reportEnvironment);
42
64
  await generateTree(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), join(reportEnvironment, "tree.json"), treeLabels, envConvertedTrs);
43
65
  await generateNav(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), envConvertedTrs, join(reportEnvironment, "nav.json"));
44
66
  }
@@ -51,6 +73,12 @@ export class AwesomePlugin {
51
73
  await generateAttachmentsFiles(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), attachments, (id) => store.attachmentContentById(id));
52
74
  }
53
75
  const reportDataFiles = singleFile ? __classPrivateFieldGet(this, _AwesomePlugin_writer, "f").reportFiles() : [];
76
+ await generateGlobals(__classPrivateFieldGet(this, _AwesomePlugin_writer, "f"), {
77
+ globalAttachments,
78
+ globalErrors,
79
+ globalExitCode,
80
+ contentFunction: (id) => store.attachmentContentById(id),
81
+ });
54
82
  await generateStaticFiles({
55
83
  ...this.options,
56
84
  id: context.id,
@@ -85,6 +113,9 @@ export class AwesomePlugin {
85
113
  }
86
114
  async info(context, store) {
87
115
  const allTrs = (await store.allTestResults()).filter((tr) => this.options.filter ? this.options.filter(tr) : true);
116
+ const newTrs = await store.allNewTestResults();
117
+ const retryTrs = allTrs.filter((tr) => !!tr?.retries?.length);
118
+ const flakyTrs = allTrs.filter((tr) => !!tr?.flaky);
88
119
  const duration = allTrs.reduce((acc, { duration: trDuration = 0 }) => acc + trDuration, 0);
89
120
  const worstStatus = getWorstStatus(allTrs.map(({ status }) => status));
90
121
  const createdAt = allTrs.reduce((acc, { stop }) => Math.max(acc, stop || 0), 0);
@@ -95,6 +126,9 @@ export class AwesomePlugin {
95
126
  duration,
96
127
  createdAt,
97
128
  plugin: "Awesome",
129
+ newTests: newTrs.map(convertToSummaryTestResult),
130
+ flakyTests: flakyTrs.map(convertToSummaryTestResult),
131
+ retryTests: retryTrs.map(convertToSummaryTestResult),
98
132
  };
99
133
  }
100
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allurereport/plugin-awesome",
3
- "version": "3.0.0-beta.16",
3
+ "version": "3.0.0-beta.18",
4
4
  "description": "Allure Awesome Plugin – brand new HTML report with modern design and new features",
5
5
  "keywords": [
6
6
  "allure",
@@ -30,10 +30,10 @@
30
30
  "test": "rimraf ./out && vitest run"
31
31
  },
32
32
  "dependencies": {
33
- "@allurereport/core-api": "3.0.0-beta.16",
34
- "@allurereport/plugin-api": "3.0.0-beta.16",
35
- "@allurereport/web-awesome": "3.0.0-beta.16",
36
- "@allurereport/web-commons": "3.0.0-beta.16",
33
+ "@allurereport/core-api": "3.0.0-beta.18",
34
+ "@allurereport/plugin-api": "3.0.0-beta.18",
35
+ "@allurereport/web-awesome": "3.0.0-beta.18",
36
+ "@allurereport/web-commons": "3.0.0-beta.18",
37
37
  "d3-shape": "^3.2.0",
38
38
  "handlebars": "^4.7.8"
39
39
  },
@@ -45,7 +45,7 @@
45
45
  "@typescript-eslint/eslint-plugin": "^8.0.0",
46
46
  "@typescript-eslint/parser": "^8.0.0",
47
47
  "@vitest/runner": "^2.1.9",
48
- "allure-vitest": "^3.0.9",
48
+ "allure-vitest": "^3.3.3",
49
49
  "eslint": "^8.57.0",
50
50
  "eslint-config-prettier": "^9.1.0",
51
51
  "eslint-plugin-import": "^2.29.1",