ortoni-report 4.0.2-beta.1 → 4.0.3

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.
Files changed (62) hide show
  1. package/changelog.md +27 -0
  2. package/dist/{chunk-INS3E7E6.mjs → chunk-4RZ5C7KY.mjs} +402 -296
  3. package/dist/{cli/cli.js → cli.js} +405 -190
  4. package/dist/cli.mjs +208 -0
  5. package/dist/helpers/HTMLGenerator.d.ts +89 -0
  6. package/dist/helpers/HTMLGenerator.js +163 -0
  7. package/dist/helpers/databaseManager.d.ts +35 -0
  8. package/dist/helpers/databaseManager.js +268 -0
  9. package/dist/helpers/fileManager.d.ts +8 -0
  10. package/dist/helpers/fileManager.js +60 -0
  11. package/dist/helpers/markdownConverter.d.ts +1 -0
  12. package/dist/helpers/markdownConverter.js +14 -0
  13. package/dist/helpers/resultProcessor.d.ts +10 -0
  14. package/dist/helpers/resultProcessor.js +60 -0
  15. package/dist/helpers/serverManager.d.ts +6 -0
  16. package/dist/helpers/serverManager.js +15 -0
  17. package/dist/helpers/templateLoader.d.ts +15 -0
  18. package/dist/helpers/templateLoader.js +88 -0
  19. package/dist/index.html +1 -1
  20. package/dist/mergeData.d.ts +13 -0
  21. package/dist/mergeData.js +182 -0
  22. package/dist/ortoni-report.d.mts +8 -1
  23. package/dist/ortoni-report.d.ts +8 -1
  24. package/dist/ortoni-report.js +211 -96
  25. package/dist/ortoni-report.mjs +25 -22
  26. package/dist/{ortoni-report.d.cts → types/reporterConfig.d.ts} +8 -33
  27. package/dist/types/reporterConfig.js +1 -0
  28. package/dist/types/testResults.d.ts +31 -0
  29. package/dist/types/testResults.js +1 -0
  30. package/dist/utils/attachFiles.d.ts +4 -0
  31. package/dist/utils/attachFiles.js +87 -0
  32. package/dist/utils/expressServer.d.ts +1 -0
  33. package/dist/utils/expressServer.js +61 -0
  34. package/dist/utils/groupProjects.d.ts +3 -0
  35. package/dist/utils/groupProjects.js +30 -0
  36. package/dist/utils/utils.d.ts +15 -0
  37. package/dist/utils/utils.js +84 -0
  38. package/package.json +11 -4
  39. package/readme.md +60 -75
  40. package/dist/chunk-45EJSEX2.mjs +0 -632
  41. package/dist/chunk-75EAJL2U.mjs +0 -632
  42. package/dist/chunk-A6HCKATU.mjs +0 -76
  43. package/dist/chunk-FGIYOFIC.mjs +0 -632
  44. package/dist/chunk-FHKWBHU6.mjs +0 -633
  45. package/dist/chunk-GLICR3VS.mjs +0 -637
  46. package/dist/chunk-HFO6XSKC.mjs +0 -633
  47. package/dist/chunk-HOZD6YIV.mjs +0 -634
  48. package/dist/chunk-IJO2YIFE.mjs +0 -637
  49. package/dist/chunk-JEIWNUQY.mjs +0 -632
  50. package/dist/chunk-JPLAGYR7.mjs +0 -632
  51. package/dist/chunk-NM6ULN2O.mjs +0 -632
  52. package/dist/chunk-OZS6QIJS.mjs +0 -638
  53. package/dist/chunk-P57227VN.mjs +0 -633
  54. package/dist/chunk-QMTRYN5N.js +0 -635
  55. package/dist/chunk-TI33PMMQ.mjs +0 -639
  56. package/dist/chunk-Z5NBP5TS.mjs +0 -635
  57. package/dist/cli/cli.cjs +0 -678
  58. package/dist/cli/cli.d.cts +0 -1
  59. package/dist/cli/cli.mjs +0 -103
  60. package/dist/ortoni-report.cjs +0 -2134
  61. /package/dist/{cli/cli.d.mts → cli.d.mts} +0 -0
  62. /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
