@allurereport/core 3.17.0 → 3.19.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/config.d.ts +1 -1
- package/dist/config.js +28 -12
- package/dist/history.d.ts +3 -0
- package/dist/history.js +47 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/qualityGate/qualityGate.d.ts +1 -0
- package/dist/qualityGate/qualityGate.js +19 -15
- package/dist/qualityGate/rules.js +20 -8
- package/dist/report.js +70 -15
- package/dist/resolutions.d.ts +8 -0
- package/dist/resolutions.js +23 -2
- package/dist/store/convert.d.ts +0 -1
- package/dist/store/convert.js +72 -58
- package/dist/store/retrySubstore.d.ts +1 -3
- package/dist/store/retrySubstore.js +0 -14
- package/dist/store/store.d.ts +4 -2
- package/dist/store/store.js +242 -189
- package/dist/utils/event.d.ts +21 -1
- package/dist/utils/event.js +30 -4
- package/dist/utils/flaky.d.ts +2 -2
- package/dist/utils/flaky.js +36 -8
- package/dist/utils/realtimeChannel.d.ts +1 -0
- package/dist/utils/realtimeChannel.js +2 -2
- package/package.json +22 -22
package/dist/config.d.ts
CHANGED
|
@@ -9,11 +9,11 @@ export interface ConfigOverride {
|
|
|
9
9
|
port?: Config["port"];
|
|
10
10
|
hideLabels?: Config["hideLabels"];
|
|
11
11
|
historyPath?: Config["historyPath"];
|
|
12
|
+
historyBaseUrl?: Config["historyBaseUrl"];
|
|
12
13
|
historyLimit?: Config["historyLimit"];
|
|
13
14
|
resolutions?: Pick<NonNullable<Config["resolutions"]>, "knownIssuesPath">;
|
|
14
15
|
plugins?: Config["plugins"];
|
|
15
16
|
}
|
|
16
|
-
export declare const parseIntegerConfigValue: (value: unknown, defaultValue: number, minValue: number) => number;
|
|
17
17
|
export declare const isAgentDescriptor: (value: string | undefined) => value is "agent" | "@allurereport/plugin-agent";
|
|
18
18
|
export declare const hasConfiguredAgent: (plugins: Record<string, PluginDescriptor>) => boolean;
|
|
19
19
|
export declare const assertValidPluginId: (id: string) => void;
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { extname, resolve } from "node:path";
|
|
3
3
|
import * as process from "node:process";
|
|
4
|
-
import { validateEnvironmentName } from "@allurereport/core-api";
|
|
4
|
+
import { parseIntegerConfigValue, validateEnvironmentName } from "@allurereport/core-api";
|
|
5
5
|
import { createJiti } from "jiti";
|
|
6
6
|
import { parse } from "yaml";
|
|
7
7
|
import { FileSystemReportFiles } from "./plugin.js";
|
|
@@ -23,16 +23,8 @@ const CONFIG_FILENAMES = [
|
|
|
23
23
|
"allurerc.yml",
|
|
24
24
|
];
|
|
25
25
|
const DEFAULT_CONFIG = {};
|
|
26
|
-
const DEFAULT_ALLURE_SERVICE_UPLOAD_CONCURRENCY = 100;
|
|
27
26
|
const DEFAULT_ALLURE_SERVICE_UPLAOD_MAX_ATTEMPTS = 5;
|
|
28
27
|
const DEFAULT_ALLURE_SERVICE_UPLOAD_MAX_SIMULTANEOUS_FAILURES = 5;
|
|
29
|
-
export const parseIntegerConfigValue = (value, defaultValue, minValue) => {
|
|
30
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
31
|
-
return defaultValue;
|
|
32
|
-
}
|
|
33
|
-
const normalized = Math.floor(value);
|
|
34
|
-
return normalized >= minValue ? normalized : defaultValue;
|
|
35
|
-
};
|
|
36
28
|
export const isAgentDescriptor = (value) => {
|
|
37
29
|
return value === "agent" || value === "@allurereport/plugin-agent";
|
|
38
30
|
};
|
|
@@ -97,7 +89,9 @@ export const validateConfig = (config) => {
|
|
|
97
89
|
"port",
|
|
98
90
|
"hideLabels",
|
|
99
91
|
"historyPath",
|
|
92
|
+
"historyBaseUrl",
|
|
100
93
|
"historyLimit",
|
|
94
|
+
"flakyDetection",
|
|
101
95
|
"resolutions",
|
|
102
96
|
"plugins",
|
|
103
97
|
"defaultLabels",
|
|
@@ -112,6 +106,7 @@ export const validateConfig = (config) => {
|
|
|
112
106
|
"categories",
|
|
113
107
|
"globalAttachments",
|
|
114
108
|
"resultsDir",
|
|
109
|
+
"dump",
|
|
115
110
|
];
|
|
116
111
|
const unsupportedFields = Object.keys(config).filter((key) => !supportedFields.includes(key));
|
|
117
112
|
return {
|
|
@@ -190,19 +185,34 @@ const resolveConfigEnvironments = (config) => {
|
|
|
190
185
|
allowedEnvironments,
|
|
191
186
|
};
|
|
192
187
|
};
|
|
188
|
+
const validateFlakyDetectionConfig = (config) => {
|
|
189
|
+
if (config === undefined) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const { historyDepth, includePassedTests } = config;
|
|
193
|
+
if (historyDepth !== undefined && (!Number.isInteger(historyDepth) || historyDepth < -1)) {
|
|
194
|
+
throw new Error("flakyDetection.historyDepth must be an integer greater than or equal to -1");
|
|
195
|
+
}
|
|
196
|
+
if (includePassedTests !== undefined && typeof includePassedTests !== "boolean") {
|
|
197
|
+
throw new TypeError("flakyDetection.includePassedTests must be a boolean");
|
|
198
|
+
}
|
|
199
|
+
};
|
|
193
200
|
export const resolveConfig = async (config, override = {}) => {
|
|
194
201
|
const validationResult = validateConfig(config);
|
|
195
202
|
if (!validationResult.valid) {
|
|
196
203
|
throw new Error(`The provided Allure config contains unsupported fields: ${validationResult.fields.join(", ")}`);
|
|
197
204
|
}
|
|
198
205
|
validateResolutionsConfig(config.resolutions);
|
|
206
|
+
validateFlakyDetectionConfig(config.flakyDetection);
|
|
199
207
|
const { environments, environment, allowedEnvironments } = resolveConfigEnvironments(config);
|
|
200
208
|
const name = override.name ?? config.name ?? "Allure Report";
|
|
201
209
|
const open = override.open ?? config.open ?? false;
|
|
202
210
|
const port = override.port ?? config.port ?? undefined;
|
|
203
211
|
const hideLabels = override.hideLabels ?? config.hideLabels;
|
|
204
212
|
const historyPath = override.historyPath ?? config.historyPath;
|
|
213
|
+
const historyBaseUrl = override.historyBaseUrl ?? config.historyBaseUrl;
|
|
205
214
|
const historyLimit = override.historyLimit ?? config.historyLimit;
|
|
215
|
+
const dump = config.dump;
|
|
206
216
|
const appendHistory = config.appendHistory ?? true;
|
|
207
217
|
const configuredKnownIssuesPath = override.resolutions?.knownIssuesPath ?? config.resolutions?.knownIssuesPath;
|
|
208
218
|
const knownIssuesPathInput = configuredKnownIssuesPath ?? (config.resolutions ? DEFAULT_KNOWN_ISSUES_PATH : undefined);
|
|
@@ -235,6 +245,7 @@ export const resolveConfig = async (config, override = {}) => {
|
|
|
235
245
|
};
|
|
236
246
|
pluginInstances = await resolvePlugins(pluginsWithAgent);
|
|
237
247
|
}
|
|
248
|
+
const uploadConcurrency = parseIntegerConfigValue(config.allureService?.uploadConcurrency, 1);
|
|
238
249
|
return {
|
|
239
250
|
name,
|
|
240
251
|
cwd,
|
|
@@ -249,7 +260,10 @@ export const resolveConfig = async (config, override = {}) => {
|
|
|
249
260
|
environments,
|
|
250
261
|
appendHistory,
|
|
251
262
|
historyLimit,
|
|
263
|
+
flakyDetection: config.flakyDetection,
|
|
252
264
|
historyPath: historyPath ? resolve(historyPath) : undefined,
|
|
265
|
+
historyBaseUrl,
|
|
266
|
+
dump,
|
|
253
267
|
reportFiles: new FileSystemReportFiles(output),
|
|
254
268
|
plugins: pluginInstances,
|
|
255
269
|
defaultLabels: config.defaultLabels ?? {},
|
|
@@ -259,9 +273,11 @@ export const resolveConfig = async (config, override = {}) => {
|
|
|
259
273
|
? {
|
|
260
274
|
accessToken: config.allureService.accessToken,
|
|
261
275
|
private: config.allureService.private,
|
|
262
|
-
uploadConcurrency:
|
|
263
|
-
uploadMaxAttempts: parseIntegerConfigValue(config.allureService.uploadMaxAttempts,
|
|
264
|
-
|
|
276
|
+
...(uploadConcurrency === undefined ? {} : { uploadConcurrency }),
|
|
277
|
+
uploadMaxAttempts: parseIntegerConfigValue(config.allureService.uploadMaxAttempts, 1) ??
|
|
278
|
+
DEFAULT_ALLURE_SERVICE_UPLAOD_MAX_ATTEMPTS,
|
|
279
|
+
uploadMaxSimultaneousFailures: parseIntegerConfigValue(config.allureService.uploadMaxSimultaneousFailures, 0) ??
|
|
280
|
+
DEFAULT_ALLURE_SERVICE_UPLOAD_MAX_SIMULTANEOUS_FAILURES,
|
|
265
281
|
}
|
|
266
282
|
: undefined,
|
|
267
283
|
categories: config.categories,
|
package/dist/history.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type AllureHistory, type HistoryDataPoint, type MetricSample, type TestCase, type TestResult } from "@allurereport/core-api";
|
|
2
|
+
export declare const normalizeHistoryBaseUrl: (historyBaseUrl: string) => string;
|
|
3
|
+
export declare const setHistoryDataPointUrl: (point: HistoryDataPoint, url: string) => HistoryDataPoint;
|
|
2
4
|
export declare const createHistory: (reportUuid: string, reportName: string | undefined, testCases: TestCase[], testResults: TestResult[], remoteUrl?: string, metrics?: MetricSample[]) => HistoryDataPoint;
|
|
3
5
|
export declare class AllureLocalHistory implements AllureHistory {
|
|
4
6
|
#private;
|
|
@@ -7,6 +9,7 @@ export declare class AllureLocalHistory implements AllureHistory {
|
|
|
7
9
|
historyPath: string;
|
|
8
10
|
limit?: number;
|
|
9
11
|
});
|
|
12
|
+
resolveTestResultUrl(historyUrl: string, pluginId: string, historicalResultId: string): string;
|
|
10
13
|
readHistory(): Promise<HistoryDataPoint[]>;
|
|
11
14
|
appendHistory(data: HistoryDataPoint): Promise<void>;
|
|
12
15
|
}
|
package/dist/history.js
CHANGED
|
@@ -19,8 +19,8 @@ import { normalizeHistoryDataPointUrls, } from "@allurereport/core-api";
|
|
|
19
19
|
import { isFileNotFoundError } from "./utils/misc.js";
|
|
20
20
|
const createHistoryItems = (testResults, remoteUrl) => {
|
|
21
21
|
return testResults
|
|
22
|
-
.filter((tr) => tr.
|
|
23
|
-
.map(({ id, name, fullName, environment,
|
|
22
|
+
.filter((tr) => tr.retryHash)
|
|
23
|
+
.map(({ id, name, fullName, environment, retryHash, status, error: { message, trace } = {}, start, stop, duration, labels, }) => {
|
|
24
24
|
return {
|
|
25
25
|
id,
|
|
26
26
|
name,
|
|
@@ -34,12 +34,12 @@ const createHistoryItems = (testResults, remoteUrl) => {
|
|
|
34
34
|
duration,
|
|
35
35
|
labels,
|
|
36
36
|
url: remoteUrl,
|
|
37
|
-
|
|
37
|
+
retryHash: retryHash,
|
|
38
38
|
reportLinks: [],
|
|
39
39
|
};
|
|
40
40
|
})
|
|
41
41
|
.reduce((acc, item) => {
|
|
42
|
-
acc[item.
|
|
42
|
+
acc[item.retryHash] = item;
|
|
43
43
|
return acc;
|
|
44
44
|
}, {});
|
|
45
45
|
};
|
|
@@ -53,6 +53,28 @@ const metricsToHistoryValues = (metrics) => {
|
|
|
53
53
|
}
|
|
54
54
|
return Object.fromEntries([...grouped.entries()].map(([key, values]) => [key, values.reduce((acc, value) => acc + value, 0) / values.length]));
|
|
55
55
|
};
|
|
56
|
+
export const normalizeHistoryBaseUrl = (historyBaseUrl) => {
|
|
57
|
+
let url;
|
|
58
|
+
try {
|
|
59
|
+
url = new URL(historyBaseUrl);
|
|
60
|
+
}
|
|
61
|
+
catch (cause) {
|
|
62
|
+
throw new Error(`Invalid historyBaseUrl ${JSON.stringify(historyBaseUrl)}: expected an absolute URL`, { cause });
|
|
63
|
+
}
|
|
64
|
+
if (url.href.includes("#")) {
|
|
65
|
+
throw new Error(`Invalid historyBaseUrl ${JSON.stringify(historyBaseUrl)}: fragments are not allowed`);
|
|
66
|
+
}
|
|
67
|
+
if (/\.html\/?$/iu.test(url.pathname)) {
|
|
68
|
+
throw new Error(`Invalid historyBaseUrl ${JSON.stringify(historyBaseUrl)}: expected a base URL, not an HTML document`);
|
|
69
|
+
}
|
|
70
|
+
url.pathname = `${url.pathname.replace(/\/+$/u, "")}/`;
|
|
71
|
+
return url.toString();
|
|
72
|
+
};
|
|
73
|
+
export const setHistoryDataPointUrl = (point, url) => ({
|
|
74
|
+
...point,
|
|
75
|
+
url,
|
|
76
|
+
testResults: Object.fromEntries(Object.entries(point.testResults).map(([historyId, item]) => [historyId, { ...item, url }])),
|
|
77
|
+
});
|
|
56
78
|
export const createHistory = (reportUuid, reportName = "Allure Report", testCases, testResults, remoteUrl = "", metrics = []) => {
|
|
57
79
|
const knownTestCaseIds = testCases.map((tc) => tc.id);
|
|
58
80
|
return {
|
|
@@ -138,6 +160,17 @@ export class AllureLocalHistory {
|
|
|
138
160
|
`doesn't match the actual number ${actualBytes}`);
|
|
139
161
|
});
|
|
140
162
|
}
|
|
163
|
+
resolveTestResultUrl(historyUrl, pluginId, historicalResultId) {
|
|
164
|
+
if (!historyUrl) {
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
const url = new URL(historyUrl);
|
|
168
|
+
if (url.pathname.endsWith("/")) {
|
|
169
|
+
url.pathname = `${url.pathname}${pluginId}/index.html`;
|
|
170
|
+
}
|
|
171
|
+
url.hash = historicalResultId;
|
|
172
|
+
return url.toString();
|
|
173
|
+
}
|
|
141
174
|
async readHistory() {
|
|
142
175
|
if (__classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f").length > 0) {
|
|
143
176
|
return __classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f");
|
|
@@ -174,15 +207,14 @@ export class AllureLocalHistory {
|
|
|
174
207
|
const { limit } = this.params;
|
|
175
208
|
await mkdir(parentDir, { recursive: true });
|
|
176
209
|
const { file: historyFile, exists: historyExists } = await __classPrivateFieldGet(this, _AllureLocalHistory_ensureFileOpenedToAppend, "f").call(this, fullPath);
|
|
210
|
+
let dst;
|
|
177
211
|
try {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
if (limit === 0 && !historyExists) {
|
|
212
|
+
if (limit === 0) {
|
|
213
|
+
if (historyExists)
|
|
214
|
+
await historyFile.truncate(0);
|
|
184
215
|
return;
|
|
185
216
|
}
|
|
217
|
+
dst = historyFile.createWriteStream({ encoding: "utf-8", start: 0, autoClose: false });
|
|
186
218
|
if (historyExists) {
|
|
187
219
|
const start = await __classPrivateFieldGet(this, _AllureLocalHistory_findFirstEntryAddress, "f").call(this, historyFile, limit ? limit - 1 : undefined);
|
|
188
220
|
const src = historyFile.createReadStream({ start, autoClose: false });
|
|
@@ -195,7 +227,11 @@ export class AllureLocalHistory {
|
|
|
195
227
|
}
|
|
196
228
|
}
|
|
197
229
|
finally {
|
|
198
|
-
|
|
230
|
+
const closing = historyFile.close();
|
|
231
|
+
if (historyFile.fd !== -1) {
|
|
232
|
+
dst?.destroy();
|
|
233
|
+
}
|
|
234
|
+
await closing;
|
|
199
235
|
if (limit !== 0) {
|
|
200
236
|
__classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f").push(normalizedData);
|
|
201
237
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export * from "./resolutions.js";
|
|
|
11
11
|
export { resolveConfig, readConfig, readRawConfig, getPluginInstance } from "./config.js";
|
|
12
12
|
export * from "./report.js";
|
|
13
13
|
export * from "./plugin.js";
|
|
14
|
-
export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
|
|
14
|
+
export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, filterFailedQualityGateResults, stringifyQualityGateResults, } from "./qualityGate/index.js";
|
package/dist/index.js
CHANGED
|
@@ -10,4 +10,4 @@ export * from "./resolutions.js";
|
|
|
10
10
|
export { resolveConfig, readConfig, readRawConfig, getPluginInstance } from "./config.js";
|
|
11
11
|
export * from "./report.js";
|
|
12
12
|
export * from "./plugin.js";
|
|
13
|
-
export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
|
|
13
|
+
export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, convertQualityGateResultsToTestErrors, filterFailedQualityGateResults, stringifyQualityGateResults, } from "./qualityGate/index.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type MetricSample, type TestError, type TestResult } from "@allurereport/core-api";
|
|
2
2
|
import type { QualityGateConfig, QualityGateMetricHistoryPoint, QualityGateValidationResult } from "@allurereport/plugin-api";
|
|
3
|
+
export declare const filterFailedQualityGateResults: (results: QualityGateValidationResult[]) => QualityGateValidationResult[];
|
|
3
4
|
export declare const stringifyQualityGateResults: (results: QualityGateValidationResult[]) => string;
|
|
4
5
|
export declare const convertQualityGateResultsToTestErrors: (results: QualityGateValidationResult[]) => TestError[];
|
|
5
6
|
export declare class QualityGateState {
|
|
@@ -4,25 +4,29 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
4
4
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5
5
|
};
|
|
6
6
|
var _QualityGateState_state, _QualityGateState_processedTestResultIds;
|
|
7
|
-
import { gray, red } from "yoctocolors";
|
|
7
|
+
import { bold, gray, red } from "yoctocolors";
|
|
8
8
|
import { isIgnoredFailure } from "../resolutions.js";
|
|
9
9
|
import { qualityGateDefaultRules } from "./rules.js";
|
|
10
|
+
export const filterFailedQualityGateResults = (results) => results.filter(({ success }) => !success);
|
|
11
|
+
const formatQualityGateValue = (value) => (Array.isArray(value) ? value.join(", ") : String(value));
|
|
12
|
+
const defaultSuccessMessage = ({ actual, expected }) => `The rule has been passed; actual ${bold(formatQualityGateValue(actual))}, expected ${bold(formatQualityGateValue(expected))}`;
|
|
10
13
|
export const stringifyQualityGateResults = (results) => {
|
|
11
|
-
|
|
14
|
+
const failedResults = filterFailedQualityGateResults(results);
|
|
15
|
+
if (failedResults.length === 0) {
|
|
12
16
|
return "";
|
|
13
17
|
}
|
|
14
18
|
const lines = [red("Quality Gate failed with following issues:")];
|
|
15
|
-
const maxMessageLength = Math.max(...
|
|
19
|
+
const maxMessageLength = Math.max(...failedResults.map((r) => r.message.length));
|
|
16
20
|
lines.push("");
|
|
17
|
-
|
|
21
|
+
failedResults.forEach((result) => {
|
|
18
22
|
lines.push(` ${red("⨯")} ${result.message.padEnd(maxMessageLength, " ")} ${gray(result.rule)}`);
|
|
19
23
|
});
|
|
20
24
|
lines.push("");
|
|
21
|
-
lines.push(red(`${
|
|
25
|
+
lines.push(red(`${failedResults.length} quality gate rules have been failed.`));
|
|
22
26
|
return lines.join("\n");
|
|
23
27
|
};
|
|
24
28
|
export const convertQualityGateResultsToTestErrors = (results) => {
|
|
25
|
-
return results.map((result) => ({
|
|
29
|
+
return filterFailedQualityGateResults(results).map((result) => ({
|
|
26
30
|
message: `Quality Gate (${result.rule}): ${result.message}`,
|
|
27
31
|
actual: result.actual,
|
|
28
32
|
expected: result.expected,
|
|
@@ -67,7 +71,7 @@ export class QualityGate {
|
|
|
67
71
|
}
|
|
68
72
|
trsToValidateById.set(tr.id, tr);
|
|
69
73
|
}
|
|
70
|
-
const trsToValidate = trsToValidateById.values()
|
|
74
|
+
const trsToValidate = [...trsToValidateById.values()];
|
|
71
75
|
const { rules, use = [...qualityGateDefaultRules] } = this.config;
|
|
72
76
|
const results = [];
|
|
73
77
|
let fastFailed = false;
|
|
@@ -102,21 +106,21 @@ export class QualityGate {
|
|
|
102
106
|
previousHistory,
|
|
103
107
|
environment,
|
|
104
108
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
const messagePayload = {
|
|
110
|
+
actual: result.actual,
|
|
111
|
+
expected,
|
|
112
|
+
};
|
|
108
113
|
results.push({
|
|
109
114
|
...result,
|
|
110
115
|
expected,
|
|
111
116
|
rule: ruleId,
|
|
112
|
-
message:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}),
|
|
117
|
+
message: result.success
|
|
118
|
+
? (rule.successMessage ?? defaultSuccessMessage)(messagePayload)
|
|
119
|
+
: rule.message(messagePayload),
|
|
116
120
|
environment,
|
|
117
121
|
testResults: [...new Set([...(state?.getTestResults(ruleId) ?? []), ...result.testResults])],
|
|
118
122
|
});
|
|
119
|
-
if (ruleset.fastFail) {
|
|
123
|
+
if (!result.success && ruleset.fastFail) {
|
|
120
124
|
fastFailed = true;
|
|
121
125
|
break;
|
|
122
126
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { filterSuccessful, filterUnsuccessful } from "@allurereport/core-api";
|
|
1
|
+
import { filterIncludedInSuccessRate, filterSuccessful, filterUnsuccessful, getSuccessRate, } from "@allurereport/core-api";
|
|
2
2
|
import { bold } from "yoctocolors";
|
|
3
3
|
const numberStateValue = (value) => (Number.isFinite(value) ? Number(value) : 0);
|
|
4
4
|
const stringArrayStateValue = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
@@ -41,12 +41,17 @@ const metricActual = (metrics, key, value) => {
|
|
|
41
41
|
...(sample?.unit ? { unit: sample.unit } : {}),
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
|
+
const formatQualityGateNumber = (value) => {
|
|
45
|
+
if (!Number.isFinite(value)) {
|
|
46
|
+
return String(value);
|
|
47
|
+
}
|
|
48
|
+
return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(3)));
|
|
49
|
+
};
|
|
44
50
|
const formatMetricValue = (value, unit) => {
|
|
45
51
|
if (!Number.isFinite(value)) {
|
|
46
52
|
return "n/a";
|
|
47
53
|
}
|
|
48
|
-
const
|
|
49
|
-
const formatted = Number.isInteger(finiteValue) ? String(finiteValue) : String(Number(finiteValue.toFixed(3)));
|
|
54
|
+
const formatted = formatQualityGateNumber(value);
|
|
50
55
|
return unit ? `${formatted} ${unit}` : formatted;
|
|
51
56
|
};
|
|
52
57
|
const metricRuleTitle = (actual) => actual.title ?? actual.key;
|
|
@@ -73,6 +78,7 @@ const metricDeltaWithinThreshold = (delta, threshold, better) => {
|
|
|
73
78
|
export const maxFailuresRule = {
|
|
74
79
|
rule: "maxFailures",
|
|
75
80
|
message: ({ actual, expected }) => `The number of failed tests ${bold(String(actual))} exceeds the allowed threshold value ${bold(String(expected))}`,
|
|
81
|
+
successMessage: ({ actual, expected }) => `The number of failed tests ${bold(String(actual))} is within the allowed threshold value ${bold(String(expected))}`,
|
|
76
82
|
validate: async ({ trs, expected, state }) => {
|
|
77
83
|
const previous = numberStateValue(state.getResult());
|
|
78
84
|
const failedTrs = trs.filter(filterUnsuccessful);
|
|
@@ -89,6 +95,7 @@ export const maxFailuresRule = {
|
|
|
89
95
|
export const minTestsCountRule = {
|
|
90
96
|
rule: "minTestsCount",
|
|
91
97
|
message: ({ actual, expected }) => `The total number of tests ${bold(String(actual))} is less than the expected threshold value ${bold(String(expected))}`,
|
|
98
|
+
successMessage: ({ actual, expected }) => `The total number of tests ${bold(String(actual))} meets the expected threshold value ${bold(String(expected))}`,
|
|
92
99
|
validate: async ({ trs, expected, state }) => {
|
|
93
100
|
const actual = trs.length + numberStateValue(state.getResult());
|
|
94
101
|
state.setResult(actual, []);
|
|
@@ -101,15 +108,17 @@ export const minTestsCountRule = {
|
|
|
101
108
|
};
|
|
102
109
|
export const successRateRule = {
|
|
103
110
|
rule: "successRate",
|
|
104
|
-
message: ({ actual, expected }) => `Success rate ${bold(
|
|
111
|
+
message: ({ actual, expected }) => `Success rate ${bold(formatQualityGateNumber(actual))} is less, than expected ${bold(formatQualityGateNumber(expected))}`,
|
|
112
|
+
successMessage: ({ actual, expected }) => `Success rate ${bold(formatQualityGateNumber(actual))} is not less, than expected ${bold(formatQualityGateNumber(expected))}`,
|
|
105
113
|
validate: async ({ trs, expected, state }) => {
|
|
106
114
|
const previous = successRateStateValue(state.getResult());
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const
|
|
115
|
+
const eligibleTrs = trs.filter(filterIncludedInSuccessRate);
|
|
116
|
+
const passedTrs = eligibleTrs.filter(filterSuccessful);
|
|
117
|
+
const notPassedTrs = eligibleTrs.filter(filterUnsuccessful);
|
|
118
|
+
const totalCount = previous.totalCount + eligibleTrs.length;
|
|
110
119
|
const passedCount = previous.passedCount + passedTrs.length;
|
|
111
120
|
const testResults = notPassedTrs.map((tr) => tr.id);
|
|
112
|
-
const rate =
|
|
121
|
+
const rate = getSuccessRate({ passed: passedCount, failed: totalCount - passedCount });
|
|
113
122
|
state.setResult({ totalCount, passedCount }, testResults);
|
|
114
123
|
return {
|
|
115
124
|
success: rate >= expected,
|
|
@@ -121,6 +130,7 @@ export const successRateRule = {
|
|
|
121
130
|
export const maxDurationRule = {
|
|
122
131
|
rule: "maxDuration",
|
|
123
132
|
message: ({ actual, expected }) => `Maximum duration of some tests exceed the defined limit; actual ${bold(String(actual))}, expected ${bold(String(expected))}`,
|
|
133
|
+
successMessage: ({ actual, expected }) => `Maximum duration of the tests is within the defined limit; actual ${bold(String(actual))}, expected ${bold(String(expected))}`,
|
|
124
134
|
validate: async ({ trs, expected, state }) => {
|
|
125
135
|
const previous = numberStateValue(state.getResult());
|
|
126
136
|
const actual = Math.max(previous, ...trs.map((tr) => tr.duration ?? 0));
|
|
@@ -137,6 +147,7 @@ export const maxDurationRule = {
|
|
|
137
147
|
export const allTestsContainEnvRule = {
|
|
138
148
|
rule: "allTestsContainEnv",
|
|
139
149
|
message: ({ actual, expected }) => `Not all tests contain the required "${bold(expected)}" environment, ${bold(String(actual))} tests have different or missing environment`,
|
|
150
|
+
successMessage: ({ expected }) => `All tests contain the required "${bold(expected)}" environment`,
|
|
140
151
|
validate: async ({ trs, expected, state }) => {
|
|
141
152
|
const previous = numberStateValue(state.getResult());
|
|
142
153
|
const testsWithoutEnv = trs.filter((tr) => (tr.environment ?? "") !== expected);
|
|
@@ -153,6 +164,7 @@ export const allTestsContainEnvRule = {
|
|
|
153
164
|
export const environmentsTestedRule = {
|
|
154
165
|
rule: "environmentsTested",
|
|
155
166
|
message: ({ actual, expected }) => `The following environments were not tested: "${actual.join('", "')}"; expected all of: "${expected.join('", "')}"`,
|
|
167
|
+
successMessage: ({ expected }) => `All expected environments were tested: "${expected.join('", "')}"`,
|
|
156
168
|
validate: async ({ trs, expected, state }) => {
|
|
157
169
|
const previouslyTested = new Set(stringArrayStateValue(state.getResult()));
|
|
158
170
|
const batchTested = trs.map((tr) => tr.environment).filter((env) => env != null && env !== "");
|