ortoni-report 4.0.3 → 4.0.4

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