ortoni-report 1.1.4 → 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.
@@ -1,185 +1,211 @@
1
- // src/ortoni-report.ts
2
- import fs from "fs";
3
- import path2 from "path";
4
- import Handlebars from "handlebars";
5
- import colors from "colors/safe";
6
-
7
- // src/utils/utils.ts
8
- import path from "path";
9
- function msToTime(duration) {
10
- const milliseconds = Math.floor(duration % 1e3);
11
- const seconds = Math.floor(duration / 1e3 % 60);
12
- const minutes = Math.floor(duration / (1e3 * 60) % 60);
13
- const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
14
- let result = "";
15
- if (hours > 0) {
16
- result += `${hours}h:`;
17
- }
18
- if (minutes > 0 || hours > 0) {
19
- result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
20
- }
21
- if (seconds > 0 || minutes > 0 || hours > 0) {
22
- result += `${seconds < 10 ? "0" + seconds : seconds}s`;
23
- }
24
- if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
25
- result += `${milliseconds}ms`;
26
- } else if (milliseconds > 0) {
27
- result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
28
- }
29
- return result;
30
- }
31
- function normalizeFilePath(filePath) {
32
- const normalizedPath = path.normalize(filePath);
33
- return path.basename(normalizedPath);
34
- }
35
- function formatDate(date) {
36
- const day = String(date.getDate()).padStart(2, "0");
37
- const month = date.toLocaleString("default", { month: "short" });
38
- const year = date.getFullYear();
39
- const time = date.toLocaleTimeString();
40
- return `${day}-${month}-${year} ${time}`;
41
- }
42
-
43
- // src/ortoni-report.ts
44
- var OrtoniReport = class {
45
- constructor(config = {}) {
46
- this.results = [];
47
- this.projectSet = /* @__PURE__ */ new Set();
48
- this.config = config;
49
- }
50
- onBegin(config, suite) {
51
- this.results = [];
52
- }
53
- onTestBegin(test, result) {
54
- }
55
- onTestEnd(test, result) {
56
- let status = result.status;
57
- if (test.outcome() === "flaky") {
58
- status = "flaky";
59
- }
60
- this.projectSet.add(test.titlePath()[1]);
61
- const testResult = {
62
- retry: result.retry > 0 ? "retry" : "",
63
- isRetry: result.retry,
64
- projectName: test.titlePath()[1],
65
- suite: test.titlePath()[3],
66
- title: test.title,
67
- status,
68
- flaky: test.outcome(),
69
- duration: msToTime(result.duration),
70
- errors: result.errors.map((e) => colors.strip(e.message || e.toString())),
71
- steps: result.steps.map((step) => ({
72
- titlePath: step.titlePath,
73
- category: step.category,
74
- duration: step.duration,
75
- error: step.error,
76
- location: step.location,
77
- parent: step.parent,
78
- startTime: step.startTime,
79
- steps: step.steps,
80
- title: step.title
81
- })),
82
- logs: colors.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
83
- screenshotPath: null,
84
- filePath: normalizeFilePath(test.titlePath()[2]),
85
- projects: this.projectSet
86
- };
87
- if (result.attachments) {
88
- const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
89
- if (screenshot && screenshot.path) {
90
- const screenshotContent = fs.readFileSync(screenshot.path, "base64");
91
- testResult.screenshotPath = screenshotContent;
92
- }
93
- }
94
- this.results.push(testResult);
95
- }
96
- onEnd(result) {
97
- const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
98
- const totalDuration = msToTime(result.duration);
99
- this.groupedResults = this.results.reduce((acc, result2, index) => {
100
- const filePath = result2.filePath;
101
- const suiteName = result2.suite;
102
- const projectName = result2.projectName;
103
- if (!acc[filePath]) {
104
- acc[filePath] = {};
105
- }
106
- if (!acc[filePath][suiteName]) {
107
- acc[filePath][suiteName] = {};
108
- }
109
- if (!acc[filePath][suiteName][projectName]) {
110
- acc[filePath][suiteName][projectName] = [];
111
- }
112
- acc[filePath][suiteName][projectName].push({ ...result2, index });
113
- return acc;
114
- }, {});
115
- Handlebars.registerHelper("json", function(context) {
116
- return safeStringify(context);
117
- });
118
- Handlebars.registerHelper("eq", function(actualStatus, expectedStatus) {
119
- return actualStatus === expectedStatus;
120
- });
121
- Handlebars.registerHelper("or", () => {
122
- var args = Array.prototype.slice.call(arguments);
123
- var options = args.pop();
124
- for (var i = 0; i < args.length; i++) {
125
- if (args[i]) {
126
- return options.fn(this);
127
- }
128
- }
129
- return options.inverse(this);
130
- });
131
- Handlebars.registerHelper("gt", function(a, b) {
132
- return a > b;
133
- });
134
- const html = this.generateHTML(filteredResults, totalDuration);
135
- const outputPath = path2.resolve(process.cwd(), "ortoni-report.html");
136
- fs.writeFileSync(outputPath, html);
137
- console.log(`Ortoni HTML report generated at ${outputPath}`);
138
- }
139
- generateHTML(filteredResults, totalDuration) {
140
- const totalTests = filteredResults.length;
141
- const passedTests = this.results.filter((r) => r.status === "passed").length;
142
- const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
143
- const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
144
- const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
145
- const templateSource = fs.readFileSync(path2.resolve(__dirname, "report-template.hbs"), "utf-8");
146
- const template = Handlebars.compile(templateSource);
147
- const data = {
148
- totalDuration,
149
- suiteName: this.suiteName,
150
- results: this.results,
151
- retryCount: this.results.filter((r) => r.isRetry).length,
152
- passCount: passedTests,
153
- failCount: failed,
154
- skipCount: this.results.filter((r) => r.status === "skipped").length,
155
- flakyCount: flakyTests,
156
- totalCount: filteredResults.length,
157
- groupedResults: this.groupedResults,
158
- projectName: this.config.projectName,
159
- authorName: this.config.authorName,
160
- testType: this.config.testType,
161
- preferredTheme: this.config.preferredTheme,
162
- successRate,
163
- lastRunDate: formatDate(/* @__PURE__ */ new Date()),
164
- projects: this.projectSet
165
- };
166
- return template(data);
167
- }
168
- };
169
- function safeStringify(obj, indent = 2) {
170
- const cache = /* @__PURE__ */ new Set();
171
- const json = JSON.stringify(obj, (key, value) => {
172
- if (typeof value === "object" && value !== null) {
173
- if (cache.has(value)) {
174
- return;
175
- }
176
- cache.add(value);
177
- }
178
- return value;
179
- }, indent);
180
- cache.clear();
181
- return json;
182
- }
183
- export {
184
- OrtoniReport as default
185
- };
1
+ // src/ortoni-report.ts
2
+ import fs from "fs";
3
+ import path2 from "path";
4
+ import Handlebars from "handlebars";
5
+ import colors from "colors/safe";
6
+
7
+ // src/utils/utils.ts
8
+ import path from "path";
9
+ function msToTime(duration) {
10
+ const milliseconds = Math.floor(duration % 1e3);
11
+ const seconds = Math.floor(duration / 1e3 % 60);
12
+ const minutes = Math.floor(duration / (1e3 * 60) % 60);
13
+ const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
14
+ let result = "";
15
+ if (hours > 0) {
16
+ result += `${hours}h:`;
17
+ }
18
+ if (minutes > 0 || hours > 0) {
19
+ result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
20
+ }
21
+ if (seconds > 0 || minutes > 0 || hours > 0) {
22
+ result += `${seconds < 10 ? "0" + seconds : seconds}s`;
23
+ }
24
+ if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
25
+ result += `${milliseconds}ms`;
26
+ } else if (milliseconds > 0) {
27
+ result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
28
+ }
29
+ return result;
30
+ }
31
+ function normalizeFilePath(filePath) {
32
+ const normalizedPath = path.normalize(filePath);
33
+ return path.basename(normalizedPath);
34
+ }
35
+ function formatDate(date) {
36
+ const day = String(date.getDate()).padStart(2, "0");
37
+ const month = date.toLocaleString("default", { month: "short" });
38
+ const year = date.getFullYear();
39
+ const time = date.toLocaleTimeString();
40
+ return `${day}-${month}-${year} ${time}`;
41
+ }
42
+
43
+ // src/ortoni-report.ts
44
+ var OrtoniReport = class {
45
+ constructor(config = {}) {
46
+ this.projectRoot = "";
47
+ this.results = [];
48
+ this.projectSet = /* @__PURE__ */ new Set();
49
+ // TODO: add tags to the filter dropdown
50
+ this.tagsSet = /* @__PURE__ */ new Set();
51
+ this.config = config;
52
+ }
53
+ onBegin(config, suite) {
54
+ this.results = [];
55
+ this.projectRoot = config.rootDir;
56
+ }
57
+ onTestBegin(test, result) {
58
+ }
59
+ onTestEnd(test, result) {
60
+ let status = result.status;
61
+ if (test.outcome() === "flaky") {
62
+ status = "flaky";
63
+ }
64
+ const projectName = test.titlePath()[1];
65
+ this.projectSet.add(projectName);
66
+ const location = test.location;
67
+ const filePath = normalizeFilePath(test.titlePath()[2]);
68
+ const tagPattern = /@[\w]+/g;
69
+ const testTags = test.title.match(tagPattern) || [];
70
+ const title = test.title.replace(tagPattern, "").trim();
71
+ const suiteTags = test.titlePath()[3].match(tagPattern) || [];
72
+ const suite = test.titlePath()[3].replace(tagPattern, "").trim();
73
+ const testResult = {
74
+ suiteTags,
75
+ testTags,
76
+ location: `${filePath}:${location.line}:${location.column}`,
77
+ retry: result.retry > 0 ? "retry" : "",
78
+ isRetry: result.retry,
79
+ projectName,
80
+ suite,
81
+ title,
82
+ status,
83
+ flaky: test.outcome(),
84
+ duration: msToTime(result.duration),
85
+ errors: result.errors.map((e) => colors.strip(e.message || e.toString())),
86
+ steps: result.steps.map((step) => {
87
+ const location2 = step.location ? `${path2.relative(this.projectRoot, step.location.file)}:${step.location.line}:${step.location.column}` : "";
88
+ return {
89
+ snippet: colors.strip(step.error?.snippet || ""),
90
+ title: step.title,
91
+ location: step.error ? location2 : ""
92
+ };
93
+ }),
94
+ logs: colors.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
95
+ filePath,
96
+ projects: this.projectSet,
97
+ base64Image: this.config.base64Image
98
+ };
99
+ if (result.attachments) {
100
+ const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
101
+ if (this.config.base64Image) {
102
+ if (screenshot && screenshot.path) {
103
+ const screenshotContent = fs.readFileSync(screenshot.path, "base64");
104
+ testResult.screenshotPath = `data:image/png;base64,${screenshotContent}`;
105
+ }
106
+ } else {
107
+ if (screenshot && screenshot.path) {
108
+ testResult.screenshotPath = path2.resolve(screenshot.path);
109
+ }
110
+ }
111
+ const tracePath = result.attachments.find((attachment) => attachment.name === "trace");
112
+ if (tracePath?.path) {
113
+ testResult.tracePath = path2.resolve(__dirname, tracePath.path);
114
+ }
115
+ const videoPath = result.attachments.find((attachment) => attachment.name === "video");
116
+ if (videoPath?.path) {
117
+ testResult.videoPath = path2.resolve(__dirname, videoPath.path);
118
+ }
119
+ }
120
+ this.results.push(testResult);
121
+ }
122
+ onEnd(result) {
123
+ const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
124
+ const totalDuration = msToTime(result.duration);
125
+ this.groupedResults = this.results.reduce((acc, result2, index) => {
126
+ const filePath = result2.filePath;
127
+ const suiteName = result2.suite;
128
+ const projectName = result2.projectName;
129
+ if (!acc[filePath]) {
130
+ acc[filePath] = {};
131
+ }
132
+ if (!acc[filePath][suiteName]) {
133
+ acc[filePath][suiteName] = {};
134
+ }
135
+ if (!acc[filePath][suiteName][projectName]) {
136
+ acc[filePath][suiteName][projectName] = [];
137
+ }
138
+ acc[filePath][suiteName][projectName].push({ ...result2, index });
139
+ return acc;
140
+ }, {});
141
+ Handlebars.registerHelper("json", function(context) {
142
+ return safeStringify(context);
143
+ });
144
+ Handlebars.registerHelper("eq", function(actualStatus, expectedStatus) {
145
+ return actualStatus === expectedStatus;
146
+ });
147
+ Handlebars.registerHelper("or", () => {
148
+ var args = Array.prototype.slice.call(arguments);
149
+ var options = args.pop();
150
+ for (var i = 0; i < args.length; i++) {
151
+ if (args[i]) {
152
+ return options.fn(this);
153
+ }
154
+ }
155
+ return options.inverse(this);
156
+ });
157
+ Handlebars.registerHelper("gt", function(a, b) {
158
+ return a > b;
159
+ });
160
+ const html = this.generateHTML(filteredResults, totalDuration);
161
+ const outputPath = path2.resolve(process.cwd(), "ortoni-report.html");
162
+ fs.writeFileSync(outputPath, html);
163
+ console.log(`Ortoni HTML report generated at ${outputPath}`);
164
+ }
165
+ generateHTML(filteredResults, totalDuration) {
166
+ const totalTests = filteredResults.length;
167
+ const passedTests = this.results.filter((r) => r.status === "passed").length;
168
+ const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
169
+ const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
170
+ const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
171
+ const templateSource = fs.readFileSync(path2.resolve(__dirname, "report-template.hbs"), "utf-8");
172
+ const template = Handlebars.compile(templateSource);
173
+ const data = {
174
+ totalDuration,
175
+ suiteName: this.suiteName,
176
+ results: this.results,
177
+ retryCount: this.results.filter((r) => r.isRetry).length,
178
+ passCount: passedTests,
179
+ failCount: failed,
180
+ skipCount: this.results.filter((r) => r.status === "skipped").length,
181
+ flakyCount: flakyTests,
182
+ totalCount: filteredResults.length,
183
+ groupedResults: this.groupedResults,
184
+ projectName: this.config.projectName,
185
+ authorName: this.config.authorName,
186
+ testType: this.config.testType,
187
+ preferredTheme: this.config.preferredTheme,
188
+ successRate,
189
+ lastRunDate: formatDate(/* @__PURE__ */ new Date()),
190
+ projects: this.projectSet
191
+ };
192
+ return template(data);
193
+ }
194
+ };
195
+ function safeStringify(obj, indent = 2) {
196
+ const cache = /* @__PURE__ */ new Set();
197
+ const json = JSON.stringify(obj, (key, value) => {
198
+ if (typeof value === "object" && value !== null) {
199
+ if (cache.has(value)) {
200
+ return;
201
+ }
202
+ cache.add(value);
203
+ }
204
+ return value;
205
+ }, indent);
206
+ cache.clear();
207
+ return json;
208
+ }
209
+ export {
210
+ OrtoniReport as default
211
+ };