@@ -1,639 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
- }) : x)(function(x) {
6
- if (typeof require !== "undefined") return require.apply(this, arguments);
7
- throw Error('Dynamic require of "' + x + '" is not supported');
8
- });
9
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
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
- console.log(`Writing report to ${outputPath}`);
43
- fs.writeFileSync(outputPath, html);
44
- return outputPath;
45
- }
46
- writeRawFile(filename, data) {
47
- const outputPath = path.join(process.cwd(), this.folderPath, filename);
48
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
49
- const content = typeof data === "string" ? data : JSON.stringify(data, null, 2);
50
- fs.writeFileSync(outputPath, content, "utf-8");
51
- return outputPath;
52
- }
53
- copyTraceViewerAssets(skip) {
54
- if (skip) return;
55
- const traceViewerFolder = path.join(
56
- __require.resolve("playwright-core"),
57
- "..",
58
- "lib",
59
- "vite",
60
- "traceViewer"
61
- );
62
- const traceViewerTargetFolder = path.join(this.folderPath, "trace");
63
- const traceViewerAssetsTargetFolder = path.join(
64
- traceViewerTargetFolder,
65
- "assets"
66
- );
67
- fs.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
68
- for (const file of fs.readdirSync(traceViewerFolder)) {
69
- if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
70
- continue;
71
- fs.copyFileSync(
72
- path.join(traceViewerFolder, file),
73
- path.join(traceViewerTargetFolder, file)
74
- );
75
- }
76
- const assetsFolder = path.join(traceViewerFolder, "assets");
77
- for (const file of fs.readdirSync(assetsFolder)) {
78
- if (file.endsWith(".map") || file.includes("xtermModule")) continue;
79
- fs.copyFileSync(
80
- path.join(assetsFolder, file),
81
- path.join(traceViewerAssetsTargetFolder, file)
82
- );
83
- }
84
- }
85
- };
86
-
87
- // src/utils/groupProjects.ts
88
- function groupResults(config, results) {
89
- if (config.showProject) {
90
- const groupedResults = results.reduce((acc, result, index) => {
91
- const testId = `${result.filePath}:${result.projectName}:${result.title}`;
92
- const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
93
- const { filePath, suite, projectName } = result;
94
- acc[filePath] = acc[filePath] || {};
95
- acc[filePath][suite] = acc[filePath][suite] || {};
96
- acc[filePath][suite][projectName] = acc[filePath][suite][projectName] || [];
97
- acc[filePath][suite][projectName].push({ ...result, index, testId, key });
98
- return acc;
99
- }, {});
100
- return groupedResults;
101
- } else {
102
- const groupedResults = results.reduce((acc, result, index) => {
103
- const testId = `${result.filePath}:${result.projectName}:${result.title}`;
104
- const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
105
- const { filePath, suite } = result;
106
- acc[filePath] = acc[filePath] || {};
107
- acc[filePath][suite] = acc[filePath][suite] || [];
108
- acc[filePath][suite].push({ ...result, index, testId, key });
109
- return acc;
110
- }, {});
111
- return groupedResults;
112
- }
113
- }
114
-
115
- // src/helpers/HTMLGenerator.ts
116
- var HTMLGenerator = class {
117
- constructor(ortoniConfig, dbManager) {
118
- this.ortoniConfig = ortoniConfig;
119
- this.dbManager = dbManager;
120
- }
121
- async generateFinalReport(filteredResults, totalDuration, results, projectSet) {
122
- const data = await this.prepareReportData(
123
- filteredResults,
124
- totalDuration,
125
- results,
126
- projectSet
127
- );
128
- return data;
129
- }
130
- async getReportData() {
131
- return {
132
- summary: await this.dbManager.getSummaryData(),
133
- trends: await this.dbManager.getTrends(),
134
- flakyTests: await this.dbManager.getFlakyTests(),
135
- slowTests: await this.dbManager.getSlowTests()
136
- };
137
- }
138
- async prepareReportData(filteredResults, totalDuration, results, projectSet) {
139
- const totalTests = filteredResults.length;
140
- const passedTests = results.filter((r) => r.status === "passed").length;
141
- const flakyTests = results.filter((r) => r.status === "flaky").length;
142
- const failed = filteredResults.filter(
143
- (r) => r.status === "failed" || r.status === "timedOut"
144
- ).length;
145
- const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
146
- const allTags = /* @__PURE__ */ new Set();
147
- results.forEach(
148
- (result) => result.testTags.forEach((tag) => allTags.add(tag))
149
- );
150
- const projectResults = this.calculateProjectResults(
151
- filteredResults,
152
- results,
153
- projectSet
154
- );
155
- const lastRunDate = (/* @__PURE__ */ new Date()).toLocaleString();
156
- const testHistories = await Promise.all(
157
- results.map(async (result) => {
158
- const testId = `${result.filePath}:${result.projectName}:${result.title}`;
159
- const history = await this.dbManager.getTestHistory(testId);
160
- return {
161
- testId,
162
- history
163
- };
164
- })
165
- );
166
- return {
167
- summary: {
168
- overAllResult: {
169
- pass: passedTests,
170
- fail: failed,
171
- skip: results.filter((r) => r.status === "skipped").length,
172
- retry: results.filter((r) => r.retryAttemptCount).length,
173
- flaky: flakyTests,
174
- total: filteredResults.length
175
- },
176
- successRate,
177
- lastRunDate,
178
- totalDuration,
179
- stats: this.extractProjectStats(projectResults)
180
- },
181
- testResult: {
182
- tests: groupResults(this.ortoniConfig, results),
183
- testHistories,
184
- allTags: Array.from(allTags),
185
- set: projectSet
186
- },
187
- userConfig: {
188
- projectName: this.ortoniConfig.projectName,
189
- authorName: this.ortoniConfig.authorName,
190
- type: this.ortoniConfig.testType,
191
- title: this.ortoniConfig.title
192
- },
193
- userMeta: {
194
- meta: this.ortoniConfig.meta
195
- },
196
- preferences: {
197
- logo: this.ortoniConfig.logo || void 0,
198
- showProject: this.ortoniConfig.showProject || false
199
- },
200
- analytics: {
201
- reportData: await this.getReportData()
202
- }
203
- };
204
- }
205
- calculateProjectResults(filteredResults, results, projectSet) {
206
- return Array.from(projectSet).map((projectName) => {
207
- const projectTests = filteredResults.filter(
208
- (r) => r.projectName === projectName
209
- );
210
- const allProjectTests = results.filter(
211
- (r) => r.projectName === projectName
212
- );
213
- return {
214
- projectName,
215
- passedTests: projectTests.filter((r) => r.status === "passed").length,
216
- failedTests: projectTests.filter(
217
- (r) => r.status === "failed" || r.status === "timedOut"
218
- ).length,
219
- skippedTests: allProjectTests.filter((r) => r.status === "skipped").length,
220
- retryTests: allProjectTests.filter((r) => r.retryAttemptCount).length,
221
- flakyTests: allProjectTests.filter((r) => r.status === "flaky").length,
222
- totalTests: projectTests.length
223
- };
224
- });
225
- }
226
- extractProjectStats(projectResults) {
227
- return {
228
- projectNames: projectResults.map((result) => result.projectName),
229
- totalTests: projectResults.map((result) => result.totalTests),
230
- passedTests: projectResults.map((result) => result.passedTests),
231
- failedTests: projectResults.map((result) => result.failedTests),
232
- skippedTests: projectResults.map((result) => result.skippedTests),
233
- retryTests: projectResults.map((result) => result.retryTests),
234
- flakyTests: projectResults.map((result) => result.flakyTests)
235
- };
236
- }
237
- };
238
-
239
- // src/utils/utils.ts
240
- import path2 from "path";
241
- function normalizeFilePath(filePath) {
242
- const normalizedPath = path2.normalize(filePath);
243
- return path2.basename(normalizedPath);
244
- }
245
- function ensureHtmlExtension(filename) {
246
- const ext = path2.extname(filename);
247
- if (ext && ext.toLowerCase() === ".html") {
248
- return filename;
249
- }
250
- return `${filename}.html`;
251
- }
252
- function escapeHtml(unsafe) {
253
- if (typeof unsafe !== "string") {
254
- return String(unsafe);
255
- }
256
- return unsafe.replace(/[&<"']/g, function(match) {
257
- const escapeMap = {
258
- "&": "&amp;",
259
- "<": "&lt;",
260
- ">": "&gt;",
261
- '"': "&quot;",
262
- "'": "&#039;"
263
- };
264
- return escapeMap[match] || match;
265
- });
266
- }
267
- function formatDateLocal(dateInput) {
268
- const date = typeof dateInput === "string" ? new Date(dateInput) : dateInput;
269
- const options = {
270
- year: "numeric",
271
- month: "short",
272
- day: "2-digit",
273
- hour: "2-digit",
274
- minute: "2-digit",
275
- hour12: true,
276
- timeZoneName: "short"
277
- // or "Asia/Kolkata"
278
- };
279
- return new Intl.DateTimeFormat(void 0, options).format(date);
280
- }
281
- function extractSuites(titlePath) {
282
- const tagPattern = /@[\w]+/g;
283
- const suiteParts = titlePath.slice(3, titlePath.length - 1).map((p) => p.replace(tagPattern, "").trim());
284
- return {
285
- hierarchy: suiteParts.join(" > "),
286
- // full hierarchy
287
- topLevelSuite: suiteParts[0] ?? "",
288
- // first suite
289
- parentSuite: suiteParts[suiteParts.length - 1] ?? ""
290
- // last suite
291
- };
292
- }
293
-
294
- // src/utils/expressServer.ts
295
- import express from "express";
296
- import path3 from "path";
297
- import { spawn } from "child_process";
298
- function startReportServer(reportFolder, reportFilename, port = 2004, open2) {
299
- const app = express();
300
- app.use(express.static(reportFolder));
301
- app.get("/", (_req, res) => {
302
- try {
303
- res.sendFile(path3.resolve(reportFolder, reportFilename));
304
- } catch (error) {
305
- console.error("Ortoni-Report: Error sending report file:", error);
306
- res.status(500).send("Error loading report");
307
- }
308
- });
309
- try {
310
- const server = app.listen(port, () => {
311
- console.log(
312
- `Server is running at http://localhost:${port}
313
- Press Ctrl+C to stop.`
314
- );
315
- if (open2 === "always" || open2 === "on-failure") {
316
- try {
317
- openBrowser(`http://localhost:${port}`);
318
- } catch (error) {
319
- console.error("Ortoni-Report: Error opening browser:", error);
320
- }
321
- }
322
- });
323
- server.on("error", (error) => {
324
- if (error.code === "EADDRINUSE") {
325
- console.error(
326
- `Ortoni-Report: Port ${port} is already in use. Trying a different port...`
327
- );
328
- } else {
329
- console.error("Ortoni-Report: Server error:", error);
330
- }
331
- });
332
- } catch (error) {
333
- console.error("Ortoni-Report: Error starting the server:", error);
334
- }
335
- }
336
- function openBrowser(url) {
337
- const platform = process.platform;
338
- let command;
339
- try {
340
- if (platform === "win32") {
341
- command = "cmd";
342
- spawn(command, ["/c", "start", url]);
343
- } else if (platform === "darwin") {
344
- command = "open";
345
- spawn(command, [url]);
346
- } else {
347
- command = "xdg-open";
348
- spawn(command, [url]);
349
- }
350
- } catch (error) {
351
- console.error("Ortoni-Report: Error opening the browser:", error);
352
- }
353
- }
354
-
355
- // src/helpers/databaseManager.ts
356
- import { open } from "sqlite";
357
- import sqlite3 from "sqlite3";
358
- var DatabaseManager = class {
359
- constructor() {
360
- this.db = null;
361
- }
362
- async initialize(dbPath) {
363
- try {
364
- this.db = await open({
365
- filename: dbPath,
366
- driver: sqlite3.Database
367
- });
368
- await this.createTables();
369
- await this.createIndexes();
370
- } catch (error) {
371
- console.error("OrtoniReport: Error initializing database:", error);
372
- }
373
- }
374
- async createTables() {
375
- if (!this.db) {
376
- console.error("OrtoniReport: Database not initialized");
377
- return;
378
- }
379
- try {
380
- await this.db.exec(`
381
- CREATE TABLE IF NOT EXISTS test_runs (
382
- id INTEGER PRIMARY KEY AUTOINCREMENT,
383
- run_date TEXT
384
- );
385
-
386
- CREATE TABLE IF NOT EXISTS test_results (
387
- id INTEGER PRIMARY KEY AUTOINCREMENT,
388
- run_id INTEGER,
389
- test_id TEXT,
390
- status TEXT,
391
- duration INTEGER, -- store duration as raw ms
392
- error_message TEXT,
393
- FOREIGN KEY (run_id) REFERENCES test_runs (id)
394
- );
395
- `);
396
- } catch (error) {
397
- console.error("OrtoniReport: Error creating tables:", error);
398
- }
399
- }
400
- async createIndexes() {
401
- if (!this.db) {
402
- console.error("OrtoniReport: Database not initialized");
403
- return;
404
- }
405
- try {
406
- await this.db.exec(`
407
- CREATE INDEX IF NOT EXISTS idx_test_id ON test_results (test_id);
408
- CREATE INDEX IF NOT EXISTS idx_run_id ON test_results (run_id);
409
- `);
410
- } catch (error) {
411
- console.error("OrtoniReport: Error creating indexes:", error);
412
- }
413
- }
414
- async saveTestRun() {
415
- if (!this.db) {
416
- console.error("OrtoniReport: Database not initialized");
417
- return null;
418
- }
419
- try {
420
- const runDate = (/* @__PURE__ */ new Date()).toISOString();
421
- const { lastID } = await this.db.run(
422
- `
423
- INSERT INTO test_runs (run_date)
424
- VALUES (?)
425
- `,
426
- [runDate]
427
- );
428
- return lastID;
429
- } catch (error) {
430
- console.error("OrtoniReport: Error saving test run:", error);
431
- return null;
432
- }
433
- }
434
- async saveTestResults(runId, results) {
435
- if (!this.db) {
436
- console.error("OrtoniReport: Database not initialized");
437
- return;
438
- }
439
- try {
440
- await this.db.exec("BEGIN TRANSACTION;");
441
- const stmt = await this.db.prepare(`
442
- INSERT INTO test_results (run_id, test_id, status, duration, error_message)
443
- VALUES (?, ?, ?, ?, ?)
444
- `);
445
- for (const result of results) {
446
- await stmt.run([
447
- runId,
448
- `${result.filePath}:${result.projectName}:${result.title}`,
449
- result.status,
450
- result.duration,
451
- // store raw ms
452
- result.errors.join("\n")
453
- ]);
454
- }
455
- await stmt.finalize();
456
- await this.db.exec("COMMIT;");
457
- } catch (error) {
458
- await this.db.exec("ROLLBACK;");
459
- console.error("OrtoniReport: Error saving test results:", error);
460
- }
461
- }
462
- async getTestHistory(testId, limit = 10) {
463
- if (!this.db) {
464
- console.error("OrtoniReport: Database not initialized");
465
- return [];
466
- }
467
- try {
468
- const results = await this.db.all(
469
- `
470
- SELECT tr.status, tr.duration, tr.error_message, trun.run_date
471
- FROM test_results tr
472
- JOIN test_runs trun ON tr.run_id = trun.id
473
- WHERE tr.test_id = ?
474
- ORDER BY trun.run_date DESC
475
- LIMIT ?
476
- `,
477
- [testId, limit]
478
- );
479
- return results.map((result) => ({
480
- ...result,
481
- run_date: formatDateLocal(result.run_date)
482
- }));
483
- } catch (error) {
484
- console.error("OrtoniReport: Error getting test history:", error);
485
- return [];
486
- }
487
- }
488
- async close() {
489
- if (this.db) {
490
- try {
491
- await this.db.close();
492
- } catch (error) {
493
- console.error("OrtoniReport: Error closing database:", error);
494
- } finally {
495
- this.db = null;
496
- }
497
- }
498
- }
499
- async getSummaryData() {
500
- if (!this.db) {
501
- console.error("OrtoniReport: Database not initialized");
502
- return {
503
- totalRuns: 0,
504
- totalTests: 0,
505
- passed: 0,
506
- failed: 0,
507
- passRate: 0,
508
- avgDuration: 0
509
- };
510
- }
511
- try {
512
- const summary = await this.db.get(`
513
- SELECT
514
- (SELECT COUNT(*) FROM test_runs) as totalRuns,
515
- (SELECT COUNT(*) FROM test_results) as totalTests,
516
- (SELECT COUNT(*) FROM test_results WHERE status = 'passed') as passed,
517
- (SELECT COUNT(*) FROM test_results WHERE status = 'failed') as failed,
518
- (SELECT AVG(duration) FROM test_results) as avgDuration
519
- `);
520
- const passRate = summary.totalTests ? (summary.passed / summary.totalTests * 100).toFixed(2) : 0;
521
- return {
522
- totalRuns: summary.totalRuns,
523
- totalTests: summary.totalTests,
524
- passed: summary.passed,
525
- failed: summary.failed,
526
- passRate: parseFloat(passRate.toString()),
527
- avgDuration: Math.round(summary.avgDuration || 0)
528
- // raw ms avg
529
- };
530
- } catch (error) {
531
- console.error("OrtoniReport: Error getting summary data:", error);
532
- return {
533
- totalRuns: 0,
534
- totalTests: 0,
535
- passed: 0,
536
- failed: 0,
537
- passRate: 0,
538
- avgDuration: 0
539
- };
540
- }
541
- }
542
- async getTrends(limit = 100) {
543
- if (!this.db) {
544
- console.error("OrtoniReport: Database not initialized");
545
- return [];
546
- }
547
- try {
548
- const rows = await this.db.all(
549
- `
550
- SELECT trun.run_date,
551
- SUM(CASE WHEN tr.status = 'passed' THEN 1 ELSE 0 END) AS passed,
552
- SUM(CASE WHEN tr.status = 'failed' THEN 1 ELSE 0 END) AS failed,
553
- AVG(tr.duration) AS avg_duration
554
- FROM test_results tr
555
- JOIN test_runs trun ON tr.run_id = trun.id
556
- GROUP BY trun.run_date
557
- ORDER BY trun.run_date DESC
558
- LIMIT ?
559
- `,
560
- [limit]
561
- );
562
- return rows.reverse().map((row) => ({
563
- ...row,
564
- run_date: formatDateLocal(row.run_date),
565
- avg_duration: Math.round(row.avg_duration || 0)
566
- // raw ms avg
567
- }));
568
- } catch (error) {
569
- console.error("OrtoniReport: Error getting trends data:", error);
570
- return [];
571
- }
572
- }
573
- async getFlakyTests(limit = 10) {
574
- if (!this.db) {
575
- console.error("OrtoniReport: Database not initialized");
576
- return [];
577
- }
578
- try {
579
- return await this.db.all(
580
- `
581
- SELECT
582
- test_id,
583
- COUNT(*) AS total,
584
- SUM(CASE WHEN status = 'flaky' THEN 1 ELSE 0 END) AS flaky,
585
- AVG(duration) AS avg_duration
586
- FROM test_results
587
- GROUP BY test_id
588
- HAVING flaky > 0
589
- ORDER BY flaky DESC
590
- LIMIT ?
591
- `,
592
- [limit]
593
- );
594
- } catch (error) {
595
- console.error("OrtoniReport: Error getting flaky tests:", error);
596
- return [];
597
- }
598
- }
599
- async getSlowTests(limit = 10) {
600
- if (!this.db) {
601
- console.error("OrtoniReport: Database not initialized");
602
- return [];
603
- }
604
- try {
605
- const rows = await this.db.all(
606
- `
607
- SELECT
608
- test_id,
609
- AVG(duration) AS avg_duration
610
- FROM test_results
611
- GROUP BY test_id
612
- ORDER BY avg_duration DESC
613
- LIMIT ?
614
- `,
615
- [limit]
616
- );
617
- return rows.map((row) => ({
618
- test_id: row.test_id,
619
- avg_duration: Math.round(row.avg_duration || 0)
620
- // raw ms avg
621
- }));
622
- } catch (error) {
623
- console.error("OrtoniReport: Error getting slow tests:", error);
624
- return [];
625
- }
626
- }
627
- };
628
-
629
- export {
630
- __publicField,
631
- FileManager,
632
- HTMLGenerator,
633
- normalizeFilePath,
634
- ensureHtmlExtension,
635
- escapeHtml,
636
- extractSuites,
637
- startReportServer,
638
- DatabaseManager
639
- };