ortoni-report 4.0.2-beta.1 → 4.1.0-test.1
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/{chunk-INS3E7E6.mjs → chunk-4RZ5C7KY.mjs} +402 -296
- package/dist/{cli/cli.js → cli.js} +404 -189
- package/dist/cli.mjs +208 -0
- package/dist/index.html +2 -2
- package/dist/ortoni-report.d.mts +8 -1
- package/dist/ortoni-report.d.ts +8 -1
- package/dist/ortoni-report.js +204 -89
- package/dist/ortoni-report.mjs +18 -15
- package/package.json +11 -4
- package/dist/chunk-45EJSEX2.mjs +0 -632
- package/dist/chunk-75EAJL2U.mjs +0 -632
- package/dist/chunk-A6HCKATU.mjs +0 -76
- package/dist/chunk-FGIYOFIC.mjs +0 -632
- package/dist/chunk-FHKWBHU6.mjs +0 -633
- package/dist/chunk-GLICR3VS.mjs +0 -637
- package/dist/chunk-HFO6XSKC.mjs +0 -633
- package/dist/chunk-HOZD6YIV.mjs +0 -634
- package/dist/chunk-IJO2YIFE.mjs +0 -637
- package/dist/chunk-JEIWNUQY.mjs +0 -632
- package/dist/chunk-JPLAGYR7.mjs +0 -632
- package/dist/chunk-NM6ULN2O.mjs +0 -632
- package/dist/chunk-OZS6QIJS.mjs +0 -638
- package/dist/chunk-P57227VN.mjs +0 -633
- package/dist/chunk-QMTRYN5N.js +0 -635
- package/dist/chunk-TI33PMMQ.mjs +0 -639
- package/dist/chunk-Z5NBP5TS.mjs +0 -635
- package/dist/cli/cli.cjs +0 -678
- package/dist/cli/cli.d.cts +0 -1
- package/dist/cli/cli.mjs +0 -103
- package/dist/ortoni-report.cjs +0 -2134
- package/dist/ortoni-report.d.cts +0 -111
- /package/dist/{cli/cli.d.mts → cli.d.mts} +0 -0
- /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
|
@@ -8,241 +8,14 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
});
|
|
9
9
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
10
10
|
|
|
11
|
-
// src/helpers/fileManager.ts
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import path from "path";
|
|
14
|
-
var FileManager = class {
|
|
15
|
-
constructor(folderPath) {
|
|
16
|
-
this.folderPath = folderPath;
|
|
17
|
-
}
|
|
18
|
-
ensureReportDirectory() {
|
|
19
|
-
const ortoniDataFolder = path.join(this.folderPath, "ortoni-data");
|
|
20
|
-
if (!fs.existsSync(this.folderPath)) {
|
|
21
|
-
fs.mkdirSync(this.folderPath, { recursive: true });
|
|
22
|
-
} else {
|
|
23
|
-
if (fs.existsSync(ortoniDataFolder)) {
|
|
24
|
-
fs.rmSync(ortoniDataFolder, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
writeReportFile(filename, data) {
|
|
29
|
-
const templatePath = path.join(__dirname, "..", "index.html");
|
|
30
|
-
console.log(templatePath);
|
|
31
|
-
let html = fs.readFileSync(templatePath, "utf-8");
|
|
32
|
-
const reportJSON = JSON.stringify({
|
|
33
|
-
data
|
|
34
|
-
});
|
|
35
|
-
html = html.replace("__ORTONI_TEST_REPORTDATA__", reportJSON);
|
|
36
|
-
console.log(`Writing report to ${reportJSON}`);
|
|
37
|
-
const outputDir = path.join(process.cwd(), this.folderPath);
|
|
38
|
-
if (!fs.existsSync(outputDir)) {
|
|
39
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
40
|
-
}
|
|
41
|
-
const outputPath = path.join(outputDir, filename);
|
|
42
|
-
fs.writeFileSync(outputPath, html);
|
|
43
|
-
return outputPath;
|
|
44
|
-
}
|
|
45
|
-
writeRawFile(filename, data) {
|
|
46
|
-
const outputPath = path.join(process.cwd(), this.folderPath, filename);
|
|
47
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
48
|
-
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
49
|
-
fs.writeFileSync(outputPath, content, "utf-8");
|
|
50
|
-
return outputPath;
|
|
51
|
-
}
|
|
52
|
-
copyTraceViewerAssets(skip) {
|
|
53
|
-
if (skip) return;
|
|
54
|
-
const traceViewerFolder = path.join(
|
|
55
|
-
__require.resolve("playwright-core"),
|
|
56
|
-
"..",
|
|
57
|
-
"lib",
|
|
58
|
-
"vite",
|
|
59
|
-
"traceViewer"
|
|
60
|
-
);
|
|
61
|
-
const traceViewerTargetFolder = path.join(this.folderPath, "trace");
|
|
62
|
-
const traceViewerAssetsTargetFolder = path.join(
|
|
63
|
-
traceViewerTargetFolder,
|
|
64
|
-
"assets"
|
|
65
|
-
);
|
|
66
|
-
fs.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
|
67
|
-
for (const file of fs.readdirSync(traceViewerFolder)) {
|
|
68
|
-
if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
|
|
69
|
-
continue;
|
|
70
|
-
fs.copyFileSync(
|
|
71
|
-
path.join(traceViewerFolder, file),
|
|
72
|
-
path.join(traceViewerTargetFolder, file)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
const assetsFolder = path.join(traceViewerFolder, "assets");
|
|
76
|
-
for (const file of fs.readdirSync(assetsFolder)) {
|
|
77
|
-
if (file.endsWith(".map") || file.includes("xtermModule")) continue;
|
|
78
|
-
fs.copyFileSync(
|
|
79
|
-
path.join(assetsFolder, file),
|
|
80
|
-
path.join(traceViewerAssetsTargetFolder, file)
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// src/utils/groupProjects.ts
|
|
87
|
-
function groupResults(config, results) {
|
|
88
|
-
if (config.showProject) {
|
|
89
|
-
const groupedResults = results.reduce((acc, result, index) => {
|
|
90
|
-
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
91
|
-
const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
|
|
92
|
-
const { filePath, suite, projectName } = result;
|
|
93
|
-
acc[filePath] = acc[filePath] || {};
|
|
94
|
-
acc[filePath][suite] = acc[filePath][suite] || {};
|
|
95
|
-
acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
|
|
96
|
-
acc[filePath][suite][projectName].push({ ...result, index, testId, key });
|
|
97
|
-
return acc;
|
|
98
|
-
}, {});
|
|
99
|
-
return groupedResults;
|
|
100
|
-
} else {
|
|
101
|
-
const groupedResults = results.reduce((acc, result, index) => {
|
|
102
|
-
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
103
|
-
const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
|
|
104
|
-
const { filePath, suite } = result;
|
|
105
|
-
acc[filePath] = acc[filePath] || {};
|
|
106
|
-
acc[filePath][suite] = acc[filePath][suite] || [];
|
|
107
|
-
acc[filePath][suite].push({ ...result, index, testId, key });
|
|
108
|
-
return acc;
|
|
109
|
-
}, {});
|
|
110
|
-
return groupedResults;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// src/helpers/HTMLGenerator.ts
|
|
115
|
-
var HTMLGenerator = class {
|
|
116
|
-
constructor(ortoniConfig, dbManager) {
|
|
117
|
-
this.ortoniConfig = ortoniConfig;
|
|
118
|
-
this.dbManager = dbManager;
|
|
119
|
-
}
|
|
120
|
-
async generateFinalReport(filteredResults, totalDuration, results, projectSet) {
|
|
121
|
-
const data = await this.prepareReportData(
|
|
122
|
-
filteredResults,
|
|
123
|
-
totalDuration,
|
|
124
|
-
results,
|
|
125
|
-
projectSet
|
|
126
|
-
);
|
|
127
|
-
return data;
|
|
128
|
-
}
|
|
129
|
-
async getReportData() {
|
|
130
|
-
return {
|
|
131
|
-
summary: await this.dbManager.getSummaryData(),
|
|
132
|
-
trends: await this.dbManager.getTrends(),
|
|
133
|
-
flakyTests: await this.dbManager.getFlakyTests(),
|
|
134
|
-
slowTests: await this.dbManager.getSlowTests()
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
138
|
-
const totalTests = filteredResults.length;
|
|
139
|
-
const passedTests = results.filter((r) => r.status === "passed").length;
|
|
140
|
-
const flakyTests = results.filter((r) => r.status === "flaky").length;
|
|
141
|
-
const failed = filteredResults.filter(
|
|
142
|
-
(r) => r.status === "failed" || r.status === "timedOut"
|
|
143
|
-
).length;
|
|
144
|
-
const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
145
|
-
const allTags = /* @__PURE__ */ new Set();
|
|
146
|
-
results.forEach(
|
|
147
|
-
(result) => result.testTags.forEach((tag) => allTags.add(tag))
|
|
148
|
-
);
|
|
149
|
-
const projectResults = this.calculateProjectResults(
|
|
150
|
-
filteredResults,
|
|
151
|
-
results,
|
|
152
|
-
projectSet
|
|
153
|
-
);
|
|
154
|
-
const lastRunDate = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
155
|
-
const testHistories = await Promise.all(
|
|
156
|
-
results.map(async (result) => {
|
|
157
|
-
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
158
|
-
const history = await this.dbManager.getTestHistory(testId);
|
|
159
|
-
return {
|
|
160
|
-
testId,
|
|
161
|
-
history
|
|
162
|
-
};
|
|
163
|
-
})
|
|
164
|
-
);
|
|
165
|
-
return {
|
|
166
|
-
summary: {
|
|
167
|
-
overAllResult: {
|
|
168
|
-
pass: passedTests,
|
|
169
|
-
fail: failed,
|
|
170
|
-
skip: results.filter((r) => r.status === "skipped").length,
|
|
171
|
-
retry: results.filter((r) => r.retryAttemptCount).length,
|
|
172
|
-
flaky: flakyTests,
|
|
173
|
-
total: filteredResults.length
|
|
174
|
-
},
|
|
175
|
-
successRate,
|
|
176
|
-
lastRunDate,
|
|
177
|
-
totalDuration,
|
|
178
|
-
stats: this.extractProjectStats(projectResults)
|
|
179
|
-
},
|
|
180
|
-
testResult: {
|
|
181
|
-
tests: groupResults(this.ortoniConfig, results),
|
|
182
|
-
testHistories,
|
|
183
|
-
allTags: Array.from(allTags),
|
|
184
|
-
set: projectSet
|
|
185
|
-
},
|
|
186
|
-
userConfig: {
|
|
187
|
-
projectName: this.ortoniConfig.projectName,
|
|
188
|
-
authorName: this.ortoniConfig.authorName,
|
|
189
|
-
type: this.ortoniConfig.testType,
|
|
190
|
-
title: this.ortoniConfig.title
|
|
191
|
-
},
|
|
192
|
-
userMeta: {
|
|
193
|
-
meta: this.ortoniConfig.meta
|
|
194
|
-
},
|
|
195
|
-
preferences: {
|
|
196
|
-
logo: this.ortoniConfig.logo || void 0,
|
|
197
|
-
showProject: this.ortoniConfig.showProject || false
|
|
198
|
-
},
|
|
199
|
-
analytics: {
|
|
200
|
-
reportData: await this.getReportData()
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
calculateProjectResults(filteredResults, results, projectSet) {
|
|
205
|
-
return Array.from(projectSet).map((projectName) => {
|
|
206
|
-
const projectTests = filteredResults.filter(
|
|
207
|
-
(r) => r.projectName === projectName
|
|
208
|
-
);
|
|
209
|
-
const allProjectTests = results.filter(
|
|
210
|
-
(r) => r.projectName === projectName
|
|
211
|
-
);
|
|
212
|
-
return {
|
|
213
|
-
projectName,
|
|
214
|
-
passedTests: projectTests.filter((r) => r.status === "passed").length,
|
|
215
|
-
failedTests: projectTests.filter(
|
|
216
|
-
(r) => r.status === "failed" || r.status === "timedOut"
|
|
217
|
-
).length,
|
|
218
|
-
skippedTests: allProjectTests.filter((r) => r.status === "skipped").length,
|
|
219
|
-
retryTests: allProjectTests.filter((r) => r.retryAttemptCount).length,
|
|
220
|
-
flakyTests: allProjectTests.filter((r) => r.status === "flaky").length,
|
|
221
|
-
totalTests: projectTests.length
|
|
222
|
-
};
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
extractProjectStats(projectResults) {
|
|
226
|
-
return {
|
|
227
|
-
projectNames: projectResults.map((result) => result.projectName),
|
|
228
|
-
totalTests: projectResults.map((result) => result.totalTests),
|
|
229
|
-
passedTests: projectResults.map((result) => result.passedTests),
|
|
230
|
-
failedTests: projectResults.map((result) => result.failedTests),
|
|
231
|
-
skippedTests: projectResults.map((result) => result.skippedTests),
|
|
232
|
-
retryTests: projectResults.map((result) => result.retryTests),
|
|
233
|
-
flakyTests: projectResults.map((result) => result.flakyTests)
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
|
|
238
11
|
// src/utils/utils.ts
|
|
239
|
-
import
|
|
12
|
+
import path from "path";
|
|
240
13
|
function normalizeFilePath(filePath) {
|
|
241
|
-
const normalizedPath =
|
|
242
|
-
return
|
|
14
|
+
const normalizedPath = path.normalize(filePath);
|
|
15
|
+
return path.basename(normalizedPath);
|
|
243
16
|
}
|
|
244
17
|
function ensureHtmlExtension(filename) {
|
|
245
|
-
const ext =
|
|
18
|
+
const ext = path.extname(filename);
|
|
246
19
|
if (ext && ext.toLowerCase() === ".html") {
|
|
247
20
|
return filename;
|
|
248
21
|
}
|
|
@@ -290,67 +63,6 @@ function extractSuites(titlePath) {
|
|
|
290
63
|
};
|
|
291
64
|
}
|
|
292
65
|
|
|
293
|
-
// src/utils/expressServer.ts
|
|
294
|
-
import express from "express";
|
|
295
|
-
import path3 from "path";
|
|
296
|
-
import { spawn } from "child_process";
|
|
297
|
-
function startReportServer(reportFolder, reportFilename, port = 2004, open2) {
|
|
298
|
-
const app = express();
|
|
299
|
-
app.use(express.static(reportFolder));
|
|
300
|
-
app.get("/", (_req, res) => {
|
|
301
|
-
try {
|
|
302
|
-
res.sendFile(path3.resolve(reportFolder, reportFilename));
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error("Ortoni-Report: Error sending report file:", error);
|
|
305
|
-
res.status(500).send("Error loading report");
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
try {
|
|
309
|
-
const server = app.listen(port, () => {
|
|
310
|
-
console.log(
|
|
311
|
-
`Server is running at http://localhost:${port}
|
|
312
|
-
Press Ctrl+C to stop.`
|
|
313
|
-
);
|
|
314
|
-
if (open2 === "always" || open2 === "on-failure") {
|
|
315
|
-
try {
|
|
316
|
-
openBrowser(`http://localhost:${port}`);
|
|
317
|
-
} catch (error) {
|
|
318
|
-
console.error("Ortoni-Report: Error opening browser:", error);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
server.on("error", (error) => {
|
|
323
|
-
if (error.code === "EADDRINUSE") {
|
|
324
|
-
console.error(
|
|
325
|
-
`Ortoni-Report: Port ${port} is already in use. Trying a different port...`
|
|
326
|
-
);
|
|
327
|
-
} else {
|
|
328
|
-
console.error("Ortoni-Report: Server error:", error);
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
} catch (error) {
|
|
332
|
-
console.error("Ortoni-Report: Error starting the server:", error);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
function openBrowser(url) {
|
|
336
|
-
const platform = process.platform;
|
|
337
|
-
let command;
|
|
338
|
-
try {
|
|
339
|
-
if (platform === "win32") {
|
|
340
|
-
command = "cmd";
|
|
341
|
-
spawn(command, ["/c", "start", url]);
|
|
342
|
-
} else if (platform === "darwin") {
|
|
343
|
-
command = "open";
|
|
344
|
-
spawn(command, [url]);
|
|
345
|
-
} else {
|
|
346
|
-
command = "xdg-open";
|
|
347
|
-
spawn(command, [url]);
|
|
348
|
-
}
|
|
349
|
-
} catch (error) {
|
|
350
|
-
console.error("Ortoni-Report: Error opening the browser:", error);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
66
|
// src/helpers/databaseManager.ts
|
|
355
67
|
import { open } from "sqlite";
|
|
356
68
|
import sqlite3 from "sqlite3";
|
|
@@ -625,14 +337,408 @@ var DatabaseManager = class {
|
|
|
625
337
|
}
|
|
626
338
|
};
|
|
627
339
|
|
|
340
|
+
// src/utils/groupProjects.ts
|
|
341
|
+
function groupResults(config, results) {
|
|
342
|
+
if (config.showProject) {
|
|
343
|
+
const groupedResults = results.reduce((acc, result, index) => {
|
|
344
|
+
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
345
|
+
const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
|
|
346
|
+
const { filePath, suite, projectName } = result;
|
|
347
|
+
acc[filePath] = acc[filePath] || {};
|
|
348
|
+
acc[filePath][suite] = acc[filePath][suite] || {};
|
|
349
|
+
acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
|
|
350
|
+
acc[filePath][suite][projectName].push({ ...result, index, testId, key });
|
|
351
|
+
return acc;
|
|
352
|
+
}, {});
|
|
353
|
+
return groupedResults;
|
|
354
|
+
} else {
|
|
355
|
+
const groupedResults = results.reduce((acc, result, index) => {
|
|
356
|
+
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
357
|
+
const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
|
|
358
|
+
const { filePath, suite } = result;
|
|
359
|
+
acc[filePath] = acc[filePath] || {};
|
|
360
|
+
acc[filePath][suite] = acc[filePath][suite] || [];
|
|
361
|
+
acc[filePath][suite].push({ ...result, index, testId, key });
|
|
362
|
+
return acc;
|
|
363
|
+
}, {});
|
|
364
|
+
return groupedResults;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/helpers/HTMLGenerator.ts
|
|
369
|
+
var HTMLGenerator = class {
|
|
370
|
+
constructor(ortoniConfig, dbManager) {
|
|
371
|
+
this.ortoniConfig = ortoniConfig;
|
|
372
|
+
this.dbManager = dbManager;
|
|
373
|
+
}
|
|
374
|
+
async generateFinalReport(filteredResults, totalDuration, results, projectSet) {
|
|
375
|
+
const data = await this.prepareReportData(
|
|
376
|
+
filteredResults,
|
|
377
|
+
totalDuration,
|
|
378
|
+
results,
|
|
379
|
+
projectSet
|
|
380
|
+
);
|
|
381
|
+
return data;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Return safe analytics/report data.
|
|
385
|
+
* If no dbManager is provided, return empty defaults and a note explaining why.
|
|
386
|
+
*/
|
|
387
|
+
async getReportData() {
|
|
388
|
+
if (!this.dbManager) {
|
|
389
|
+
return {
|
|
390
|
+
summary: {},
|
|
391
|
+
trends: {},
|
|
392
|
+
flakyTests: [],
|
|
393
|
+
slowTests: [],
|
|
394
|
+
note: "Test history/trends are unavailable (saveHistory disabled or DB not initialized)."
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const [summary, trends, flakyTests, slowTests] = await Promise.all([
|
|
399
|
+
this.dbManager.getSummaryData ? this.dbManager.getSummaryData() : Promise.resolve({}),
|
|
400
|
+
this.dbManager.getTrends ? this.dbManager.getTrends() : Promise.resolve({}),
|
|
401
|
+
this.dbManager.getFlakyTests ? this.dbManager.getFlakyTests() : Promise.resolve([]),
|
|
402
|
+
this.dbManager.getSlowTests ? this.dbManager.getSlowTests() : Promise.resolve([])
|
|
403
|
+
]);
|
|
404
|
+
return {
|
|
405
|
+
summary: summary ?? {},
|
|
406
|
+
trends: trends ?? {},
|
|
407
|
+
flakyTests: flakyTests ?? [],
|
|
408
|
+
slowTests: slowTests ?? []
|
|
409
|
+
};
|
|
410
|
+
} catch (err) {
|
|
411
|
+
console.warn(
|
|
412
|
+
"HTMLGenerator: failed to read analytics from DB, continuing without history.",
|
|
413
|
+
err
|
|
414
|
+
);
|
|
415
|
+
return {
|
|
416
|
+
summary: {},
|
|
417
|
+
trends: {},
|
|
418
|
+
flakyTests: [],
|
|
419
|
+
slowTests: [],
|
|
420
|
+
note: "Test history/trends could not be loaded due to a DB error."
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
425
|
+
const totalTests = filteredResults.length;
|
|
426
|
+
const passedTests = results.filter((r) => r.status === "passed").length;
|
|
427
|
+
const flakyTests = results.filter((r) => r.status === "flaky").length;
|
|
428
|
+
const failed = filteredResults.filter(
|
|
429
|
+
(r) => r.status === "failed" || r.status === "timedOut"
|
|
430
|
+
).length;
|
|
431
|
+
const successRate = totalTests === 0 ? "0.00" : ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
432
|
+
const allTags = /* @__PURE__ */ new Set();
|
|
433
|
+
results.forEach(
|
|
434
|
+
(result) => (result.testTags || []).forEach((tag) => allTags.add(tag))
|
|
435
|
+
);
|
|
436
|
+
const projectResults = this.calculateProjectResults(
|
|
437
|
+
filteredResults,
|
|
438
|
+
results,
|
|
439
|
+
projectSet
|
|
440
|
+
);
|
|
441
|
+
const lastRunDate = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
442
|
+
const testHistories = await Promise.all(
|
|
443
|
+
results.map(async (result) => {
|
|
444
|
+
const testId = `${result.filePath}:${result.projectName}:${result.title}`;
|
|
445
|
+
if (!this.dbManager || !this.dbManager.getTestHistory) {
|
|
446
|
+
return {
|
|
447
|
+
testId,
|
|
448
|
+
history: []
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
const history = await this.dbManager.getTestHistory(testId);
|
|
453
|
+
return {
|
|
454
|
+
testId,
|
|
455
|
+
history: history ?? []
|
|
456
|
+
};
|
|
457
|
+
} catch (err) {
|
|
458
|
+
console.warn(
|
|
459
|
+
`HTMLGenerator: failed to read history for ${testId}`,
|
|
460
|
+
err
|
|
461
|
+
);
|
|
462
|
+
return {
|
|
463
|
+
testId,
|
|
464
|
+
history: []
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
);
|
|
469
|
+
const reportData = await this.getReportData();
|
|
470
|
+
return {
|
|
471
|
+
summary: {
|
|
472
|
+
overAllResult: {
|
|
473
|
+
pass: passedTests,
|
|
474
|
+
fail: failed,
|
|
475
|
+
skip: results.filter((r) => r.status === "skipped").length,
|
|
476
|
+
retry: results.filter((r) => r.retryAttemptCount).length,
|
|
477
|
+
flaky: flakyTests,
|
|
478
|
+
total: filteredResults.length
|
|
479
|
+
},
|
|
480
|
+
successRate,
|
|
481
|
+
lastRunDate,
|
|
482
|
+
totalDuration,
|
|
483
|
+
stats: this.extractProjectStats(projectResults)
|
|
484
|
+
},
|
|
485
|
+
testResult: {
|
|
486
|
+
tests: groupResults(this.ortoniConfig, results),
|
|
487
|
+
testHistories,
|
|
488
|
+
allTags: Array.from(allTags),
|
|
489
|
+
set: projectSet
|
|
490
|
+
},
|
|
491
|
+
userConfig: {
|
|
492
|
+
projectName: this.ortoniConfig.projectName,
|
|
493
|
+
authorName: this.ortoniConfig.authorName,
|
|
494
|
+
type: this.ortoniConfig.testType,
|
|
495
|
+
title: this.ortoniConfig.title
|
|
496
|
+
},
|
|
497
|
+
userMeta: {
|
|
498
|
+
meta: this.ortoniConfig.meta
|
|
499
|
+
},
|
|
500
|
+
preferences: {
|
|
501
|
+
logo: this.ortoniConfig.logo || void 0,
|
|
502
|
+
showProject: this.ortoniConfig.showProject || false
|
|
503
|
+
},
|
|
504
|
+
analytics: {
|
|
505
|
+
reportData
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
calculateProjectResults(filteredResults, results, projectSet) {
|
|
510
|
+
return Array.from(projectSet).map((projectName) => {
|
|
511
|
+
const projectTests = filteredResults.filter(
|
|
512
|
+
(r) => r.projectName === projectName
|
|
513
|
+
);
|
|
514
|
+
const allProjectTests = results.filter(
|
|
515
|
+
(r) => r.projectName === projectName
|
|
516
|
+
);
|
|
517
|
+
return {
|
|
518
|
+
projectName,
|
|
519
|
+
passedTests: projectTests.filter((r) => r.status === "passed").length,
|
|
520
|
+
failedTests: projectTests.filter(
|
|
521
|
+
(r) => r.status === "failed" || r.status === "timedOut"
|
|
522
|
+
).length,
|
|
523
|
+
skippedTests: allProjectTests.filter((r) => r.status === "skipped").length,
|
|
524
|
+
retryTests: allProjectTests.filter((r) => r.retryAttemptCount).length,
|
|
525
|
+
flakyTests: allProjectTests.filter((r) => r.status === "flaky").length,
|
|
526
|
+
totalTests: projectTests.length
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
extractProjectStats(projectResults) {
|
|
531
|
+
return {
|
|
532
|
+
projectNames: projectResults.map((result) => result.projectName),
|
|
533
|
+
totalTests: projectResults.map((result) => result.totalTests),
|
|
534
|
+
passedTests: projectResults.map((result) => result.passedTests),
|
|
535
|
+
failedTests: projectResults.map((result) => result.failedTests),
|
|
536
|
+
skippedTests: projectResults.map((result) => result.skippedTests),
|
|
537
|
+
retryTests: projectResults.map((result) => result.retryTests),
|
|
538
|
+
flakyTests: projectResults.map((result) => result.flakyTests)
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/helpers/fileManager.ts
|
|
544
|
+
import fs2 from "fs";
|
|
545
|
+
import path3 from "path";
|
|
546
|
+
|
|
547
|
+
// src/helpers/templateLoader.ts
|
|
548
|
+
import fs from "fs";
|
|
549
|
+
import path2 from "path";
|
|
550
|
+
async function readBundledTemplate(pkgName = "ortoni-report") {
|
|
551
|
+
const packagedRel = "dist/index.html";
|
|
552
|
+
try {
|
|
553
|
+
if (typeof __require === "function") {
|
|
554
|
+
const resolved = __require.resolve(`${pkgName}/${packagedRel}`);
|
|
555
|
+
if (fs.existsSync(resolved)) {
|
|
556
|
+
return fs.readFileSync(resolved, "utf-8");
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
const moduleNS = await import("module");
|
|
563
|
+
if (moduleNS && typeof moduleNS.createRequire === "function") {
|
|
564
|
+
const createRequire = moduleNS.createRequire;
|
|
565
|
+
const req = createRequire(
|
|
566
|
+
// @ts-ignore
|
|
567
|
+
typeof __filename !== "undefined" ? __filename : import.meta.url
|
|
568
|
+
);
|
|
569
|
+
const resolved = req.resolve(`${pkgName}/${packagedRel}`);
|
|
570
|
+
if (fs.existsSync(resolved)) {
|
|
571
|
+
return fs.readFileSync(resolved, "utf-8");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
const here = path2.resolve(__dirname, "../dist/index.html");
|
|
578
|
+
if (fs.existsSync(here)) return fs.readFileSync(here, "utf-8");
|
|
579
|
+
} catch {
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
const nm = path2.join(process.cwd(), "node_modules", pkgName, packagedRel);
|
|
583
|
+
if (fs.existsSync(nm)) return fs.readFileSync(nm, "utf-8");
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
const alt = path2.join(process.cwd(), "dist", "index.html");
|
|
588
|
+
if (fs.existsSync(alt)) return fs.readFileSync(alt, "utf-8");
|
|
589
|
+
} catch {
|
|
590
|
+
}
|
|
591
|
+
throw new Error(
|
|
592
|
+
`ortoni-report template not found (tried:
|
|
593
|
+
- require.resolve('${pkgName}/${packagedRel}')
|
|
594
|
+
- import('module').createRequire(...).resolve('${pkgName}/${packagedRel}')
|
|
595
|
+
- relative ../dist/index.html
|
|
596
|
+
- ${path2.join(
|
|
597
|
+
process.cwd(),
|
|
598
|
+
"node_modules",
|
|
599
|
+
pkgName,
|
|
600
|
+
packagedRel
|
|
601
|
+
)}
|
|
602
|
+
- ${path2.join(process.cwd(), "dist", "index.html")}
|
|
603
|
+
Ensure 'dist/index.html' is present in the published package and package.json 'files' includes 'dist/'.`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/helpers/fileManager.ts
|
|
608
|
+
var FileManager = class {
|
|
609
|
+
constructor(folderPath) {
|
|
610
|
+
this.folderPath = folderPath;
|
|
611
|
+
}
|
|
612
|
+
ensureReportDirectory() {
|
|
613
|
+
const ortoniDataFolder = path3.join(this.folderPath, "ortoni-data");
|
|
614
|
+
if (!fs2.existsSync(this.folderPath)) {
|
|
615
|
+
fs2.mkdirSync(this.folderPath, { recursive: true });
|
|
616
|
+
} else {
|
|
617
|
+
if (fs2.existsSync(ortoniDataFolder)) {
|
|
618
|
+
fs2.rmSync(ortoniDataFolder, { recursive: true, force: true });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async writeReportFile(filename, data) {
|
|
623
|
+
let html = await readBundledTemplate();
|
|
624
|
+
const reportJSON = JSON.stringify({
|
|
625
|
+
data
|
|
626
|
+
});
|
|
627
|
+
html = html.replace("__ORTONI_TEST_REPORTDATA__", reportJSON);
|
|
628
|
+
const outputPath = path3.join(process.cwd(), this.folderPath, filename);
|
|
629
|
+
fs2.writeFileSync(outputPath, html);
|
|
630
|
+
return outputPath;
|
|
631
|
+
}
|
|
632
|
+
writeRawFile(filename, data) {
|
|
633
|
+
const outputPath = path3.join(process.cwd(), this.folderPath, filename);
|
|
634
|
+
fs2.mkdirSync(path3.dirname(outputPath), { recursive: true });
|
|
635
|
+
const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
636
|
+
fs2.writeFileSync(outputPath, content, "utf-8");
|
|
637
|
+
return outputPath;
|
|
638
|
+
}
|
|
639
|
+
copyTraceViewerAssets(skip) {
|
|
640
|
+
if (skip) return;
|
|
641
|
+
const traceViewerFolder = path3.join(
|
|
642
|
+
__require.resolve("playwright-core"),
|
|
643
|
+
"..",
|
|
644
|
+
"lib",
|
|
645
|
+
"vite",
|
|
646
|
+
"traceViewer"
|
|
647
|
+
);
|
|
648
|
+
const traceViewerTargetFolder = path3.join(this.folderPath, "trace");
|
|
649
|
+
const traceViewerAssetsTargetFolder = path3.join(
|
|
650
|
+
traceViewerTargetFolder,
|
|
651
|
+
"assets"
|
|
652
|
+
);
|
|
653
|
+
fs2.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
|
654
|
+
for (const file of fs2.readdirSync(traceViewerFolder)) {
|
|
655
|
+
if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
|
|
656
|
+
continue;
|
|
657
|
+
fs2.copyFileSync(
|
|
658
|
+
path3.join(traceViewerFolder, file),
|
|
659
|
+
path3.join(traceViewerTargetFolder, file)
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
const assetsFolder = path3.join(traceViewerFolder, "assets");
|
|
663
|
+
for (const file of fs2.readdirSync(assetsFolder)) {
|
|
664
|
+
if (file.endsWith(".map") || file.includes("xtermModule")) continue;
|
|
665
|
+
fs2.copyFileSync(
|
|
666
|
+
path3.join(assetsFolder, file),
|
|
667
|
+
path3.join(traceViewerAssetsTargetFolder, file)
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// src/utils/expressServer.ts
|
|
674
|
+
import express from "express";
|
|
675
|
+
import path4 from "path";
|
|
676
|
+
import { spawn } from "child_process";
|
|
677
|
+
function startReportServer(reportFolder, reportFilename, port = 2004, open2) {
|
|
678
|
+
const app = express();
|
|
679
|
+
app.use(express.static(reportFolder, { index: false }));
|
|
680
|
+
app.get("/", (_req, res) => {
|
|
681
|
+
try {
|
|
682
|
+
res.sendFile(path4.resolve(reportFolder, reportFilename));
|
|
683
|
+
} catch (error) {
|
|
684
|
+
console.error("Ortoni Report: Error sending report file:", error);
|
|
685
|
+
res.status(500).send("Error loading report");
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
try {
|
|
689
|
+
const server = app.listen(port, () => {
|
|
690
|
+
console.log(
|
|
691
|
+
`Server is running at http://localhost:${port}
|
|
692
|
+
Press Ctrl+C to stop.`
|
|
693
|
+
);
|
|
694
|
+
if (open2 === "always" || open2 === "on-failure") {
|
|
695
|
+
try {
|
|
696
|
+
openBrowser(`http://localhost:${port}`);
|
|
697
|
+
} catch (error) {
|
|
698
|
+
console.error("Ortoni Report: Error opening browser:", error);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
server.on("error", (error) => {
|
|
703
|
+
if (error.code === "EADDRINUSE") {
|
|
704
|
+
console.error(
|
|
705
|
+
`Ortoni Report: Port ${port} is already in use. Trying a different port...`
|
|
706
|
+
);
|
|
707
|
+
} else {
|
|
708
|
+
console.error("Ortoni Report: Server error:", error);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
} catch (error) {
|
|
712
|
+
console.error("Ortoni Report: Error starting the server:", error);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function openBrowser(url) {
|
|
716
|
+
const platform = process.platform;
|
|
717
|
+
let command;
|
|
718
|
+
try {
|
|
719
|
+
if (platform === "win32") {
|
|
720
|
+
command = "cmd";
|
|
721
|
+
spawn(command, ["/c", "start", url]);
|
|
722
|
+
} else if (platform === "darwin") {
|
|
723
|
+
command = "open";
|
|
724
|
+
spawn(command, [url]);
|
|
725
|
+
} else {
|
|
726
|
+
command = "xdg-open";
|
|
727
|
+
spawn(command, [url]);
|
|
728
|
+
}
|
|
729
|
+
} catch (error) {
|
|
730
|
+
console.error("Ortoni Report: Error opening the browser:", error);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
628
734
|
export {
|
|
629
735
|
__publicField,
|
|
630
|
-
FileManager,
|
|
631
|
-
HTMLGenerator,
|
|
632
736
|
normalizeFilePath,
|
|
633
737
|
ensureHtmlExtension,
|
|
634
738
|
escapeHtml,
|
|
635
739
|
extractSuites,
|
|
636
|
-
|
|
637
|
-
|
|
740
|
+
DatabaseManager,
|
|
741
|
+
HTMLGenerator,
|
|
742
|
+
FileManager,
|
|
743
|
+
startReportServer
|
|
638
744
|
};
|