@allurereport/core 3.2.0 → 3.4.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 CHANGED
@@ -1,4 +1,4 @@
1
- import type { DefaultLabelsConfig, EnvironmentsConfig, KnownTestFailure, ReportVariables } from "@allurereport/core-api";
1
+ import type { CategoriesConfig, DefaultLabelsConfig, EnvironmentsConfig, KnownTestFailure, ReportVariables } from "@allurereport/core-api";
2
2
  import type { Plugin, QualityGateConfig, ReportFiles } from "@allurereport/plugin-api";
3
3
  import type { ResultsReader } from "@allurereport/reader-api";
4
4
  export interface PluginInstance {
@@ -12,11 +12,12 @@ export interface FullConfig {
12
12
  output: string;
13
13
  open: boolean;
14
14
  port: string | undefined;
15
+ hideLabels?: (string | RegExp)[];
15
16
  historyPath?: string;
16
17
  historyLimit?: number;
17
18
  knownIssuesPath: string;
18
19
  defaultLabels?: DefaultLabelsConfig;
19
- stage?: string;
20
+ dump?: string;
20
21
  environment?: string;
21
22
  environments?: EnvironmentsConfig;
22
23
  variables?: ReportVariables;
@@ -27,6 +28,7 @@ export interface FullConfig {
27
28
  known?: KnownTestFailure[];
28
29
  realTime?: any;
29
30
  qualityGate?: QualityGateConfig;
31
+ categories?: CategoriesConfig;
30
32
  allureService?: {
31
33
  accessToken?: string;
32
34
  };
package/dist/config.d.ts CHANGED
@@ -5,11 +5,13 @@ export interface ConfigOverride {
5
5
  output?: Config["output"];
6
6
  open?: Config["open"];
7
7
  port?: Config["port"];
8
+ hideLabels?: Config["hideLabels"];
8
9
  historyPath?: Config["historyPath"];
9
10
  historyLimit?: Config["historyLimit"];
10
11
  knownIssuesPath?: Config["knownIssuesPath"];
11
12
  plugins?: Config["plugins"];
12
13
  }
14
+ export declare const assertValidPluginId: (id: string) => void;
13
15
  export declare const getPluginId: (key: string) => string;
14
16
  export declare const findConfig: (cwd: string, configPath?: string) => Promise<string | undefined>;
15
17
  export declare const validateConfig: (config: Config) => {
package/dist/config.js CHANGED
@@ -2,11 +2,14 @@ import * as console from "node:console";
2
2
  import { readFile, stat } from "node:fs/promises";
3
3
  import { extname, resolve } from "node:path";
4
4
  import * as process from "node:process";
5
+ import { validateEnvironmentName } from "@allurereport/core-api";
5
6
  import { parse } from "yaml";
6
7
  import { readKnownIssues } from "./known.js";
7
8
  import { FileSystemReportFiles } from "./plugin.js";
9
+ import { environmentIdentityById, environmentIdentityByName, normalizeEnvironmentDescriptorMap, } from "./utils/environment.js";
8
10
  import { importWrapper } from "./utils/module.js";
9
11
  import { normalizeImportPath } from "./utils/path.js";
12
+ import { assertValidPluginIdForWindows, isWindows } from "./utils/windows.js";
10
13
  const CONFIG_FILENAMES = [
11
14
  "allurerc.js",
12
15
  "allurerc.mjs",
@@ -16,8 +19,31 @@ const CONFIG_FILENAMES = [
16
19
  "allurerc.yml",
17
20
  ];
18
21
  const DEFAULT_CONFIG = {};
22
+ export const assertValidPluginId = (id) => {
23
+ if (id.length === 0) {
24
+ throw new Error("Invalid plugin id: must not be empty");
25
+ }
26
+ if (id === "." || id === "..") {
27
+ throw new Error(`Invalid plugin id ${JSON.stringify(id)}: must not be "." or ".."`);
28
+ }
29
+ if (id.includes("..")) {
30
+ throw new Error(`Invalid plugin id ${JSON.stringify(id)}: must not contain ".."`);
31
+ }
32
+ if (/[/\\]/.test(id)) {
33
+ throw new Error(`Invalid plugin id ${JSON.stringify(id)}: must not contain path separators`);
34
+ }
35
+ if (isWindows()) {
36
+ assertValidPluginIdForWindows(id);
37
+ }
38
+ };
19
39
  export const getPluginId = (key) => {
20
- return key.replace(/^@.*\//, "").replace(/[/\\]/g, "-");
40
+ const trimmed = key.trim();
41
+ if (trimmed.length === 0) {
42
+ throw new Error(`Invalid plugin key ${JSON.stringify(key)}: must not be empty or whitespace-only`);
43
+ }
44
+ const id = trimmed.replace(/^@.*\//, "").replace(/[/\\]/g, "-");
45
+ assertValidPluginId(id);
46
+ return id;
21
47
  };
22
48
  export const findConfig = async (cwd, configPath) => {
23
49
  if (configPath) {
@@ -51,16 +77,19 @@ export const validateConfig = (config) => {
51
77
  "output",
52
78
  "open",
53
79
  "port",
80
+ "hideLabels",
54
81
  "historyPath",
55
82
  "historyLimit",
56
83
  "knownIssuesPath",
57
84
  "plugins",
58
85
  "defaultLabels",
59
86
  "variables",
87
+ "environment",
60
88
  "environments",
61
89
  "appendHistory",
62
90
  "qualityGate",
63
91
  "allureService",
92
+ "categories",
64
93
  ];
65
94
  const unsupportedFields = Object.keys(config).filter((key) => !supportedFields.includes(key));
66
95
  return {
@@ -97,14 +126,42 @@ export const loadJsonConfig = async (configPath) => {
97
126
  export const loadJsConfig = async (configPath) => {
98
127
  return (await import(normalizeImportPath(configPath))).default;
99
128
  };
129
+ const resolveConfigEnvironments = (config) => {
130
+ const errors = [];
131
+ const { normalized: environments, errors: environmentErrors } = normalizeEnvironmentDescriptorMap(config.environments ?? {}, "config.environments");
132
+ let environment;
133
+ errors.push(...environmentErrors);
134
+ if (config.environment !== undefined) {
135
+ const environmentResult = validateEnvironmentName(config.environment);
136
+ if (!environmentResult.valid) {
137
+ errors.push(`environment ${environmentResult.reason}`);
138
+ }
139
+ else {
140
+ const normalizedEnvironment = environmentResult.normalized;
141
+ environment =
142
+ environmentIdentityById(environments, normalizedEnvironment)?.id ??
143
+ environmentIdentityByName(environments, normalizedEnvironment)?.id ??
144
+ normalizedEnvironment;
145
+ }
146
+ }
147
+ if (errors.length > 0) {
148
+ throw new Error(`The provided Allure config contains invalid environments: ${errors.join("; ")}`);
149
+ }
150
+ return {
151
+ environments,
152
+ environment,
153
+ };
154
+ };
100
155
  export const resolveConfig = async (config, override = {}) => {
101
156
  const validationResult = validateConfig(config);
102
157
  if (!validationResult.valid) {
103
158
  throw new Error(`The provided Allure config contains unsupported fields: ${validationResult.fields.join(", ")}`);
104
159
  }
160
+ const { environments, environment } = resolveConfigEnvironments(config);
105
161
  const name = override.name ?? config.name ?? "Allure Report";
106
162
  const open = override.open ?? config.open ?? false;
107
163
  const port = override.port ?? config.port ?? undefined;
164
+ const hideLabels = override.hideLabels ?? config.hideLabels;
108
165
  const historyPath = override.historyPath ?? config.historyPath;
109
166
  const historyLimit = override.historyLimit ?? config.historyLimit;
110
167
  const appendHistory = config.appendHistory ?? true;
@@ -112,22 +169,24 @@ export const resolveConfig = async (config, override = {}) => {
112
169
  const output = resolve(override.output ?? config.output ?? "./allure-report");
113
170
  const known = await readKnownIssues(knownIssuesPath);
114
171
  const variables = config.variables ?? {};
115
- const environments = config.environments ?? {};
116
- const plugins = Object.keys(override?.plugins ?? config?.plugins ?? {}).length === 0
172
+ const configuredPlugins = override.plugins ?? config.plugins;
173
+ const plugins = Object.keys(configuredPlugins ?? {}).length === 0
117
174
  ? {
118
175
  awesome: {
119
176
  options: {},
120
177
  },
121
178
  }
122
- : config.plugins;
179
+ : configuredPlugins;
123
180
  const pluginInstances = await resolvePlugins(plugins);
124
181
  return {
125
182
  name,
126
183
  output,
127
184
  open,
128
185
  port,
186
+ hideLabels,
129
187
  knownIssuesPath,
130
188
  known,
189
+ environment,
131
190
  variables,
132
191
  environments,
133
192
  appendHistory,
@@ -138,6 +197,7 @@ export const resolveConfig = async (config, override = {}) => {
138
197
  defaultLabels: config.defaultLabels ?? {},
139
198
  qualityGate: config.qualityGate,
140
199
  allureService: config.allureService,
200
+ categories: config.categories,
141
201
  };
142
202
  };
143
203
  export const readConfig = async (cwd = process.cwd(), configPath, override) => {
@@ -165,19 +225,26 @@ export const readConfig = async (cwd = process.cwd(), configPath, override) => {
165
225
  export const getPluginInstance = (config, predicate) => {
166
226
  return config?.plugins?.find(predicate);
167
227
  };
228
+ const isModuleNotFoundError = (err) => {
229
+ return err instanceof Error && "code" in err && err.code === "ERR_MODULE_NOT_FOUND";
230
+ };
168
231
  export const resolvePlugin = async (path) => {
169
232
  if (!path.startsWith("@allurereport/plugin-")) {
170
233
  try {
171
234
  const module = await importWrapper(`@allurereport/plugin-${path}`);
172
235
  return module.default;
173
236
  }
174
- catch (err) { }
237
+ catch (err) {
238
+ if (!isModuleNotFoundError(err)) {
239
+ throw err;
240
+ }
241
+ }
175
242
  }
176
243
  try {
177
244
  const module = await importWrapper(path);
178
245
  return module.default;
179
246
  }
180
- catch (err) {
247
+ catch {
181
248
  throw new Error(`Cannot resolve plugin: ${path}`);
182
249
  }
183
250
  };
package/dist/history.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AllureHistory, HistoryDataPoint, TestCase, TestResult } from "@allurereport/core-api";
1
+ import { type AllureHistory, type HistoryDataPoint, type TestCase, type TestResult } from "@allurereport/core-api";
2
2
  export declare const createHistory: (reportUuid: string, reportName: string | undefined, testCases: TestCase[], testResults: TestResult[], remoteUrl?: string) => HistoryDataPoint;
3
3
  export declare class AllureLocalHistory implements AllureHistory {
4
4
  #private;
package/dist/history.js CHANGED
@@ -3,14 +3,21 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
3
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
4
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
5
  };
6
- var _AllureLocalHistory_openFileToReadIfExists, _AllureLocalHistory_ensureFileOpenedToAppend, _AllureLocalHistory_findFirstEntryAddress, _AllureLocalHistory_throwUnexpectedReadError;
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _AllureLocalHistory_cachedHistory, _AllureLocalHistory_openFileToReadIfExists, _AllureLocalHistory_ensureFileOpenedToAppend, _AllureLocalHistory_findFirstEntryAddress, _AllureLocalHistory_throwUnexpectedReadError;
7
13
  import { once } from "node:events";
8
14
  import { mkdir, open } from "node:fs/promises";
9
15
  import path from "node:path";
10
16
  import readline from "node:readline/promises";
11
17
  import { pipeline } from "node:stream/promises";
18
+ import { normalizeHistoryDataPointUrls, } from "@allurereport/core-api";
12
19
  import { isFileNotFoundError } from "./utils/misc.js";
13
- const createHistoryItems = (testResults) => {
20
+ const createHistoryItems = (testResults, remoteUrl) => {
14
21
  return testResults
15
22
  .filter((tr) => tr.historyId)
16
23
  .map(({ id, name, fullName, environment, historyId, status, error: { message, trace } = {}, start, stop, duration, labels, }) => {
@@ -26,7 +33,7 @@ const createHistoryItems = (testResults) => {
26
33
  stop,
27
34
  duration,
28
35
  labels,
29
- url: "",
36
+ url: remoteUrl,
30
37
  historyId: historyId,
31
38
  reportLinks: [],
32
39
  };
@@ -43,7 +50,7 @@ export const createHistory = (reportUuid, reportName = "Allure Report", testCase
43
50
  name: reportName,
44
51
  timestamp: new Date().getTime(),
45
52
  knownTestCaseIds,
46
- testResults: createHistoryItems(testResults),
53
+ testResults: createHistoryItems(testResults, remoteUrl),
47
54
  metrics: {},
48
55
  url: remoteUrl,
49
56
  };
@@ -51,6 +58,7 @@ export const createHistory = (reportUuid, reportName = "Allure Report", testCase
51
58
  export class AllureLocalHistory {
52
59
  constructor(params) {
53
60
  this.params = params;
61
+ _AllureLocalHistory_cachedHistory.set(this, []);
54
62
  _AllureLocalHistory_openFileToReadIfExists.set(this, async (filePath) => {
55
63
  try {
56
64
  return await open(filePath, "r");
@@ -121,6 +129,9 @@ export class AllureLocalHistory {
121
129
  });
122
130
  }
123
131
  async readHistory() {
132
+ if (__classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f").length > 0) {
133
+ return __classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f");
134
+ }
124
135
  const fullPath = path.resolve(this.params.historyPath);
125
136
  const historyFile = await __classPrivateFieldGet(this, _AllureLocalHistory_openFileToReadIfExists, "f").call(this, fullPath);
126
137
  if (historyFile === undefined) {
@@ -134,18 +145,20 @@ export class AllureLocalHistory {
134
145
  .createInterface({ input: stream, terminal: false, crlfDelay: Infinity })
135
146
  .on("line", (line) => {
136
147
  if (line && line.trim().length) {
137
- const historyEntry = JSON.parse(line);
148
+ const historyEntry = normalizeHistoryDataPointUrls(JSON.parse(line));
138
149
  historyPoints.push(historyEntry);
139
150
  }
140
151
  });
141
152
  await once(readlineInterface, "close");
142
- return historyPoints;
153
+ __classPrivateFieldSet(this, _AllureLocalHistory_cachedHistory, historyPoints, "f");
154
+ return __classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f");
143
155
  }
144
156
  finally {
145
157
  await historyFile.close();
146
158
  }
147
159
  }
148
160
  async appendHistory(data) {
161
+ const normalizedData = normalizeHistoryDataPointUrls(data);
149
162
  const fullPath = path.resolve(this.params.historyPath);
150
163
  const parentDir = path.dirname(fullPath);
151
164
  const { limit } = this.params;
@@ -153,22 +166,33 @@ export class AllureLocalHistory {
153
166
  const { file: historyFile, exists: historyExists } = await __classPrivateFieldGet(this, _AllureLocalHistory_ensureFileOpenedToAppend, "f").call(this, fullPath);
154
167
  try {
155
168
  const dst = historyFile.createWriteStream({ encoding: "utf-8", start: 0, autoClose: false });
156
- if (limit !== 0) {
157
- if (historyExists) {
158
- const start = limit ? await __classPrivateFieldGet(this, _AllureLocalHistory_findFirstEntryAddress, "f").call(this, historyFile, limit - 1) : 0;
159
- const src = historyFile.createReadStream({ start, autoClose: false });
160
- await pipeline(src, dst, { end: false });
161
- }
162
- const sources = [JSON.stringify(data), Buffer.from([0x0a])];
163
- await pipeline(sources, dst);
169
+ if (limit === 0 && historyExists) {
170
+ await historyFile.truncate(0);
171
+ return;
172
+ }
173
+ if (limit === 0 && !historyExists) {
174
+ return;
164
175
  }
176
+ if (historyExists) {
177
+ const start = await __classPrivateFieldGet(this, _AllureLocalHistory_findFirstEntryAddress, "f").call(this, historyFile, limit ? limit - 1 : undefined);
178
+ const src = historyFile.createReadStream({ start, autoClose: false });
179
+ await pipeline(src, dst, { end: false });
180
+ }
181
+ const sources = [JSON.stringify(normalizedData), Buffer.from([0x0a])];
182
+ await pipeline(sources, dst);
165
183
  if (historyExists) {
166
184
  await historyFile.truncate(dst.bytesWritten);
167
185
  }
168
186
  }
169
187
  finally {
170
188
  await historyFile.close();
189
+ if (limit !== 0) {
190
+ __classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f").push(normalizedData);
191
+ }
192
+ if (limit) {
193
+ __classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f").splice(limit);
194
+ }
171
195
  }
172
196
  }
173
197
  }
174
- _AllureLocalHistory_openFileToReadIfExists = new WeakMap(), _AllureLocalHistory_ensureFileOpenedToAppend = new WeakMap(), _AllureLocalHistory_findFirstEntryAddress = new WeakMap(), _AllureLocalHistory_throwUnexpectedReadError = new WeakMap();
198
+ _AllureLocalHistory_cachedHistory = new WeakMap(), _AllureLocalHistory_openFileToReadIfExists = new WeakMap(), _AllureLocalHistory_ensureFileOpenedToAppend = new WeakMap(), _AllureLocalHistory_findFirstEntryAddress = new WeakMap(), _AllureLocalHistory_throwUnexpectedReadError = new WeakMap();
package/dist/index.d.ts CHANGED
@@ -4,9 +4,10 @@ export * from "./utils/crypto.js";
4
4
  export * from "./utils/path.js";
5
5
  export * from "./utils/new.js";
6
6
  export * from "./utils/flaky.js";
7
+ export * from "./utils/environment.js";
7
8
  export * from "./history.js";
8
9
  export * from "./known.js";
9
10
  export { resolveConfig, readConfig, getPluginInstance } from "./config.js";
10
11
  export * from "./report.js";
11
12
  export * from "./plugin.js";
12
- export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
13
+ export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
package/dist/index.js CHANGED
@@ -3,9 +3,10 @@ export * from "./utils/crypto.js";
3
3
  export * from "./utils/path.js";
4
4
  export * from "./utils/new.js";
5
5
  export * from "./utils/flaky.js";
6
+ export * from "./utils/environment.js";
6
7
  export * from "./history.js";
7
8
  export * from "./known.js";
8
9
  export { resolveConfig, readConfig, getPluginInstance } from "./config.js";
9
10
  export * from "./report.js";
10
11
  export * from "./plugin.js";
11
- export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
12
+ export { QualityGateState, qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule, allTestsContainEnvRule, environmentsTestedRule, convertQualityGateResultsToTestErrors, stringifyQualityGateResults, } from "./qualityGate/index.js";
package/dist/plugin.js CHANGED
@@ -13,6 +13,7 @@ var _DefaultPluginState_state, _PluginFiles_parent, _PluginFiles_pluginId, _InMe
13
13
  import { mkdir, writeFile } from "node:fs/promises";
14
14
  import { dirname, resolve } from "node:path";
15
15
  import { join as joinPosix } from "node:path/posix";
16
+ import { resolvePathUnderOutputRoot } from "./utils/safeOutputPath.js";
16
17
  export class DefaultPluginState {
17
18
  constructor(state) {
18
19
  _DefaultPluginState_state.set(this, void 0);
@@ -58,7 +59,7 @@ export class FileSystemReportFiles {
58
59
  constructor(output) {
59
60
  _FileSystemReportFiles_output.set(this, void 0);
60
61
  this.addFile = async (path, data) => {
61
- const targetPath = resolve(__classPrivateFieldGet(this, _FileSystemReportFiles_output, "f"), path);
62
+ const targetPath = resolvePathUnderOutputRoot(__classPrivateFieldGet(this, _FileSystemReportFiles_output, "f"), path);
62
63
  const targetDirPath = dirname(targetPath);
63
64
  await mkdir(targetDirPath, { recursive: true });
64
65
  await writeFile(targetPath, data, { encoding: "utf-8" });
@@ -4,7 +4,6 @@ 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;
7
- import { DEFAULT_ENVIRONMENT } from "@allurereport/core-api";
8
7
  import { gray, red } from "yoctocolors";
9
8
  import { qualityGateDefaultRules } from "./rules.js";
10
9
  export const stringifyQualityGateResults = (results) => {
@@ -77,6 +76,7 @@ export class QualityGate {
77
76
  },
78
77
  expected,
79
78
  knownIssues,
79
+ environment,
80
80
  });
81
81
  if (result.success) {
82
82
  continue;
@@ -89,7 +89,7 @@ export class QualityGate {
89
89
  actual: result.actual,
90
90
  expected,
91
91
  }),
92
- environment: environment || DEFAULT_ENVIRONMENT,
92
+ environment,
93
93
  });
94
94
  if (ruleset.fastFail) {
95
95
  fastFailed = true;
@@ -3,4 +3,6 @@ export declare const maxFailuresRule: QualityGateRule<number>;
3
3
  export declare const minTestsCountRule: QualityGateRule<number>;
4
4
  export declare const successRateRule: QualityGateRule<number>;
5
5
  export declare const maxDurationRule: QualityGateRule<number>;
6
- export declare const qualityGateDefaultRules: QualityGateRule<number>[];
6
+ export declare const allTestsContainEnvRule: QualityGateRule<string>;
7
+ export declare const environmentsTestedRule: QualityGateRule<string[]>;
8
+ export declare const qualityGateDefaultRules: (QualityGateRule<number> | QualityGateRule<string> | QualityGateRule<string[]>)[];
@@ -1,11 +1,11 @@
1
- import { filterSuccessful, filterUnsuccessful } from "@allurereport/core-api";
1
+ import { filterSuccessful, filterUnknownByKnownIssues, filterUnsuccessful } from "@allurereport/core-api";
2
2
  import { bold } from "yoctocolors";
3
3
  export const maxFailuresRule = {
4
4
  rule: "maxFailures",
5
5
  message: ({ actual, expected }) => `The number of failed tests ${bold(String(actual))} exceeds the allowed threshold value ${bold(String(expected))}`,
6
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));
7
+ const knownIssuesHistoryIds = new Set(knownIssues.map(({ historyId }) => historyId));
8
+ const unknown = filterUnknownByKnownIssues(trs, knownIssuesHistoryIds);
9
9
  const failedTrs = unknown.filter(filterUnsuccessful);
10
10
  const actual = failedTrs.length + (state.getResult() ?? 0);
11
11
  state.setResult(actual);
@@ -31,8 +31,8 @@ export const successRateRule = {
31
31
  rule: "successRate",
32
32
  message: ({ actual, expected }) => `Success rate ${bold(String(actual))} is less, than expected ${bold(String(expected))}`,
33
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));
34
+ const knownIssuesHistoryIds = new Set(knownIssues.map(({ historyId }) => historyId));
35
+ const unknown = filterUnknownByKnownIssues(trs, knownIssuesHistoryIds);
36
36
  const passedTrs = unknown.filter(filterSuccessful);
37
37
  const rate = passedTrs.length === 0 ? 0 : passedTrs.length / unknown.length;
38
38
  return {
@@ -52,4 +52,38 @@ export const maxDurationRule = {
52
52
  };
53
53
  },
54
54
  };
55
- export const qualityGateDefaultRules = [maxFailuresRule, minTestsCountRule, successRateRule, maxDurationRule];
55
+ export const allTestsContainEnvRule = {
56
+ rule: "allTestsContainEnv",
57
+ message: ({ actual, expected }) => `Not all tests contain the required environment, ${bold(`"${expected}"`)}; ${bold(actual.length === 1 ? "one" : String(actual))} ${actual.length === 1 ? "test has" : "tests have"} different or missing environment`,
58
+ validate: async ({ trs, expected }) => {
59
+ const testsWithoutEnv = trs.filter((tr) => (tr.environment ?? "") !== expected);
60
+ const actual = testsWithoutEnv.length;
61
+ return {
62
+ success: actual === 0,
63
+ actual,
64
+ };
65
+ },
66
+ };
67
+ export const environmentsTestedRule = {
68
+ rule: "environmentsTested",
69
+ message: ({ actual, expected }) => `The following environments were not tested: "${actual.join('", "')}"; expected all of: "${expected.join('", "')}"`,
70
+ validate: async ({ trs, expected, state }) => {
71
+ const previouslyTested = new Set(state.getResult() ?? []);
72
+ const batchTested = trs.map((tr) => tr.environment).filter((env) => env != null && env !== "");
73
+ const testedEnvs = new Set([...previouslyTested, ...batchTested]);
74
+ state.setResult([...testedEnvs]);
75
+ const missing = expected.filter((env) => !testedEnvs.has(env));
76
+ return {
77
+ success: missing.length === 0,
78
+ actual: missing,
79
+ };
80
+ },
81
+ };
82
+ export const qualityGateDefaultRules = [
83
+ maxFailuresRule,
84
+ minTestsCountRule,
85
+ successRateRule,
86
+ maxDurationRule,
87
+ allTestsContainEnvRule,
88
+ environmentsTestedRule,
89
+ ];
package/dist/report.d.ts CHANGED
@@ -27,6 +27,6 @@ export declare class AllureReport {
27
27
  }>;
28
28
  start: () => Promise<void>;
29
29
  dumpState: () => Promise<void>;
30
- restoreState: (stages: string[]) => Promise<void>;
30
+ restoreState: (dumps: string[]) => Promise<void>;
31
31
  done: () => Promise<void>;
32
32
  }