@allurereport/core-api 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.
@@ -82,7 +82,7 @@ export type CategoryNodeItem = {
82
82
  name: string;
83
83
  key?: string;
84
84
  value?: string;
85
- historyId?: string;
85
+ retryHash?: string;
86
86
  retriesCount?: number;
87
87
  transition?: TestStatusTransition;
88
88
  tooltips?: Record<string, string>;
package/dist/config.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  export type DefaultLabelsConfig = Record<string, string | string[]>;
2
+ export type FlakyDetectionConfig = {
3
+ historyDepth?: number;
4
+ includePassedTests?: boolean;
5
+ };
6
+ export declare const parseIntegerConfigValue: (value: unknown, minValue?: number) => number | undefined;
2
7
  export type AllureServiceConfig = {
3
8
  accessToken?: string;
4
9
  private?: boolean;
@@ -6,4 +11,4 @@ export type AllureServiceConfig = {
6
11
  uploadMaxAttempts?: number;
7
12
  uploadMaxSimultaneousFailures?: number;
8
13
  };
9
- export type ResolvedAllureServiceConfig = AllureServiceConfig & Required<Pick<AllureServiceConfig, "uploadConcurrency" | "uploadMaxAttempts" | "uploadMaxSimultaneousFailures">>;
14
+ export type ResolvedAllureServiceConfig = AllureServiceConfig & Required<Pick<AllureServiceConfig, "uploadMaxAttempts" | "uploadMaxSimultaneousFailures">>;
package/dist/config.js CHANGED
@@ -1 +1,7 @@
1
- export {};
1
+ export const parseIntegerConfigValue = (value, minValue) => {
2
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3
+ return undefined;
4
+ }
5
+ const normalized = Math.floor(value);
6
+ return minValue === undefined || normalized >= minValue ? normalized : undefined;
7
+ };
package/dist/history.d.ts CHANGED
@@ -12,7 +12,7 @@ export interface HistoryTestResult {
12
12
  duration?: number;
13
13
  labels?: TestLabel[];
14
14
  url: string;
15
- historyId?: string;
15
+ retryHash?: string;
16
16
  reportLinks?: any[];
17
17
  }
18
18
  export interface HistoryDataPoint {
@@ -24,10 +24,12 @@ export interface HistoryDataPoint {
24
24
  metrics: Record<string, number>;
25
25
  url: string;
26
26
  }
27
+ export type HistoryTestResultUrlResolver = (historyUrl: string, pluginId: string, historicalResultId: string) => string;
27
28
  export interface AllureHistory {
28
29
  readHistory(params?: {
29
30
  repo?: string;
30
31
  branch?: string;
31
32
  }): Promise<HistoryDataPoint[]>;
32
33
  appendHistory(history: HistoryDataPoint): Promise<void>;
34
+ resolveTestResultUrl?: HistoryTestResultUrlResolver;
33
35
  }
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export * from "./metrics.js";
10
10
  export type * from "./model.js";
11
11
  export type * from "./testCase.js";
12
12
  export type * from "./testPlan.js";
13
- export type * from "./config.js";
13
+ export * from "./config.js";
14
14
  export * from "./static.js";
15
15
  export * from "./categories.js";
16
16
  export * from "./utils/step.js";
@@ -22,8 +22,10 @@ export * from "./utils/label.js";
22
22
  export * from "./utils/parameter.js";
23
23
  export * from "./utils/testplan.js";
24
24
  export * from "./utils/status.js";
25
+ export * from "./utils/successRate.js";
25
26
  export * from "./utils/environment.js";
26
27
  export * from "./utils/history.js";
28
+ export * from "./utils/identity.js";
27
29
  export * from "./utils/strings.js";
28
30
  export * from "./utils/dictionary.js";
29
31
  export * from "./utils/path.js";
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from "./constants.js";
2
2
  export * from "./ci.js";
3
3
  export * from "./gitFlow.js";
4
4
  export * from "./metrics.js";
5
+ export * from "./config.js";
5
6
  export * from "./static.js";
6
7
  export * from "./categories.js";
7
8
  export * from "./utils/step.js";
@@ -12,8 +13,10 @@ export * from "./utils/label.js";
12
13
  export * from "./utils/parameter.js";
13
14
  export * from "./utils/testplan.js";
14
15
  export * from "./utils/status.js";
16
+ export * from "./utils/successRate.js";
15
17
  export * from "./utils/environment.js";
16
18
  export * from "./utils/history.js";
19
+ export * from "./utils/identity.js";
17
20
  export * from "./utils/strings.js";
18
21
  export * from "./utils/dictionary.js";
19
22
  export * from "./utils/path.js";
package/dist/model.d.ts CHANGED
@@ -21,6 +21,9 @@ export interface SourceMetadata {
21
21
  metadata: {
22
22
  [key: string]: any;
23
23
  };
24
+ legacyHistoryId?: string;
25
+ reportedFlaky?: boolean;
26
+ legacyTestCaseHash?: string;
24
27
  }
25
28
  export interface TestError {
26
29
  message?: string;
@@ -33,10 +36,13 @@ export interface TestResult {
33
36
  name: string;
34
37
  status: TestStatus;
35
38
  error?: TestError;
39
+ errors?: TestError[];
36
40
  testCase?: TestCase;
37
41
  environment?: string;
38
42
  fullName?: string;
39
- historyId?: string;
43
+ testCaseHash: string | null;
44
+ parametersHash: string;
45
+ environmentHash: string | null;
40
46
  description?: string;
41
47
  descriptionHtml?: string;
42
48
  precondition?: string;
@@ -48,10 +54,11 @@ export interface TestResult {
48
54
  duration?: number;
49
55
  flaky: boolean;
50
56
  muted: boolean;
57
+ known: boolean;
51
58
  resolution?: ResolutionCategory;
52
59
  resolutionComment?: string;
53
60
  transition?: TestStatusTransition;
54
- retryHash?: string;
61
+ retryHash: string | null;
55
62
  isRetry: boolean;
56
63
  hostId?: string;
57
64
  threadId?: string;
@@ -1,10 +1,9 @@
1
1
  import type { HistoryDataPoint, HistoryTestResult } from "../history.js";
2
- import type { TestParameter } from "../metadata.js";
3
2
  import type { TestResult } from "../model.js";
4
- export declare const stringifyHistoryParams: (parameters?: TestParameter[]) => string;
5
- export declare const getFallbackHistoryId: (tr: Pick<TestResult, "labels" | "parameters">) => string | undefined;
6
- export declare const getHistoryIdCandidates: (tr: Pick<TestResult, "historyId" | "labels" | "parameters">) => string[];
3
+ export declare const createHistoryTestResultLookup: (testResults: Iterable<TestResult | HistoryTestResult>) => <T extends TestResult | HistoryTestResult>(point: {
4
+ testResults?: Record<string, T>;
5
+ }, result: TestResult | HistoryTestResult) => T | undefined;
7
6
  export declare const normalizeHistoryDataPoint: (historyDataPoint: HistoryDataPoint) => HistoryDataPoint;
8
7
  export declare const normalizeHistoryDataPointUrls: (historyDataPoint: HistoryDataPoint) => HistoryDataPoint;
9
- export declare const selectHistoryTestResults: (historyDataPoints: HistoryDataPoint[], historyIdCandidates: readonly string[]) => HistoryTestResult[];
8
+ export declare const selectHistoryTestResults: (historyDataPoints: HistoryDataPoint[], retryHashes: readonly string[]) => HistoryTestResult[];
10
9
  export declare const htrsByTr: (hdps: HistoryDataPoint[], tr: TestResult | HistoryTestResult) => HistoryTestResult[];
@@ -1,13 +1,25 @@
1
- import { createHash } from "node:crypto";
2
- import { fallbackTestCaseIdLabelName } from "../constants.js";
3
- import { findLastByLabelName } from "./label.js";
4
- const md5 = (data) => createHash("md5").update(data).digest("hex");
1
+ import { DEFAULT_ENVIRONMENT } from "./environment.js";
5
2
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
3
  const normalizeHistoryTestResults = (testResults) => {
7
4
  if (!isRecord(testResults)) {
8
5
  return {};
9
6
  }
10
- return Object.fromEntries(Object.entries(testResults).filter(([, value]) => isRecord(value)));
7
+ return Object.fromEntries(Object.entries(testResults).flatMap(([retryHash, value]) => {
8
+ if (!isRecord(value)) {
9
+ return [];
10
+ }
11
+ const historyTestResult = { ...value };
12
+ delete historyTestResult.historyId;
13
+ return [
14
+ [
15
+ retryHash,
16
+ {
17
+ ...historyTestResult,
18
+ retryHash,
19
+ },
20
+ ],
21
+ ];
22
+ }));
11
23
  };
12
24
  const normalizeHistoryMetrics = (metrics) => {
13
25
  if (!isRecord(metrics)) {
@@ -15,33 +27,64 @@ const normalizeHistoryMetrics = (metrics) => {
15
27
  }
16
28
  return Object.fromEntries(Object.entries(metrics).filter(([, value]) => Number.isFinite(value)));
17
29
  };
18
- const parametersCompare = (a, b) => {
19
- return (a.name ?? "").localeCompare(b.name ?? "") || (a.value ?? "").localeCompare(b.value ?? "");
20
- };
21
- export const stringifyHistoryParams = (parameters = []) => {
22
- return [...parameters]
23
- .filter((parameter) => !parameter?.excluded)
24
- .sort(parametersCompare)
25
- .map((parameter) => `${parameter.name}:${parameter.value}`)
26
- .join(",");
27
- };
28
- export const getFallbackHistoryId = (tr) => {
29
- const fallbackTestCaseId = findLastByLabelName(tr.labels ?? [], fallbackTestCaseIdLabelName);
30
- if (!fallbackTestCaseId) {
31
- return undefined;
30
+ const isDefaultEnvironment = (result) => result.environment === undefined || result.environment === DEFAULT_ENVIRONMENT;
31
+ export const createHistoryTestResultLookup = (testResults) => {
32
+ const canonicalKeys = new Set();
33
+ const retryHashByLegacyHistoryId = new Map();
34
+ for (const result of testResults) {
35
+ if (!result.retryHash) {
36
+ continue;
37
+ }
38
+ canonicalKeys.add(result.retryHash);
39
+ const legacyId = "sourceMetadata" in result ? result.sourceMetadata?.legacyHistoryId : undefined;
40
+ if (!legacyId || !isDefaultEnvironment(result) || ("environmentHash" in result && result.environmentHash)) {
41
+ continue;
42
+ }
43
+ if (!retryHashByLegacyHistoryId.has(legacyId)) {
44
+ retryHashByLegacyHistoryId.set(legacyId, result.retryHash);
45
+ }
46
+ else if (retryHashByLegacyHistoryId.get(legacyId) !== result.retryHash) {
47
+ retryHashByLegacyHistoryId.set(legacyId, undefined);
48
+ }
32
49
  }
33
- return `${fallbackTestCaseId}.${md5(stringifyHistoryParams(tr.parameters ?? []))}`;
34
- };
35
- export const getHistoryIdCandidates = (tr) => {
36
- const result = [];
37
- if (tr.historyId) {
38
- result.push(tr.historyId);
50
+ for (const [legacyId, retryHash] of retryHashByLegacyHistoryId) {
51
+ if (retryHash === undefined || (canonicalKeys.has(legacyId) && legacyId !== retryHash)) {
52
+ retryHashByLegacyHistoryId.set(legacyId, undefined);
53
+ }
39
54
  }
40
- const fallbackHistoryId = getFallbackHistoryId(tr);
41
- if (fallbackHistoryId && !result.includes(fallbackHistoryId)) {
42
- result.push(fallbackHistoryId);
55
+ const legacyByCanonical = new Map();
56
+ for (const [legacyId, retryHash] of retryHashByLegacyHistoryId) {
57
+ if (!retryHash || legacyId === retryHash) {
58
+ continue;
59
+ }
60
+ legacyByCanonical.set(retryHash, legacyByCanonical.has(retryHash) ? undefined : legacyId);
43
61
  }
44
- return result;
62
+ return (point, result) => {
63
+ if (!result.retryHash) {
64
+ return undefined;
65
+ }
66
+ const historicalDefault = !("sourceMetadata" in result) && isDefaultEnvironment(result);
67
+ const retryHash = historicalDefault
68
+ ? (retryHashByLegacyHistoryId.get(result.retryHash) ?? result.retryHash)
69
+ : result.retryHash;
70
+ const canonical = Object.hasOwn(point.testResults ?? {}, retryHash) ? point.testResults?.[retryHash] : undefined;
71
+ if (canonical) {
72
+ return canonical;
73
+ }
74
+ const legacyId = "sourceMetadata" in result
75
+ ? result.sourceMetadata?.legacyHistoryId
76
+ : historicalDefault
77
+ ? legacyByCanonical.get(retryHash)
78
+ : undefined;
79
+ if (!legacyId ||
80
+ retryHashByLegacyHistoryId.get(legacyId) !== retryHash ||
81
+ !isDefaultEnvironment(result) ||
82
+ ("environmentHash" in result && result.environmentHash)) {
83
+ return undefined;
84
+ }
85
+ const legacy = Object.hasOwn(point.testResults ?? {}, legacyId) ? point.testResults?.[legacyId] : undefined;
86
+ return legacy && isDefaultEnvironment(legacy) ? legacy : undefined;
87
+ };
45
88
  };
46
89
  export const normalizeHistoryDataPoint = (historyDataPoint) => ({
47
90
  ...historyDataPoint,
@@ -57,14 +100,14 @@ export const normalizeHistoryDataPointUrls = (historyDataPoint) => {
57
100
  return normalizedHistoryDataPoint;
58
101
  }
59
102
  let testResults = normalizedHistoryDataPoint.testResults;
60
- for (const [historyId, historyTestResult] of Object.entries(normalizedHistoryDataPoint.testResults)) {
103
+ for (const [retryHash, historyTestResult] of Object.entries(normalizedHistoryDataPoint.testResults)) {
61
104
  if (historyTestResult.url) {
62
105
  continue;
63
106
  }
64
107
  if (testResults === normalizedHistoryDataPoint.testResults) {
65
108
  testResults = { ...normalizedHistoryDataPoint.testResults };
66
109
  }
67
- testResults[historyId] = {
110
+ testResults[retryHash] = {
68
111
  ...historyTestResult,
69
112
  url,
70
113
  };
@@ -77,13 +120,13 @@ export const normalizeHistoryDataPointUrls = (historyDataPoint) => {
77
120
  testResults,
78
121
  };
79
122
  };
80
- export const selectHistoryTestResults = (historyDataPoints, historyIdCandidates) => {
81
- if (historyIdCandidates.length === 0) {
123
+ export const selectHistoryTestResults = (historyDataPoints, retryHashes) => {
124
+ if (retryHashes.length === 0) {
82
125
  return [];
83
126
  }
84
127
  return historyDataPoints.reduce((acc, historyDataPoint) => {
85
- for (const historyId of historyIdCandidates) {
86
- const historyTestResult = historyDataPoint.testResults?.[historyId];
128
+ for (const retryHash of retryHashes) {
129
+ const historyTestResult = historyDataPoint.testResults?.[retryHash];
87
130
  if (!historyTestResult) {
88
131
  continue;
89
132
  }
@@ -94,8 +137,9 @@ export const selectHistoryTestResults = (historyDataPoints, historyIdCandidates)
94
137
  }, []);
95
138
  };
96
139
  export const htrsByTr = (hdps, tr) => {
97
- if (!tr?.historyId) {
98
- return [];
99
- }
100
- return selectHistoryTestResults(hdps, [tr.historyId]);
140
+ const lookup = createHistoryTestResultLookup([tr]);
141
+ return hdps.flatMap((point) => {
142
+ const result = lookup(point, tr);
143
+ return result ? [result] : [];
144
+ });
101
145
  };
@@ -0,0 +1,17 @@
1
+ export declare const UNKNOWN_PARAMETER_VALUE = "#___unknown_value___#";
2
+ export type IdentityParameter = {
3
+ name?: string | null;
4
+ value?: string | null;
5
+ excluded?: boolean | null;
6
+ };
7
+ export declare const compareUtf8: (first: string, second: string) => number;
8
+ export declare const md5Utf8: (value: string) => string;
9
+ export declare const calculateTestCaseHash: (testCaseId: string | null | undefined, fullName: string | null | undefined) => string | null;
10
+ export declare const stringifyIdentityParameters: (parameters: readonly (IdentityParameter | null | undefined)[] | null | undefined) => string;
11
+ export declare const calculateParametersHash: (parameters: readonly (IdentityParameter | null | undefined)[] | null | undefined) => string;
12
+ export declare const calculateEnvironmentHash: (namedEnvironmentId: string | null | undefined) => string | null;
13
+ export declare const calculateRetryHash: ({ testCaseHash, parametersHash, environmentHash, }: {
14
+ testCaseHash: string | null | undefined;
15
+ parametersHash: string;
16
+ environmentHash?: string | null;
17
+ }) => string | null;
@@ -0,0 +1,50 @@
1
+ import { createHash } from "node:crypto";
2
+ import { DEFAULT_ENVIRONMENT } from "./environment.js";
3
+ export const UNKNOWN_PARAMETER_VALUE = "#___unknown_value___#";
4
+ const utf8Encoder = new TextEncoder();
5
+ export const compareUtf8 = (first, second) => {
6
+ const firstBytes = utf8Encoder.encode(first);
7
+ const secondBytes = utf8Encoder.encode(second);
8
+ const length = Math.min(firstBytes.length, secondBytes.length);
9
+ for (let index = 0; index < length; index++) {
10
+ const comparison = firstBytes[index] - secondBytes[index];
11
+ if (comparison !== 0) {
12
+ return comparison;
13
+ }
14
+ }
15
+ return firstBytes.length - secondBytes.length;
16
+ };
17
+ export const md5Utf8 = (value) => createHash("md5").update(value, "utf8").digest("hex");
18
+ export const calculateTestCaseHash = (testCaseId, fullName) => {
19
+ const identity = testCaseId?.length ? testCaseId : fullName?.length ? fullName : undefined;
20
+ return identity === undefined ? null : md5Utf8(identity);
21
+ };
22
+ const compareIdentityParameters = (first, second) => compareUtf8(first.name, second.name) || compareUtf8(first.value, second.value);
23
+ export const stringifyIdentityParameters = (parameters) => {
24
+ const normalized = [];
25
+ for (const parameter of parameters ?? []) {
26
+ if (parameter == null) {
27
+ continue;
28
+ }
29
+ const { name } = parameter;
30
+ if (typeof name !== "string" || name.length === 0 || parameter.excluded === true) {
31
+ continue;
32
+ }
33
+ normalized.push({
34
+ name,
35
+ value: parameter.value ?? UNKNOWN_PARAMETER_VALUE,
36
+ });
37
+ }
38
+ const unique = normalized
39
+ .sort(compareIdentityParameters)
40
+ .filter((parameter, index, all) => index === 0 || parameter.name !== all[index - 1].name || parameter.value !== all[index - 1].value);
41
+ return unique.map(({ name, value }) => `${name}:${value}`).join(",");
42
+ };
43
+ export const calculateParametersHash = (parameters) => md5Utf8(stringifyIdentityParameters(parameters));
44
+ export const calculateEnvironmentHash = (namedEnvironmentId) => namedEnvironmentId == null || namedEnvironmentId === DEFAULT_ENVIRONMENT ? null : md5Utf8(namedEnvironmentId);
45
+ export const calculateRetryHash = ({ testCaseHash, parametersHash, environmentHash, }) => {
46
+ if (!testCaseHash) {
47
+ return null;
48
+ }
49
+ return environmentHash ? `${testCaseHash}.${parametersHash}.${environmentHash}` : `${testCaseHash}.${parametersHash}`;
50
+ };
@@ -0,0 +1,3 @@
1
+ import type { Statistic } from "../aggregate.js";
2
+ export declare const getSuccessRateTotal: (statistic: Partial<Statistic>) => number;
3
+ export declare const getSuccessRate: (statistic: Partial<Statistic>) => number;
@@ -0,0 +1,6 @@
1
+ import { includedInSuccessRate } from "../constants.js";
2
+ export const getSuccessRateTotal = (statistic) => Array.from(includedInSuccessRate).reduce((total, status) => total + (statistic[status] ?? 0), 0);
3
+ export const getSuccessRate = (statistic) => {
4
+ const total = getSuccessRateTotal(statistic);
5
+ return total > 0 ? (statistic.passed ?? 0) / total : 0;
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allurereport/core-api",
3
- "version": "3.17.0",
3
+ "version": "3.19.0",
4
4
  "description": "Allure Core API",
5
5
  "keywords": [
6
6
  "allure"