@allurereport/core 3.0.0-beta.8 → 3.0.0
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/dist/api.d.ts +23 -13
- package/dist/config.d.ts +14 -8
- package/dist/config.js +90 -20
- package/dist/history.d.ts +8 -3
- package/dist/history.js +35 -20
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/plugin.d.ts +6 -5
- package/dist/plugin.js +9 -3
- package/dist/qualityGate/index.d.ts +2 -0
- package/dist/qualityGate/index.js +2 -0
- package/dist/qualityGate/qualityGate.d.ts +21 -0
- package/dist/qualityGate/qualityGate.js +103 -0
- package/dist/qualityGate/rules.d.ts +6 -0
- package/dist/qualityGate/rules.js +55 -0
- package/dist/report.d.ts +19 -4
- package/dist/report.js +432 -55
- package/dist/store/convert.js +39 -10
- package/dist/store/store.d.ts +35 -10
- package/dist/store/store.js +446 -55
- package/dist/utils/event.d.ts +38 -9
- package/dist/utils/event.js +101 -25
- package/dist/utils/flaky.d.ts +2 -0
- package/dist/utils/flaky.js +12 -0
- package/dist/utils/new.d.ts +6 -0
- package/dist/utils/new.js +23 -0
- package/package.json +34 -18
- package/dist/qualityGate.d.ts +0 -25
- package/dist/qualityGate.js +0 -116
package/dist/api.d.ts
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { DefaultLabelsConfig, EnvironmentsConfig, KnownTestFailure, ReportVariables } from "@allurereport/core-api";
|
|
2
|
+
import type { Plugin, QualityGateConfig, ReportFiles } from "@allurereport/plugin-api";
|
|
3
3
|
import type { ResultsReader } from "@allurereport/reader-api";
|
|
4
|
+
export interface PluginInstance {
|
|
5
|
+
id: string;
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
plugin: Plugin;
|
|
8
|
+
options: Record<string, any>;
|
|
9
|
+
}
|
|
4
10
|
export interface FullConfig {
|
|
5
11
|
name: string;
|
|
6
12
|
output: string;
|
|
13
|
+
open: boolean;
|
|
14
|
+
port: string | undefined;
|
|
15
|
+
historyPath?: string;
|
|
16
|
+
knownIssuesPath: string;
|
|
17
|
+
defaultLabels?: DefaultLabelsConfig;
|
|
18
|
+
stage?: string;
|
|
19
|
+
environment?: string;
|
|
20
|
+
environments?: EnvironmentsConfig;
|
|
21
|
+
variables?: ReportVariables;
|
|
7
22
|
reportFiles: ReportFiles;
|
|
8
23
|
readers?: ResultsReader[];
|
|
9
24
|
plugins?: PluginInstance[];
|
|
10
|
-
history: HistoryDataPoint[];
|
|
11
|
-
historyPath: string;
|
|
12
25
|
appendHistory?: boolean;
|
|
13
|
-
knownIssuesPath: string;
|
|
14
26
|
known?: KnownTestFailure[];
|
|
15
|
-
qualityGate?: QualityGateConfig;
|
|
16
|
-
defaultLabels?: DefaultLabelsConfig;
|
|
17
27
|
realTime?: any;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
qualityGate?: QualityGateConfig;
|
|
29
|
+
allureService?: {
|
|
30
|
+
url?: string;
|
|
31
|
+
project?: string;
|
|
32
|
+
accessToken?: string;
|
|
33
|
+
};
|
|
24
34
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import type { Config } from "@allurereport/plugin-api";
|
|
2
|
-
import type { FullConfig } from "./api.js";
|
|
3
|
-
export declare const getPluginId: (key: string) => string;
|
|
4
|
-
export declare const findConfig: (cwd: string, configPath?: string) => Promise<string | undefined>;
|
|
2
|
+
import type { FullConfig, PluginInstance } from "./api.js";
|
|
5
3
|
export interface ConfigOverride {
|
|
6
|
-
name?:
|
|
7
|
-
output?:
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
name?: Config["name"];
|
|
5
|
+
output?: Config["output"];
|
|
6
|
+
open?: Config["open"];
|
|
7
|
+
port?: Config["port"];
|
|
8
|
+
historyPath?: Config["historyPath"];
|
|
9
|
+
knownIssuesPath?: Config["knownIssuesPath"];
|
|
10
|
+
plugins?: Config["plugins"];
|
|
10
11
|
}
|
|
12
|
+
export declare const getPluginId: (key: string) => string;
|
|
13
|
+
export declare const findConfig: (cwd: string, configPath?: string) => Promise<string | undefined>;
|
|
11
14
|
export declare const validateConfig: (config: Config) => {
|
|
12
15
|
valid: boolean;
|
|
13
16
|
fields: string[];
|
|
14
17
|
};
|
|
15
|
-
export declare const
|
|
18
|
+
export declare const loadYamlConfig: (configPath: string) => Promise<Config>;
|
|
19
|
+
export declare const loadJsonConfig: (configPath: string) => Promise<Config>;
|
|
20
|
+
export declare const loadJsConfig: (configPath: string) => Promise<Config>;
|
|
16
21
|
export declare const resolveConfig: (config: Config, override?: ConfigOverride) => Promise<FullConfig>;
|
|
17
22
|
export declare const readConfig: (cwd?: string, configPath?: string, override?: ConfigOverride) => Promise<FullConfig>;
|
|
23
|
+
export declare const getPluginInstance: (config: FullConfig, predicate: (plugin: PluginInstance) => boolean) => PluginInstance | undefined;
|
|
18
24
|
export declare const resolvePlugin: (path: string) => Promise<any>;
|
package/dist/config.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import * as console from "node:console";
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
3
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { extname, resolve } from "node:path";
|
|
4
4
|
import * as process from "node:process";
|
|
5
|
-
import {
|
|
5
|
+
import { parse } from "yaml";
|
|
6
6
|
import { readKnownIssues } from "./known.js";
|
|
7
7
|
import { FileSystemReportFiles } from "./plugin.js";
|
|
8
8
|
import { importWrapper } from "./utils/module.js";
|
|
9
9
|
import { normalizeImportPath } from "./utils/path.js";
|
|
10
|
+
const CONFIG_FILENAMES = [
|
|
11
|
+
"allurerc.js",
|
|
12
|
+
"allurerc.mjs",
|
|
13
|
+
"allurerc.cjs",
|
|
14
|
+
"allurerc.json",
|
|
15
|
+
"allurerc.yaml",
|
|
16
|
+
"allurerc.yml",
|
|
17
|
+
];
|
|
18
|
+
const DEFAULT_CONFIG = {};
|
|
10
19
|
export const getPluginId = (key) => {
|
|
11
20
|
return key.replace(/^@.*\//, "").replace(/[/\\]/g, "-");
|
|
12
21
|
};
|
|
13
|
-
const configNames = ["allurerc.js", "allurerc.mjs"];
|
|
14
|
-
const defaultConfig = {};
|
|
15
22
|
export const findConfig = async (cwd, configPath) => {
|
|
16
23
|
if (configPath) {
|
|
17
24
|
const resolved = resolve(cwd, configPath);
|
|
@@ -26,8 +33,8 @@ export const findConfig = async (cwd, configPath) => {
|
|
|
26
33
|
}
|
|
27
34
|
throw new Error(`invalid config path ${resolved}: not a regular file`);
|
|
28
35
|
}
|
|
29
|
-
for (const
|
|
30
|
-
const resolved = resolve(cwd,
|
|
36
|
+
for (const configFilename of CONFIG_FILENAMES) {
|
|
37
|
+
const resolved = resolve(cwd, configFilename);
|
|
31
38
|
try {
|
|
32
39
|
const stats = await stat(resolved);
|
|
33
40
|
if (stats.isFile()) {
|
|
@@ -42,11 +49,17 @@ export const validateConfig = (config) => {
|
|
|
42
49
|
const supportedFields = [
|
|
43
50
|
"name",
|
|
44
51
|
"output",
|
|
52
|
+
"open",
|
|
53
|
+
"port",
|
|
45
54
|
"historyPath",
|
|
46
55
|
"knownIssuesPath",
|
|
47
|
-
"qualityGate",
|
|
48
56
|
"plugins",
|
|
49
57
|
"defaultLabels",
|
|
58
|
+
"variables",
|
|
59
|
+
"environments",
|
|
60
|
+
"appendHistory",
|
|
61
|
+
"qualityGate",
|
|
62
|
+
"allureService",
|
|
50
63
|
];
|
|
51
64
|
const unsupportedFields = Object.keys(config).filter((key) => !supportedFields.includes(key));
|
|
52
65
|
return {
|
|
@@ -54,7 +67,33 @@ export const validateConfig = (config) => {
|
|
|
54
67
|
fields: unsupportedFields,
|
|
55
68
|
};
|
|
56
69
|
};
|
|
57
|
-
export const
|
|
70
|
+
export const loadYamlConfig = async (configPath) => {
|
|
71
|
+
try {
|
|
72
|
+
const rawConfig = await readFile(configPath, "utf-8");
|
|
73
|
+
const parsedConfig = parse(rawConfig);
|
|
74
|
+
return parsedConfig || DEFAULT_CONFIG;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err?.code === "ENOENT") {
|
|
78
|
+
return DEFAULT_CONFIG;
|
|
79
|
+
}
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
export const loadJsonConfig = async (configPath) => {
|
|
84
|
+
try {
|
|
85
|
+
const rawConfig = await readFile(configPath, "utf-8");
|
|
86
|
+
const parsedConfig = JSON.parse(rawConfig);
|
|
87
|
+
return parsedConfig || DEFAULT_CONFIG;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
if (err?.code === "ENOENT") {
|
|
91
|
+
return DEFAULT_CONFIG;
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
export const loadJsConfig = async (configPath) => {
|
|
58
97
|
return (await import(normalizeImportPath(configPath))).default;
|
|
59
98
|
};
|
|
60
99
|
export const resolveConfig = async (config, override = {}) => {
|
|
@@ -63,12 +102,16 @@ export const resolveConfig = async (config, override = {}) => {
|
|
|
63
102
|
throw new Error(`The provided Allure config contains unsupported fields: ${validationResult.fields.join(", ")}`);
|
|
64
103
|
}
|
|
65
104
|
const name = override.name ?? config.name ?? "Allure Report";
|
|
66
|
-
const
|
|
105
|
+
const open = override.open ?? config.open ?? false;
|
|
106
|
+
const port = override.port ?? config.port ?? undefined;
|
|
107
|
+
const historyPath = override.historyPath ?? config.historyPath;
|
|
108
|
+
const appendHistory = config.appendHistory ?? true;
|
|
67
109
|
const knownIssuesPath = resolve(override.knownIssuesPath ?? config.knownIssuesPath ?? "./allure/known.json");
|
|
68
110
|
const output = resolve(override.output ?? config.output ?? "./allure-report");
|
|
69
|
-
const history = await readHistory(historyPath);
|
|
70
111
|
const known = await readKnownIssues(knownIssuesPath);
|
|
71
|
-
const
|
|
112
|
+
const variables = config.variables ?? {};
|
|
113
|
+
const environments = config.environments ?? {};
|
|
114
|
+
const plugins = Object.keys(override?.plugins ?? config?.plugins ?? {}).length === 0
|
|
72
115
|
? {
|
|
73
116
|
awesome: {
|
|
74
117
|
options: {},
|
|
@@ -78,20 +121,46 @@ export const resolveConfig = async (config, override = {}) => {
|
|
|
78
121
|
const pluginInstances = await resolvePlugins(plugins);
|
|
79
122
|
return {
|
|
80
123
|
name,
|
|
81
|
-
reportFiles: new FileSystemReportFiles(output),
|
|
82
|
-
plugins: pluginInstances,
|
|
83
124
|
output,
|
|
84
|
-
|
|
85
|
-
|
|
125
|
+
open,
|
|
126
|
+
port,
|
|
86
127
|
knownIssuesPath,
|
|
87
128
|
known,
|
|
129
|
+
variables,
|
|
130
|
+
environments,
|
|
131
|
+
appendHistory,
|
|
132
|
+
historyPath: historyPath ? resolve(historyPath) : undefined,
|
|
133
|
+
reportFiles: new FileSystemReportFiles(output),
|
|
134
|
+
plugins: pluginInstances,
|
|
135
|
+
defaultLabels: config.defaultLabels ?? {},
|
|
88
136
|
qualityGate: config.qualityGate,
|
|
137
|
+
allureService: config.allureService,
|
|
89
138
|
};
|
|
90
139
|
};
|
|
91
140
|
export const readConfig = async (cwd = process.cwd(), configPath, override) => {
|
|
92
|
-
const cfg = await findConfig(cwd, configPath);
|
|
93
|
-
|
|
94
|
-
|
|
141
|
+
const cfg = (await findConfig(cwd, configPath)) ?? "";
|
|
142
|
+
let config;
|
|
143
|
+
switch (extname(cfg)) {
|
|
144
|
+
case ".json":
|
|
145
|
+
config = await loadJsonConfig(cfg);
|
|
146
|
+
break;
|
|
147
|
+
case ".yaml":
|
|
148
|
+
case ".yml":
|
|
149
|
+
config = await loadYamlConfig(cfg);
|
|
150
|
+
break;
|
|
151
|
+
case ".js":
|
|
152
|
+
case ".cjs":
|
|
153
|
+
case ".mjs":
|
|
154
|
+
config = await loadJsConfig(cfg);
|
|
155
|
+
break;
|
|
156
|
+
default:
|
|
157
|
+
config = DEFAULT_CONFIG;
|
|
158
|
+
}
|
|
159
|
+
const fullConfig = await resolveConfig(config, override);
|
|
160
|
+
return fullConfig;
|
|
161
|
+
};
|
|
162
|
+
export const getPluginInstance = (config, predicate) => {
|
|
163
|
+
return config?.plugins?.find(predicate);
|
|
95
164
|
};
|
|
96
165
|
export const resolvePlugin = async (path) => {
|
|
97
166
|
if (!path.startsWith("@allurereport/plugin-")) {
|
|
@@ -113,9 +182,10 @@ const resolvePlugins = async (plugins) => {
|
|
|
113
182
|
const pluginInstances = [];
|
|
114
183
|
for (const id in plugins) {
|
|
115
184
|
const pluginConfig = plugins[id];
|
|
185
|
+
const pluginId = getPluginId(id);
|
|
116
186
|
const Plugin = await resolvePlugin(pluginConfig.import ?? id);
|
|
117
187
|
pluginInstances.push({
|
|
118
|
-
id:
|
|
188
|
+
id: pluginId,
|
|
119
189
|
enabled: pluginConfig.enabled ?? true,
|
|
120
190
|
options: pluginConfig.options ?? {},
|
|
121
191
|
plugin: new Plugin(pluginConfig.options),
|
package/dist/history.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { HistoryDataPoint, TestCase, TestResult } from "@allurereport/core-api";
|
|
2
|
-
export declare const createHistory: (reportUuid: string, reportName: string | undefined, testCases: TestCase[], testResults: TestResult[]) => HistoryDataPoint;
|
|
3
|
-
export declare const readHistory: (historyPath: string) => Promise<HistoryDataPoint[]>;
|
|
1
|
+
import type { AllureHistory, HistoryDataPoint, TestCase, TestResult } from "@allurereport/core-api";
|
|
2
|
+
export declare const createHistory: (reportUuid: string, reportName: string | undefined, testCases: TestCase[], testResults: TestResult[], remoteUrl?: string) => HistoryDataPoint;
|
|
4
3
|
export declare const writeHistory: (historyPath: string, data: HistoryDataPoint) => Promise<void>;
|
|
4
|
+
export declare class AllureLocalHistory implements AllureHistory {
|
|
5
|
+
private readonly historyPath;
|
|
6
|
+
constructor(historyPath: string);
|
|
7
|
+
readHistory(): Promise<HistoryDataPoint[]>;
|
|
8
|
+
appendHistory(data: HistoryDataPoint): Promise<void>;
|
|
9
|
+
}
|
package/dist/history.js
CHANGED
|
@@ -4,27 +4,30 @@ import { isFileNotFoundError } from "./utils/misc.js";
|
|
|
4
4
|
const createHistoryItems = (testResults) => {
|
|
5
5
|
return testResults
|
|
6
6
|
.filter((tr) => tr.historyId)
|
|
7
|
-
.map(({ id, name, fullName, historyId, status, error: { message, trace } = {}, start, stop, duration }) => {
|
|
7
|
+
.map(({ id, name, fullName, environment, historyId, status, error: { message, trace } = {}, start, stop, duration, labels, }) => {
|
|
8
8
|
return {
|
|
9
9
|
id,
|
|
10
10
|
name,
|
|
11
11
|
fullName,
|
|
12
|
+
environment,
|
|
12
13
|
status,
|
|
13
14
|
message,
|
|
14
15
|
trace,
|
|
15
16
|
start,
|
|
16
17
|
stop,
|
|
17
18
|
duration,
|
|
19
|
+
labels,
|
|
20
|
+
url: "",
|
|
18
21
|
historyId: historyId,
|
|
19
22
|
reportLinks: [],
|
|
20
23
|
};
|
|
21
24
|
})
|
|
22
|
-
.reduce((
|
|
23
|
-
|
|
24
|
-
return
|
|
25
|
+
.reduce((acc, item) => {
|
|
26
|
+
acc[item.historyId] = item;
|
|
27
|
+
return acc;
|
|
25
28
|
}, {});
|
|
26
29
|
};
|
|
27
|
-
export const createHistory = (reportUuid, reportName = "Allure Report", testCases, testResults) => {
|
|
30
|
+
export const createHistory = (reportUuid, reportName = "Allure Report", testCases, testResults, remoteUrl = "") => {
|
|
28
31
|
const knownTestCaseIds = testCases.map((tc) => tc.id);
|
|
29
32
|
return {
|
|
30
33
|
uuid: reportUuid,
|
|
@@ -33,26 +36,38 @@ export const createHistory = (reportUuid, reportName = "Allure Report", testCase
|
|
|
33
36
|
knownTestCaseIds,
|
|
34
37
|
testResults: createHistoryItems(testResults),
|
|
35
38
|
metrics: {},
|
|
39
|
+
url: remoteUrl,
|
|
36
40
|
};
|
|
37
41
|
};
|
|
38
|
-
export const readHistory = async (historyPath) => {
|
|
39
|
-
const path = resolve(historyPath);
|
|
40
|
-
try {
|
|
41
|
-
return (await readFile(path, { encoding: "utf-8" }))
|
|
42
|
-
.split("\n")
|
|
43
|
-
.filter((line) => line && line.trim() !== "")
|
|
44
|
-
.map((line) => JSON.parse(line));
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
if (isFileNotFoundError(e)) {
|
|
48
|
-
return [];
|
|
49
|
-
}
|
|
50
|
-
throw e;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
42
|
export const writeHistory = async (historyPath, data) => {
|
|
54
43
|
const path = resolve(historyPath);
|
|
55
44
|
const parentDir = dirname(path);
|
|
56
45
|
await mkdir(parentDir, { recursive: true });
|
|
57
46
|
await writeFile(path, `${JSON.stringify(data)}\n`, { encoding: "utf-8", flag: "a+" });
|
|
58
47
|
};
|
|
48
|
+
export class AllureLocalHistory {
|
|
49
|
+
constructor(historyPath) {
|
|
50
|
+
this.historyPath = historyPath;
|
|
51
|
+
}
|
|
52
|
+
async readHistory() {
|
|
53
|
+
const path = resolve(this.historyPath);
|
|
54
|
+
try {
|
|
55
|
+
return (await readFile(path, { encoding: "utf-8" }))
|
|
56
|
+
.split("\n")
|
|
57
|
+
.filter((line) => line && line.trim() !== "")
|
|
58
|
+
.map((line) => JSON.parse(line));
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
if (isFileNotFoundError(e)) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async appendHistory(data) {
|
|
68
|
+
const path = resolve(this.historyPath);
|
|
69
|
+
const parentDir = dirname(path);
|
|
70
|
+
await mkdir(parentDir, { recursive: true });
|
|
71
|
+
await writeFile(path, `${JSON.stringify(data)}\n`, { encoding: "utf-8", flag: "a+" });
|
|
72
|
+
}
|
|
73
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ export type * from "./api.js";
|
|
|
2
2
|
export * from "./utils/misc.js";
|
|
3
3
|
export * from "./utils/crypto.js";
|
|
4
4
|
export * from "./utils/path.js";
|
|
5
|
+
export * from "./utils/new.js";
|
|
6
|
+
export * from "./utils/flaky.js";
|
|
5
7
|
export * from "./history.js";
|
|
6
8
|
export * from "./known.js";
|
|
7
|
-
export { resolveConfig, readConfig } from "./config.js";
|
|
9
|
+
export { resolveConfig, readConfig, getPluginInstance } from "./config.js";
|
|
8
10
|
export * from "./report.js";
|
|
9
11
|
export * from "./plugin.js";
|
|
10
|
-
export
|
|
12
|
+
export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export * from "./utils/misc.js";
|
|
2
2
|
export * from "./utils/crypto.js";
|
|
3
3
|
export * from "./utils/path.js";
|
|
4
|
+
export * from "./utils/new.js";
|
|
5
|
+
export * from "./utils/flaky.js";
|
|
4
6
|
export * from "./history.js";
|
|
5
7
|
export * from "./known.js";
|
|
6
|
-
export { resolveConfig, readConfig } from "./config.js";
|
|
8
|
+
export { resolveConfig, readConfig, getPluginInstance } from "./config.js";
|
|
7
9
|
export * from "./report.js";
|
|
8
10
|
export * from "./plugin.js";
|
|
9
|
-
export
|
|
11
|
+
export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
|
package/dist/plugin.d.ts
CHANGED
|
@@ -3,20 +3,21 @@ export declare class DefaultPluginState implements PluginState {
|
|
|
3
3
|
#private;
|
|
4
4
|
constructor(state: Record<string, any>);
|
|
5
5
|
set: (key: string, value: any) => Promise<void>;
|
|
6
|
-
get: (key: string) => Promise<
|
|
6
|
+
get: <T>(key: string) => Promise<T>;
|
|
7
7
|
unset: (key: string) => Promise<void>;
|
|
8
8
|
}
|
|
9
9
|
export declare class PluginFiles implements ReportFiles {
|
|
10
10
|
#private;
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
readonly callback?: ((key: string, path: string) => void) | undefined;
|
|
12
|
+
constructor(parent: ReportFiles, pluginId: string, callback?: ((key: string, path: string) => void) | undefined);
|
|
13
|
+
addFile: (key: string, data: Buffer) => Promise<string>;
|
|
13
14
|
}
|
|
14
15
|
export declare class InMemoryReportFiles implements ReportFiles {
|
|
15
16
|
#private;
|
|
16
|
-
addFile: (path: string, data: Buffer) => Promise<
|
|
17
|
+
addFile: (path: string, data: Buffer) => Promise<string>;
|
|
17
18
|
}
|
|
18
19
|
export declare class FileSystemReportFiles implements ReportFiles {
|
|
19
20
|
#private;
|
|
20
21
|
constructor(output: string);
|
|
21
|
-
addFile: (path: string, data: Buffer) => Promise<
|
|
22
|
+
addFile: (path: string, data: Buffer) => Promise<string>;
|
|
22
23
|
}
|
package/dist/plugin.js
CHANGED
|
@@ -11,7 +11,8 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
11
11
|
};
|
|
12
12
|
var _DefaultPluginState_state, _PluginFiles_parent, _PluginFiles_pluginId, _InMemoryReportFiles_state, _FileSystemReportFiles_output;
|
|
13
13
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
14
|
-
import { dirname,
|
|
14
|
+
import { dirname, resolve } from "node:path";
|
|
15
|
+
import { join as joinPosix } from "node:path/posix";
|
|
15
16
|
export class DefaultPluginState {
|
|
16
17
|
constructor(state) {
|
|
17
18
|
_DefaultPluginState_state.set(this, void 0);
|
|
@@ -29,11 +30,14 @@ export class DefaultPluginState {
|
|
|
29
30
|
}
|
|
30
31
|
_DefaultPluginState_state = new WeakMap();
|
|
31
32
|
export class PluginFiles {
|
|
32
|
-
constructor(parent, pluginId) {
|
|
33
|
+
constructor(parent, pluginId, callback) {
|
|
34
|
+
this.callback = callback;
|
|
33
35
|
_PluginFiles_parent.set(this, void 0);
|
|
34
36
|
_PluginFiles_pluginId.set(this, void 0);
|
|
35
37
|
this.addFile = async (key, data) => {
|
|
36
|
-
await __classPrivateFieldGet(this, _PluginFiles_parent, "f").addFile(
|
|
38
|
+
const filepath = await __classPrivateFieldGet(this, _PluginFiles_parent, "f").addFile(joinPosix(__classPrivateFieldGet(this, _PluginFiles_pluginId, "f"), key), data);
|
|
39
|
+
this.callback?.(key, filepath);
|
|
40
|
+
return filepath;
|
|
37
41
|
};
|
|
38
42
|
__classPrivateFieldSet(this, _PluginFiles_parent, parent, "f");
|
|
39
43
|
__classPrivateFieldSet(this, _PluginFiles_pluginId, pluginId, "f");
|
|
@@ -45,6 +49,7 @@ export class InMemoryReportFiles {
|
|
|
45
49
|
_InMemoryReportFiles_state.set(this, {});
|
|
46
50
|
this.addFile = async (path, data) => {
|
|
47
51
|
__classPrivateFieldGet(this, _InMemoryReportFiles_state, "f")[path] = data;
|
|
52
|
+
return path;
|
|
48
53
|
};
|
|
49
54
|
}
|
|
50
55
|
}
|
|
@@ -57,6 +62,7 @@ export class FileSystemReportFiles {
|
|
|
57
62
|
const targetDirPath = dirname(targetPath);
|
|
58
63
|
await mkdir(targetDirPath, { recursive: true });
|
|
59
64
|
await writeFile(targetPath, data, { encoding: "utf-8" });
|
|
65
|
+
return targetPath;
|
|
60
66
|
};
|
|
61
67
|
__classPrivateFieldSet(this, _FileSystemReportFiles_output, resolve(output), "f");
|
|
62
68
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { KnownTestFailure, TestError, TestResult } from "@allurereport/core-api";
|
|
2
|
+
import type { QualityGateConfig, QualityGateValidationResult } from "@allurereport/plugin-api";
|
|
3
|
+
export declare const stringifyQualityGateResults: (results: QualityGateValidationResult[]) => string;
|
|
4
|
+
export declare const convertQualityGateResultsToTestErrors: (results: QualityGateValidationResult[]) => TestError[];
|
|
5
|
+
export declare class QualityGateState {
|
|
6
|
+
#private;
|
|
7
|
+
setResult(rule: string, value: any): void;
|
|
8
|
+
getResult(rule: string): any;
|
|
9
|
+
}
|
|
10
|
+
export declare class QualityGate {
|
|
11
|
+
private readonly config;
|
|
12
|
+
constructor(config: QualityGateConfig);
|
|
13
|
+
validate(payload: {
|
|
14
|
+
state?: QualityGateState;
|
|
15
|
+
trs: TestResult[];
|
|
16
|
+
knownIssues: KnownTestFailure[];
|
|
17
|
+
}): Promise<{
|
|
18
|
+
fastFailed: boolean;
|
|
19
|
+
results: QualityGateValidationResult[];
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
3
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
|
+
};
|
|
6
|
+
var _QualityGateState_state;
|
|
7
|
+
import { gray, red } from "yoctocolors";
|
|
8
|
+
import { qualityGateDefaultRules } from "./rules.js";
|
|
9
|
+
export const stringifyQualityGateResults = (results) => {
|
|
10
|
+
if (results.length === 0) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
const lines = [red("Quality Gate failed with following issues:")];
|
|
14
|
+
const maxMessageLength = Math.max(...results.map((r) => r.message.length));
|
|
15
|
+
lines.push("");
|
|
16
|
+
results.forEach((result) => {
|
|
17
|
+
lines.push(` ${red("⨯")} ${result.message.padEnd(maxMessageLength, " ")} ${gray(result.rule)}`);
|
|
18
|
+
});
|
|
19
|
+
lines.push("");
|
|
20
|
+
lines.push(red(`${results.length} quality gate rules have been failed.`));
|
|
21
|
+
return lines.join("\n");
|
|
22
|
+
};
|
|
23
|
+
export const convertQualityGateResultsToTestErrors = (results) => {
|
|
24
|
+
return results.map((result) => ({
|
|
25
|
+
message: `Quality Gate (${result.rule}): ${result.message}`,
|
|
26
|
+
actual: result.actual,
|
|
27
|
+
expected: result.expected,
|
|
28
|
+
}));
|
|
29
|
+
};
|
|
30
|
+
export class QualityGateState {
|
|
31
|
+
constructor() {
|
|
32
|
+
_QualityGateState_state.set(this, {});
|
|
33
|
+
}
|
|
34
|
+
setResult(rule, value) {
|
|
35
|
+
__classPrivateFieldGet(this, _QualityGateState_state, "f")[rule] = value;
|
|
36
|
+
}
|
|
37
|
+
getResult(rule) {
|
|
38
|
+
return __classPrivateFieldGet(this, _QualityGateState_state, "f")[rule];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
_QualityGateState_state = new WeakMap();
|
|
42
|
+
export class QualityGate {
|
|
43
|
+
constructor(config) {
|
|
44
|
+
this.config = config;
|
|
45
|
+
}
|
|
46
|
+
async validate(payload) {
|
|
47
|
+
const { state, trs, knownIssues } = payload;
|
|
48
|
+
const { rules, use = [...qualityGateDefaultRules] } = this.config;
|
|
49
|
+
const results = [];
|
|
50
|
+
let fastFailed = false;
|
|
51
|
+
if (!rules?.length) {
|
|
52
|
+
return {
|
|
53
|
+
fastFailed: false,
|
|
54
|
+
results,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
for (const ruleset of rules) {
|
|
58
|
+
if (fastFailed) {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
for (const [key, expected] of Object.entries(ruleset)) {
|
|
62
|
+
if (key === "filter" || key === "id" || key === "fastFail") {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const rule = use.filter((r) => r.rule === key).pop();
|
|
66
|
+
if (!rule) {
|
|
67
|
+
throw new Error(`Rule ${key} is not provided. Make sure you have provided it in the "use" field of the quality gate config!`);
|
|
68
|
+
}
|
|
69
|
+
const trsToValidate = ruleset.filter ? trs.filter(ruleset.filter) : trs;
|
|
70
|
+
const ruleId = ruleset.id ? [ruleset.id, rule.rule].join("/") : rule.rule;
|
|
71
|
+
const result = await rule.validate({
|
|
72
|
+
trs: trsToValidate,
|
|
73
|
+
state: {
|
|
74
|
+
getResult: () => state?.getResult?.(ruleId),
|
|
75
|
+
setResult: (value) => state?.setResult?.(ruleId, value),
|
|
76
|
+
},
|
|
77
|
+
expected,
|
|
78
|
+
knownIssues,
|
|
79
|
+
});
|
|
80
|
+
if (result.success) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
results.push({
|
|
84
|
+
...result,
|
|
85
|
+
expected,
|
|
86
|
+
rule: ruleset.id ? [ruleset.id, rule.rule].join("/") : rule.rule,
|
|
87
|
+
message: rule.message({
|
|
88
|
+
actual: result.actual,
|
|
89
|
+
expected,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
if (ruleset.fastFail) {
|
|
93
|
+
fastFailed = true;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
fastFailed,
|
|
100
|
+
results,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type QualityGateRule } from "@allurereport/plugin-api";
|
|
2
|
+
export declare const maxFailuresRule: QualityGateRule<number>;
|
|
3
|
+
export declare const minTestsCountRule: QualityGateRule<number>;
|
|
4
|
+
export declare const successRateRule: QualityGateRule<number>;
|
|
5
|
+
export declare const maxDurationRule: QualityGateRule<number>;
|
|
6
|
+
export declare const qualityGateDefaultRules: QualityGateRule<number>[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { filterSuccessful, filterUnsuccessful } from "@allurereport/core-api";
|
|
2
|
+
import { bold } from "yoctocolors";
|
|
3
|
+
export const maxFailuresRule = {
|
|
4
|
+
rule: "maxFailures",
|
|
5
|
+
message: ({ actual, expected }) => `The number of failed tests ${bold(String(actual))} exceeds the allowed threshold value ${bold(String(expected))}`,
|
|
6
|
+
validate: async ({ trs, knownIssues, expected, state }) => {
|
|
7
|
+
const knownIssuesHistoryIds = knownIssues.map(({ historyId }) => historyId);
|
|
8
|
+
const unknown = trs.filter((tr) => !tr.historyId || !knownIssuesHistoryIds.includes(tr.historyId));
|
|
9
|
+
const failedTrs = unknown.filter(filterUnsuccessful);
|
|
10
|
+
const actual = failedTrs.length + (state.getResult() ?? 0);
|
|
11
|
+
state.setResult(actual);
|
|
12
|
+
return {
|
|
13
|
+
success: actual <= expected,
|
|
14
|
+
actual,
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export const minTestsCountRule = {
|
|
19
|
+
rule: "minTestsCount",
|
|
20
|
+
message: ({ actual, expected }) => `The total number of tests ${bold(String(actual))} is less than the expected threshold value ${bold(String(expected))}`,
|
|
21
|
+
validate: async ({ trs, expected, state }) => {
|
|
22
|
+
const actual = trs.length + (state.getResult() ?? 0);
|
|
23
|
+
state.setResult(actual);
|
|
24
|
+
return {
|
|
25
|
+
success: actual >= expected,
|
|
26
|
+
actual,
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export const successRateRule = {
|
|
31
|
+
rule: "successRate",
|
|
32
|
+
message: ({ actual, expected }) => `Success rate ${bold(String(actual))} is less, than expected ${bold(String(expected))}`,
|
|
33
|
+
validate: async ({ trs, knownIssues, expected }) => {
|
|
34
|
+
const knownIssuesHistoryIds = knownIssues.map(({ historyId }) => historyId);
|
|
35
|
+
const unknown = trs.filter((tr) => !tr.historyId || !knownIssuesHistoryIds.includes(tr.historyId));
|
|
36
|
+
const passedTrs = unknown.filter(filterSuccessful);
|
|
37
|
+
const rate = passedTrs.length === 0 ? 0 : passedTrs.length / unknown.length;
|
|
38
|
+
return {
|
|
39
|
+
success: rate >= expected,
|
|
40
|
+
actual: rate,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export const maxDurationRule = {
|
|
45
|
+
rule: "maxDuration",
|
|
46
|
+
message: ({ actual, expected }) => `Maximum duration of some tests exceed the defined limit; actual ${bold(String(actual))}, expected ${bold(String(expected))}`,
|
|
47
|
+
validate: async ({ trs, expected }) => {
|
|
48
|
+
const actual = Math.max(...trs.map((tr) => tr.duration ?? 0));
|
|
49
|
+
return {
|
|
50
|
+
success: actual <= expected,
|
|
51
|
+
actual,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
export const qualityGateDefaultRules = [maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule];
|