@allurereport/core 3.21.0 → 3.22.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.js CHANGED
@@ -190,10 +190,13 @@ const validateFlakyDetectionConfig = (config) => {
190
190
  if (config === undefined) {
191
191
  return;
192
192
  }
193
- const { historyDepth, includePassedTests } = config;
193
+ const { historyDepth, includePassedTests, stabilizationPeriod } = config;
194
194
  if (historyDepth !== undefined && (!Number.isInteger(historyDepth) || historyDepth < -1)) {
195
195
  throw new Error("flakyDetection.historyDepth must be an integer greater than or equal to -1");
196
196
  }
197
+ if (stabilizationPeriod !== undefined && (!Number.isInteger(stabilizationPeriod) || stabilizationPeriod < 1)) {
198
+ throw new Error("flakyDetection.stabilizationPeriod must be a positive integer");
199
+ }
197
200
  if (includePassedTests !== undefined && typeof includePassedTests !== "boolean") {
198
201
  throw new TypeError("flakyDetection.includePassedTests must be a boolean");
199
202
  }
package/dist/history.js CHANGED
@@ -93,16 +93,6 @@ const closeStream = async (stream) => {
93
93
  stream.destroy();
94
94
  });
95
95
  };
96
- const closeFileHandle = async (file) => {
97
- try {
98
- await file.close();
99
- }
100
- catch (err) {
101
- if (err?.code !== "EBADF") {
102
- throw err;
103
- }
104
- }
105
- };
106
96
  export class AllureLocalHistory {
107
97
  constructor(params) {
108
98
  this.params = params;
@@ -221,7 +211,7 @@ export class AllureLocalHistory {
221
211
  }
222
212
  finally {
223
213
  await __classPrivateFieldGet(this, _AllureLocalHistory_closeStreams, "f").call(this, openedStreams);
224
- await closeFileHandle(historyFile);
214
+ await historyFile.close();
225
215
  }
226
216
  }
227
217
  async appendHistory(data) {
@@ -255,7 +245,7 @@ export class AllureLocalHistory {
255
245
  }
256
246
  finally {
257
247
  await __classPrivateFieldGet(this, _AllureLocalHistory_closeStreams, "f").call(this, openedStreams);
258
- await closeFileHandle(historyFile);
248
+ await historyFile.close();
259
249
  if (limit !== 0) {
260
250
  __classPrivateFieldGet(this, _AllureLocalHistory_cachedHistory, "f").push(normalizedData);
261
251
  }
@@ -65,8 +65,9 @@ export class QualityGate {
65
65
  async validate(payload) {
66
66
  const { state, trs, metrics = [], previousHistory = [], environment } = payload;
67
67
  const trsToValidateById = new Map();
68
+ const resolutionEvaluatedAt = Date.now();
68
69
  for (const tr of trs) {
69
- if (tr.isRetry || isIgnoredFailure(tr) || state?.isTestResultProcessed(tr.id)) {
70
+ if (tr.isRetry || isIgnoredFailure(tr, resolutionEvaluatedAt) || state?.isTestResultProcessed(tr.id)) {
70
71
  continue;
71
72
  }
72
73
  trsToValidateById.set(tr.id, tr);
package/dist/report.js CHANGED
@@ -18,11 +18,11 @@ import { lstat, mkdtemp, readdir, rename, rm, writeFile } from "node:fs/promises
18
18
  import { tmpdir } from "node:os";
19
19
  import { basename, dirname, join, resolve } from "node:path";
20
20
  import { promisify } from "node:util";
21
- import { detect } from "@allurereport/ci";
21
+ import { createReportCiContext, detect } from "@allurereport/ci";
22
22
  import { createProgressLogger } from "@allurereport/cli-commons";
23
23
  import { normalizeCategoriesConfig, parseIntegerConfigValue } from "@allurereport/core-api";
24
24
  import { AllureStoreDumpFiles, createTestResultRegistry, } from "@allurereport/plugin-api";
25
- import { allure1, allure2, attachments, cucumberjson, cucumberMessages, junitXml, readXcResultBundle, } from "@allurereport/reader";
25
+ import { allure1, allure2, attachments, cucumberjson, cucumberMessages, junitXml, openTestReporting, readXcResultBundle, } from "@allurereport/reader";
26
26
  import { PathResultFile } from "@allurereport/reader-api";
27
27
  import { AllureRemoteHistory, AllureServiceClient, AllureTestOpsClient, KnownError, UnknownError, } from "@allurereport/service";
28
28
  import { generateSummary } from "@allurereport/summary";
@@ -898,6 +898,7 @@ export class AllureReport {
898
898
  });
899
899
  __classPrivateFieldGet(this, _AllureReport_finishGeneratePerfSpan, "f").call(this);
900
900
  await __classPrivateFieldGet(this, _AllureReport_ingestSelfPerfMetrics, "f").call(this);
901
+ const ciContext = createReportCiContext(__classPrivateFieldGet(this, _AllureReport_ci, "f"));
901
902
  await __classPrivateFieldGet(this, _AllureReport_eachPlugin, "f").call(this, false, async (plugin, context) => {
902
903
  const summary = await plugin?.info?.(context, __classPrivateFieldGet(this, _AllureReport_store, "f"));
903
904
  if (!summary) {
@@ -906,6 +907,7 @@ export class AllureReport {
906
907
  summary.pluginId = context.id;
907
908
  summary.pullRequestHref = __classPrivateFieldGet(this, _AllureReport_ci, "f")?.pullRequestUrl;
908
909
  summary.jobHref = __classPrivateFieldGet(this, _AllureReport_ci, "f")?.jobRunUrl;
910
+ Object.assign(summary, ciContext);
909
911
  summaries.push({
910
912
  ...summary,
911
913
  href: `${context.id}/`,
@@ -1060,7 +1062,7 @@ export class AllureReport {
1060
1062
  }
1061
1063
  }
1062
1064
  });
1063
- const { name, readers = [allure1, allure2, cucumberjson, cucumberMessages, junitXml, attachments], plugins = [], resolutions, reportFiles, realTime, historyPath, historyBaseUrl, historyLimit, flakyDetection, appendHistory, defaultLabels = {}, variables = {}, environment, allowedEnvironments, environments, output, hideLabels, qualityGate, performance, dump, categories, allureService, globalAttachments, cwd, } = opts;
1065
+ const { name, readers = [allure1, allure2, cucumberjson, cucumberMessages, openTestReporting, junitXml, attachments], plugins = [], resolutions, reportFiles, realTime, historyPath, historyBaseUrl, historyLimit, flakyDetection, appendHistory, defaultLabels = {}, variables = {}, environment, allowedEnvironments, environments, output, hideLabels, qualityGate, performance, dump, categories, allureService, globalAttachments, cwd, } = opts;
1064
1066
  const allureServiceAccessToken = allureService?.accessToken;
1065
1067
  if (allureServiceAccessToken) {
1066
1068
  const allureServiceClientConfig = {
@@ -11,7 +11,7 @@ export declare const KNOWN_FROM_RESULT_RESOLUTION: {
11
11
  readonly comment: "Accepted from result (known)";
12
12
  };
13
13
  export declare const getResolutionByRules: (testResult: TestResult, config?: ResolutionsConfig) => ResolutionRule | undefined;
14
- export declare const isIgnoredFailure: (testResult: TestResult) => boolean;
14
+ export declare const isIgnoredFailure: (testResult: TestResult, now?: number) => boolean;
15
15
  export declare const createResolutionIssue: (rule: IssueResolutionRule, config?: ResolutionsConfig) => ResolutionIssue;
16
16
  export declare const resolveExactIssuesFilePath: (pathOrDir: string | undefined, label: string) => Promise<string | undefined>;
17
17
  export declare const writeKnownIssues: (store: AllureStore, knownIssuesPath?: string) => Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import { mkdir, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, extname, resolve } from "node:path";
3
+ import { isIgnoredResolution, parseResolutionExpiresAt } from "@allurereport/core-api";
3
4
  import { isFileNotFoundError } from "./utils/misc.js";
4
5
  export const DEFAULT_KNOWN_ISSUES_PATH = "known-issues.json";
5
6
  const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0;
@@ -82,6 +83,14 @@ const validateIgnoredRule = (rule, path, errors) => {
82
83
  if (!nonEmpty(rule.comment)) {
83
84
  errors.push(`${path}.comment must be a non-empty string`);
84
85
  }
86
+ if (rule.resolution === "accepted" && rule.expiresAt !== undefined) {
87
+ errors.push(`${path}.expiresAt is only supported for muted resolutions`);
88
+ }
89
+ if (rule.resolution === "muted" &&
90
+ rule.expiresAt !== undefined &&
91
+ (!nonEmpty(rule.expiresAt) || parseResolutionExpiresAt(rule.expiresAt) === undefined)) {
92
+ errors.push(`${path}.expiresAt must be a JavaScript-compatible ISO 8601 timestamp with an explicit timezone`);
93
+ }
85
94
  };
86
95
  export const validateResolutionsConfig = (config) => {
87
96
  if (!config) {
@@ -154,7 +163,7 @@ export const getResolutionByRules = (testResult, config) => {
154
163
  }
155
164
  return undefined;
156
165
  };
157
- export const isIgnoredFailure = (testResult) => testResult.resolution === "muted" || testResult.resolution === "accepted";
166
+ export const isIgnoredFailure = (testResult, now = Date.now()) => isIgnoredResolution(testResult, now);
158
167
  export const createResolutionIssue = (rule, config) => {
159
168
  const template = config?.links?.[rule.issue.type];
160
169
  const issue = { ...rule.issue, comment: rule.comment };
@@ -723,7 +723,8 @@ export class DefaultAllureStore {
723
723
  }
724
724
  async blockingFailedTestResults() {
725
725
  const failedTestResults = await this.failedTestResults();
726
- return failedTestResults.filter((tr) => !isIgnoredFailure(tr));
726
+ const resolutionEvaluatedAt = Date.now();
727
+ return failedTestResults.filter((tr) => !isIgnoredFailure(tr, resolutionEvaluatedAt));
727
728
  }
728
729
  async unknownFailedTestResults() {
729
730
  return this.blockingFailedTestResults();
@@ -943,6 +944,7 @@ export class DefaultAllureStore {
943
944
  for (const testResult of __classPrivateFieldGet(this, _DefaultAllureStore_testResults, "f").values()) {
944
945
  delete testResult.resolution;
945
946
  delete testResult.resolutionComment;
947
+ delete testResult.resolutionExpiresAt;
946
948
  }
947
949
  const normalizedEnvironments = environments
948
950
  .map((environmentValue) => {
@@ -993,6 +995,7 @@ export class DefaultAllureStore {
993
995
  __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_removeResolutionIssueAssociation).call(this, testResult.id);
994
996
  delete testResult.resolution;
995
997
  delete testResult.resolutionComment;
998
+ delete testResult.resolutionExpiresAt;
996
999
  __classPrivateFieldGet(this, _DefaultAllureStore_testResults, "f").set(testResult.id, testResult);
997
1000
  const envId = __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_environmentIdByTestResult).call(this, testResult);
998
1001
  __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_assertAllowedEnvironmentId).call(this, envId, `restored testResults[${JSON.stringify(testResult.id)}]`);
@@ -1202,6 +1205,10 @@ _DefaultAllureStore_testResults = new WeakMap(), _DefaultAllureStore_attachments
1202
1205
  __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_removeResolutionIssueAssociation).call(this, testResult.id);
1203
1206
  testResult.resolution = rule.resolution;
1204
1207
  testResult.resolutionComment = rule.comment;
1208
+ delete testResult.resolutionExpiresAt;
1209
+ if (rule.resolution === "muted" && rule.expiresAt !== undefined) {
1210
+ testResult.resolutionExpiresAt = rule.expiresAt;
1211
+ }
1205
1212
  if (rule.resolution === "issue") {
1206
1213
  __classPrivateFieldGet(this, _DefaultAllureStore_instances, "m", _DefaultAllureStore_associateResolutionIssue).call(this, createResolutionIssue(rule, __classPrivateFieldGet(this, _DefaultAllureStore_resolutionsConfig, "f")), testResult.id);
1207
1214
  }
@@ -1302,13 +1309,11 @@ _DefaultAllureStore_testResults = new WeakMap(), _DefaultAllureStore_attachments
1302
1309
  }, _DefaultAllureStore_getHistoryLookup = function _DefaultAllureStore_getHistoryLookup() {
1303
1310
  return (__classPrivateFieldSet(this, _DefaultAllureStore_historyLookup, __classPrivateFieldGet(this, _DefaultAllureStore_historyLookup, "f") ?? createHistoryTestResultLookup(__classPrivateFieldGet(this, _DefaultAllureStore_testResults, "f").values()), "f"));
1304
1311
  }, _DefaultAllureStore_historyFor = function _DefaultAllureStore_historyFor(result, lookup) {
1305
- return __classPrivateFieldGet(this, _DefaultAllureStore_historyPoints, "f").flatMap((point) => {
1306
- const historicalResult = lookup(point, result);
1307
- return historicalResult ? [historicalResult] : [];
1308
- });
1312
+ return __classPrivateFieldGet(this, _DefaultAllureStore_historyPoints, "f").map((point) => lookup(point, result));
1309
1313
  }, _DefaultAllureStore_applyHistoryFlags = function _DefaultAllureStore_applyHistoryFlags(result, history) {
1310
1314
  result.flaky = result.sourceMetadata?.reportedFlaky === true || __classPrivateFieldGet(this, _DefaultAllureStore_detectFlaky, "f").call(this, result, history);
1311
- result.transition = getStatusTransition(result, history);
1315
+ const resolvedHistory = history.filter((entry) => entry !== undefined);
1316
+ result.transition = getStatusTransition(result, resolvedHistory);
1312
1317
  }, _DefaultAllureStore_restoreAttachmentContent = function _DefaultAllureStore_restoreAttachmentContent(id, content) {
1313
1318
  const attachment = __classPrivateFieldGet(this, _DefaultAllureStore_attachments, "f").get(id);
1314
1319
  __classPrivateFieldGet(this, _DefaultAllureStore_attachmentContents, "f").set(id, content);
@@ -1,2 +1,2 @@
1
- import { type FlakyDetectionConfig, type HistoryTestResult, type TestResult } from "@allurereport/core-api";
2
- export declare const createFlakyDetector: ({ historyDepth, includePassedTests, }?: FlakyDetectionConfig) => ((tr: TestResult, history: HistoryTestResult[]) => boolean);
1
+ import { type HistoryTestResult, type FlakyDetectionConfig, type TestResult } from "@allurereport/core-api";
2
+ export declare const createFlakyDetector: ({ includePassedTests, historyDepth, stabilizationPeriod }?: FlakyDetectionConfig) => (tr: TestResult, history: (HistoryTestResult | undefined)[]) => boolean;
@@ -1,40 +1,7 @@
1
- import { DEFAULT_ENVIRONMENT, } from "@allurereport/core-api";
2
- const DEFAULT_HISTORY_DEPTH = 5;
3
- const TRANSITION_HALF_LIFE = 2;
4
- const STABILITY_PRIOR_WEIGHT = 3;
5
- const FLAKY_SCORE_THRESHOLD = 30;
6
- const badStatuses = ["failed", "broken"];
7
- const isFlakyByWeightedTransitions = (tr, history, historyDepth, includePassedTests) => {
8
- const currentPassed = tr.status === "passed";
9
- const isEligibleStatus = badStatuses.includes(tr.status) || (includePassedTests && currentPassed);
10
- if (historyDepth === -1 || !isEligibleStatus) {
1
+ import { getTestFlakiness, } from "@allurereport/core-api";
2
+ export const createFlakyDetector = ({ includePassedTests = false, historyDepth, stabilizationPeriod } = {}) => (tr, history) => {
3
+ if (tr.status !== "failed" && tr.status !== "broken" && !(includePassedTests && tr.status === "passed")) {
11
4
  return false;
12
5
  }
13
- let previousPassed = currentPassed;
14
- let comparisons = 0;
15
- let weightedTransitions = 0;
16
- let totalWeight = 0;
17
- for (const result of history) {
18
- if (historyDepth !== 0 && comparisons >= historyDepth) {
19
- break;
20
- }
21
- if (result.id === tr.id ||
22
- (result.environment ?? DEFAULT_ENVIRONMENT) !== (tr.environment ?? DEFAULT_ENVIRONMENT) ||
23
- (result.status !== "passed" && !badStatuses.includes(result.status))) {
24
- continue;
25
- }
26
- const passed = result.status === "passed";
27
- const weight = 2 ** (-comparisons / TRANSITION_HALF_LIFE);
28
- if (passed !== previousPassed) {
29
- weightedTransitions += weight;
30
- }
31
- totalWeight += weight;
32
- previousPassed = passed;
33
- comparisons++;
34
- }
35
- const score = (100 * weightedTransitions) / (STABILITY_PRIOR_WEIGHT + totalWeight);
36
- return score >= FLAKY_SCORE_THRESHOLD;
37
- };
38
- export const createFlakyDetector = ({ historyDepth = DEFAULT_HISTORY_DEPTH, includePassedTests = false, } = {}) => {
39
- return (tr, history) => isFlakyByWeightedTransitions(tr, history, historyDepth, includePassedTests);
6
+ return getTestFlakiness(tr, history, { historyDepth, stabilizationPeriod }) === true;
40
7
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allurereport/core",
3
- "version": "3.21.0",
3
+ "version": "3.22.0",
4
4
  "description": "Collection of generic Allure utilities used across the entire project",
5
5
  "keywords": [
6
6
  "allure"
@@ -20,34 +20,34 @@
20
20
  "scripts": {
21
21
  "build": "run clean && tsc --project ./tsconfig.json",
22
22
  "clean": "rimraf ./dist",
23
- "test": "vitest run",
23
+ "test": "rimraf out && vp test run",
24
24
  "test:unit": "yarn test",
25
- "lint": "oxlint --import-plugin src test features stories",
26
- "lint:fix": "oxlint --import-plugin --fix src test features stories"
25
+ "lint": "vp lint --import-plugin src test features stories",
26
+ "lint:fix": "vp lint --import-plugin --fix src test features stories"
27
27
  },
28
28
  "dependencies": {
29
- "@allurereport/ci": "3.21.0",
30
- "@allurereport/cli-commons": "3.21.0",
31
- "@allurereport/core-api": "3.21.0",
32
- "@allurereport/plugin-agent": "3.21.0",
33
- "@allurereport/plugin-allure2": "3.21.0",
34
- "@allurereport/plugin-api": "3.21.0",
35
- "@allurereport/plugin-awesome": "3.21.0",
36
- "@allurereport/plugin-classic": "3.21.0",
37
- "@allurereport/plugin-csv": "3.21.0",
38
- "@allurereport/plugin-dashboard": "3.21.0",
39
- "@allurereport/plugin-jira": "3.21.0",
40
- "@allurereport/plugin-log": "3.21.0",
41
- "@allurereport/plugin-metrics": "3.21.0",
42
- "@allurereport/plugin-progress": "3.21.0",
43
- "@allurereport/plugin-slack": "3.21.0",
44
- "@allurereport/plugin-testops": "3.21.0",
45
- "@allurereport/plugin-testplan": "3.21.0",
46
- "@allurereport/plugin-xray": "3.21.0",
47
- "@allurereport/reader": "3.21.0",
48
- "@allurereport/reader-api": "3.21.0",
49
- "@allurereport/service": "3.21.0",
50
- "@allurereport/summary": "3.21.0",
29
+ "@allurereport/ci": "3.22.0",
30
+ "@allurereport/cli-commons": "3.22.0",
31
+ "@allurereport/core-api": "3.22.0",
32
+ "@allurereport/plugin-agent": "3.22.0",
33
+ "@allurereport/plugin-allure2": "3.22.0",
34
+ "@allurereport/plugin-api": "3.22.0",
35
+ "@allurereport/plugin-awesome": "3.22.0",
36
+ "@allurereport/plugin-classic": "3.22.0",
37
+ "@allurereport/plugin-csv": "3.22.0",
38
+ "@allurereport/plugin-dashboard": "3.22.0",
39
+ "@allurereport/plugin-jira": "3.22.0",
40
+ "@allurereport/plugin-log": "3.22.0",
41
+ "@allurereport/plugin-metrics": "3.22.0",
42
+ "@allurereport/plugin-progress": "3.22.0",
43
+ "@allurereport/plugin-slack": "3.22.0",
44
+ "@allurereport/plugin-testops": "3.22.0",
45
+ "@allurereport/plugin-testplan": "3.22.0",
46
+ "@allurereport/plugin-xray": "3.22.0",
47
+ "@allurereport/reader": "3.22.0",
48
+ "@allurereport/reader-api": "3.22.0",
49
+ "@allurereport/service": "3.22.0",
50
+ "@allurereport/summary": "3.22.0",
51
51
  "glob": "^13.0.6",
52
52
  "handlebars": "4.7.10",
53
53
  "jiti": "^2.5.1",
@@ -58,17 +58,19 @@
58
58
  "zip-stream": "^7.0.5"
59
59
  },
60
60
  "devDependencies": {
61
- "@allurereport/test-config": "3.21.0",
61
+ "@allurereport/common-config": "3.22.0",
62
62
  "@types/node": "^20",
63
63
  "@types/zip-stream": "^7.0.0",
64
- "@vitest/coverage-istanbul": "^4.1.0",
64
+ "@vitest/coverage-istanbul": "5.0.4",
65
65
  "@vitest/runner": "^2",
66
- "@vitest/snapshot": "^5.0.1",
66
+ "@vitest/snapshot": "5.0.4",
67
67
  "allure-js-commons": "^3",
68
68
  "allure-vitest": "^3",
69
69
  "rimraf": "^6",
70
70
  "tslib": "^2.7.0",
71
71
  "typescript": "^5",
72
- "vitest": "^4.1.0"
72
+ "vite": "@voidzero-dev/vite-plus-core@1.2.0",
73
+ "vite-plus": "1.2.0",
74
+ "vitest": "5.0.4"
73
75
  }
74
76
  }