ortoni-report 1.1.5 → 1.1.6
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/changelog.md +10 -0
- package/dist/css/main.css +22445 -22445
- package/dist/ortoni-report.d.ts +26 -12
- package/dist/ortoni-report.js +242 -216
- package/dist/ortoni-report.mjs +211 -185
- package/dist/report-template.hbs +279 -225
- package/dist/utils/modal.js +10 -3
- package/package.json +3 -2
- package/readme.md +61 -117
package/dist/ortoni-report.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
3
|
interface OrtoniReportConfig {
|
|
4
4
|
projectName?: string;
|
|
5
5
|
authorName?: string;
|
|
6
6
|
testType?: string;
|
|
7
7
|
preferredTheme?: 'light' | 'dark';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
base64Image?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Steps {
|
|
12
|
+
snippet: string | undefined;
|
|
13
|
+
title: string;
|
|
14
|
+
location: string;
|
|
15
|
+
}
|
|
10
16
|
interface TestResultData {
|
|
17
|
+
suiteTags: string[];
|
|
18
|
+
testTags: string[];
|
|
19
|
+
location: string;
|
|
11
20
|
retry: string;
|
|
12
21
|
isRetry: number;
|
|
13
22
|
projectName: any;
|
|
@@ -17,14 +26,18 @@ interface TestResultData {
|
|
|
17
26
|
flaky: string;
|
|
18
27
|
duration: string;
|
|
19
28
|
errors: any[];
|
|
20
|
-
steps:
|
|
29
|
+
steps: Steps[];
|
|
21
30
|
logs: string;
|
|
22
|
-
screenshotPath
|
|
23
|
-
filePath:
|
|
31
|
+
screenshotPath?: string | null | undefined;
|
|
32
|
+
filePath: string;
|
|
24
33
|
projects: Set<string>;
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
tracePath?: string;
|
|
35
|
+
videoPath?: string;
|
|
36
|
+
base64Image: boolean | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
declare class OrtoniReport implements Reporter {
|
|
40
|
+
private projectRoot;
|
|
28
41
|
private results;
|
|
29
42
|
private groupedResults;
|
|
30
43
|
private suiteName;
|
|
@@ -33,9 +46,10 @@ declare class OrtoniReport implements Reporter {
|
|
|
33
46
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
34
47
|
onTestBegin(test: TestCase, result: TestResult): void;
|
|
35
48
|
private projectSet;
|
|
49
|
+
private tagsSet;
|
|
36
50
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
37
51
|
onEnd(result: FullResult): void;
|
|
38
52
|
generateHTML(filteredResults: TestResultData[], totalDuration: string): string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export { OrtoniReportConfig, OrtoniReport as default };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { OrtoniReportConfig, OrtoniReport as default };
|
package/dist/ortoni-report.js
CHANGED
|
@@ -1,216 +1,242 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/ortoni-report.ts
|
|
31
|
-
var ortoni_report_exports = {};
|
|
32
|
-
__export(ortoni_report_exports, {
|
|
33
|
-
default: () => OrtoniReport
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(ortoni_report_exports);
|
|
36
|
-
var import_fs = __toESM(require("fs"));
|
|
37
|
-
var import_path2 = __toESM(require("path"));
|
|
38
|
-
var import_handlebars = __toESM(require("handlebars"));
|
|
39
|
-
var import_safe = __toESM(require("colors/safe"));
|
|
40
|
-
|
|
41
|
-
// src/utils/utils.ts
|
|
42
|
-
var import_path = __toESM(require("path"));
|
|
43
|
-
function msToTime(duration) {
|
|
44
|
-
const milliseconds = Math.floor(duration % 1e3);
|
|
45
|
-
const seconds = Math.floor(duration / 1e3 % 60);
|
|
46
|
-
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
47
|
-
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
48
|
-
let result = "";
|
|
49
|
-
if (hours > 0) {
|
|
50
|
-
result += `${hours}h:`;
|
|
51
|
-
}
|
|
52
|
-
if (minutes > 0 || hours > 0) {
|
|
53
|
-
result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
|
|
54
|
-
}
|
|
55
|
-
if (seconds > 0 || minutes > 0 || hours > 0) {
|
|
56
|
-
result += `${seconds < 10 ? "0" + seconds : seconds}s`;
|
|
57
|
-
}
|
|
58
|
-
if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
|
|
59
|
-
result += `${milliseconds}ms`;
|
|
60
|
-
} else if (milliseconds > 0) {
|
|
61
|
-
result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
|
|
62
|
-
}
|
|
63
|
-
return result;
|
|
64
|
-
}
|
|
65
|
-
function normalizeFilePath(filePath) {
|
|
66
|
-
const normalizedPath = import_path.default.normalize(filePath);
|
|
67
|
-
return import_path.default.basename(normalizedPath);
|
|
68
|
-
}
|
|
69
|
-
function formatDate(date) {
|
|
70
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
71
|
-
const month = date.toLocaleString("default", { month: "short" });
|
|
72
|
-
const year = date.getFullYear();
|
|
73
|
-
const time = date.toLocaleTimeString();
|
|
74
|
-
return `${day}-${month}-${year} ${time}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/ortoni-report.ts
|
|
78
|
-
var OrtoniReport = class {
|
|
79
|
-
constructor(config = {}) {
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
82
|
-
this.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/ortoni-report.ts
|
|
31
|
+
var ortoni_report_exports = {};
|
|
32
|
+
__export(ortoni_report_exports, {
|
|
33
|
+
default: () => OrtoniReport
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(ortoni_report_exports);
|
|
36
|
+
var import_fs = __toESM(require("fs"));
|
|
37
|
+
var import_path2 = __toESM(require("path"));
|
|
38
|
+
var import_handlebars = __toESM(require("handlebars"));
|
|
39
|
+
var import_safe = __toESM(require("colors/safe"));
|
|
40
|
+
|
|
41
|
+
// src/utils/utils.ts
|
|
42
|
+
var import_path = __toESM(require("path"));
|
|
43
|
+
function msToTime(duration) {
|
|
44
|
+
const milliseconds = Math.floor(duration % 1e3);
|
|
45
|
+
const seconds = Math.floor(duration / 1e3 % 60);
|
|
46
|
+
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
47
|
+
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
48
|
+
let result = "";
|
|
49
|
+
if (hours > 0) {
|
|
50
|
+
result += `${hours}h:`;
|
|
51
|
+
}
|
|
52
|
+
if (minutes > 0 || hours > 0) {
|
|
53
|
+
result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
|
|
54
|
+
}
|
|
55
|
+
if (seconds > 0 || minutes > 0 || hours > 0) {
|
|
56
|
+
result += `${seconds < 10 ? "0" + seconds : seconds}s`;
|
|
57
|
+
}
|
|
58
|
+
if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
|
|
59
|
+
result += `${milliseconds}ms`;
|
|
60
|
+
} else if (milliseconds > 0) {
|
|
61
|
+
result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
function normalizeFilePath(filePath) {
|
|
66
|
+
const normalizedPath = import_path.default.normalize(filePath);
|
|
67
|
+
return import_path.default.basename(normalizedPath);
|
|
68
|
+
}
|
|
69
|
+
function formatDate(date) {
|
|
70
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
71
|
+
const month = date.toLocaleString("default", { month: "short" });
|
|
72
|
+
const year = date.getFullYear();
|
|
73
|
+
const time = date.toLocaleTimeString();
|
|
74
|
+
return `${day}-${month}-${year} ${time}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/ortoni-report.ts
|
|
78
|
+
var OrtoniReport = class {
|
|
79
|
+
constructor(config = {}) {
|
|
80
|
+
this.projectRoot = "";
|
|
81
|
+
this.results = [];
|
|
82
|
+
this.projectSet = /* @__PURE__ */ new Set();
|
|
83
|
+
// TODO: add tags to the filter dropdown
|
|
84
|
+
this.tagsSet = /* @__PURE__ */ new Set();
|
|
85
|
+
this.config = config;
|
|
86
|
+
}
|
|
87
|
+
onBegin(config, suite) {
|
|
88
|
+
this.results = [];
|
|
89
|
+
this.projectRoot = config.rootDir;
|
|
90
|
+
}
|
|
91
|
+
onTestBegin(test, result) {
|
|
92
|
+
}
|
|
93
|
+
onTestEnd(test, result) {
|
|
94
|
+
let status = result.status;
|
|
95
|
+
if (test.outcome() === "flaky") {
|
|
96
|
+
status = "flaky";
|
|
97
|
+
}
|
|
98
|
+
const projectName = test.titlePath()[1];
|
|
99
|
+
this.projectSet.add(projectName);
|
|
100
|
+
const location = test.location;
|
|
101
|
+
const filePath = normalizeFilePath(test.titlePath()[2]);
|
|
102
|
+
const tagPattern = /@[\w]+/g;
|
|
103
|
+
const testTags = test.title.match(tagPattern) || [];
|
|
104
|
+
const title = test.title.replace(tagPattern, "").trim();
|
|
105
|
+
const suiteTags = test.titlePath()[3].match(tagPattern) || [];
|
|
106
|
+
const suite = test.titlePath()[3].replace(tagPattern, "").trim();
|
|
107
|
+
const testResult = {
|
|
108
|
+
suiteTags,
|
|
109
|
+
testTags,
|
|
110
|
+
location: `${filePath}:${location.line}:${location.column}`,
|
|
111
|
+
retry: result.retry > 0 ? "retry" : "",
|
|
112
|
+
isRetry: result.retry,
|
|
113
|
+
projectName,
|
|
114
|
+
suite,
|
|
115
|
+
title,
|
|
116
|
+
status,
|
|
117
|
+
flaky: test.outcome(),
|
|
118
|
+
duration: msToTime(result.duration),
|
|
119
|
+
errors: result.errors.map((e) => import_safe.default.strip(e.message || e.toString())),
|
|
120
|
+
steps: result.steps.map((step) => {
|
|
121
|
+
const location2 = step.location ? `${import_path2.default.relative(this.projectRoot, step.location.file)}:${step.location.line}:${step.location.column}` : "";
|
|
122
|
+
return {
|
|
123
|
+
snippet: import_safe.default.strip(step.error?.snippet || ""),
|
|
124
|
+
title: step.title,
|
|
125
|
+
location: step.error ? location2 : ""
|
|
126
|
+
};
|
|
127
|
+
}),
|
|
128
|
+
logs: import_safe.default.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
|
|
129
|
+
filePath,
|
|
130
|
+
projects: this.projectSet,
|
|
131
|
+
base64Image: this.config.base64Image
|
|
132
|
+
};
|
|
133
|
+
if (result.attachments) {
|
|
134
|
+
const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
|
|
135
|
+
if (this.config.base64Image) {
|
|
136
|
+
if (screenshot && screenshot.path) {
|
|
137
|
+
const screenshotContent = import_fs.default.readFileSync(screenshot.path, "base64");
|
|
138
|
+
testResult.screenshotPath = `data:image/png;base64,${screenshotContent}`;
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
if (screenshot && screenshot.path) {
|
|
142
|
+
testResult.screenshotPath = import_path2.default.resolve(screenshot.path);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const tracePath = result.attachments.find((attachment) => attachment.name === "trace");
|
|
146
|
+
if (tracePath?.path) {
|
|
147
|
+
testResult.tracePath = import_path2.default.resolve(__dirname, tracePath.path);
|
|
148
|
+
}
|
|
149
|
+
const videoPath = result.attachments.find((attachment) => attachment.name === "video");
|
|
150
|
+
if (videoPath?.path) {
|
|
151
|
+
testResult.videoPath = import_path2.default.resolve(__dirname, videoPath.path);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
this.results.push(testResult);
|
|
155
|
+
}
|
|
156
|
+
onEnd(result) {
|
|
157
|
+
const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
|
|
158
|
+
const totalDuration = msToTime(result.duration);
|
|
159
|
+
this.groupedResults = this.results.reduce((acc, result2, index) => {
|
|
160
|
+
const filePath = result2.filePath;
|
|
161
|
+
const suiteName = result2.suite;
|
|
162
|
+
const projectName = result2.projectName;
|
|
163
|
+
if (!acc[filePath]) {
|
|
164
|
+
acc[filePath] = {};
|
|
165
|
+
}
|
|
166
|
+
if (!acc[filePath][suiteName]) {
|
|
167
|
+
acc[filePath][suiteName] = {};
|
|
168
|
+
}
|
|
169
|
+
if (!acc[filePath][suiteName][projectName]) {
|
|
170
|
+
acc[filePath][suiteName][projectName] = [];
|
|
171
|
+
}
|
|
172
|
+
acc[filePath][suiteName][projectName].push({ ...result2, index });
|
|
173
|
+
return acc;
|
|
174
|
+
}, {});
|
|
175
|
+
import_handlebars.default.registerHelper("json", function(context) {
|
|
176
|
+
return safeStringify(context);
|
|
177
|
+
});
|
|
178
|
+
import_handlebars.default.registerHelper("eq", function(actualStatus, expectedStatus) {
|
|
179
|
+
return actualStatus === expectedStatus;
|
|
180
|
+
});
|
|
181
|
+
import_handlebars.default.registerHelper("or", () => {
|
|
182
|
+
var args = Array.prototype.slice.call(arguments);
|
|
183
|
+
var options = args.pop();
|
|
184
|
+
for (var i = 0; i < args.length; i++) {
|
|
185
|
+
if (args[i]) {
|
|
186
|
+
return options.fn(this);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return options.inverse(this);
|
|
190
|
+
});
|
|
191
|
+
import_handlebars.default.registerHelper("gt", function(a, b) {
|
|
192
|
+
return a > b;
|
|
193
|
+
});
|
|
194
|
+
const html = this.generateHTML(filteredResults, totalDuration);
|
|
195
|
+
const outputPath = import_path2.default.resolve(process.cwd(), "ortoni-report.html");
|
|
196
|
+
import_fs.default.writeFileSync(outputPath, html);
|
|
197
|
+
console.log(`Ortoni HTML report generated at ${outputPath}`);
|
|
198
|
+
}
|
|
199
|
+
generateHTML(filteredResults, totalDuration) {
|
|
200
|
+
const totalTests = filteredResults.length;
|
|
201
|
+
const passedTests = this.results.filter((r) => r.status === "passed").length;
|
|
202
|
+
const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
|
|
203
|
+
const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
|
|
204
|
+
const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
205
|
+
const templateSource = import_fs.default.readFileSync(import_path2.default.resolve(__dirname, "report-template.hbs"), "utf-8");
|
|
206
|
+
const template = import_handlebars.default.compile(templateSource);
|
|
207
|
+
const data = {
|
|
208
|
+
totalDuration,
|
|
209
|
+
suiteName: this.suiteName,
|
|
210
|
+
results: this.results,
|
|
211
|
+
retryCount: this.results.filter((r) => r.isRetry).length,
|
|
212
|
+
passCount: passedTests,
|
|
213
|
+
failCount: failed,
|
|
214
|
+
skipCount: this.results.filter((r) => r.status === "skipped").length,
|
|
215
|
+
flakyCount: flakyTests,
|
|
216
|
+
totalCount: filteredResults.length,
|
|
217
|
+
groupedResults: this.groupedResults,
|
|
218
|
+
projectName: this.config.projectName,
|
|
219
|
+
authorName: this.config.authorName,
|
|
220
|
+
testType: this.config.testType,
|
|
221
|
+
preferredTheme: this.config.preferredTheme,
|
|
222
|
+
successRate,
|
|
223
|
+
lastRunDate: formatDate(/* @__PURE__ */ new Date()),
|
|
224
|
+
projects: this.projectSet
|
|
225
|
+
};
|
|
226
|
+
return template(data);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function safeStringify(obj, indent = 2) {
|
|
230
|
+
const cache = /* @__PURE__ */ new Set();
|
|
231
|
+
const json = JSON.stringify(obj, (key, value) => {
|
|
232
|
+
if (typeof value === "object" && value !== null) {
|
|
233
|
+
if (cache.has(value)) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
cache.add(value);
|
|
237
|
+
}
|
|
238
|
+
return value;
|
|
239
|
+
}, indent);
|
|
240
|
+
cache.clear();
|
|
241
|
+
return json;
|
|
242
|
+
}
|