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
@@ -8,9 +8,9 @@ import {
8
8
  extractSuites,
9
9
  normalizeFilePath,
10
10
  startReportServer
11
- } from "./chunk-JEIWNUQY.mjs";
11
+ } from "./chunk-4RZ5C7KY.mjs";
12
12
 
13
- // src/helpers/resultProcessor .ts
13
+ // src/helpers/resultProcessor.ts
14
14
  import AnsiToHtml from "ansi-to-html";
15
15
  import path2 from "path";
16
16
 
@@ -1272,7 +1272,7 @@ function getFileExtension(contentType) {
1272
1272
  return extensions[contentType] || "unknown";
1273
1273
  }
1274
1274
 
1275
- // src/helpers/resultProcessor .ts
1275
+ // src/helpers/resultProcessor.ts
1276
1276
  var TestResultProcessor = class {
1277
1277
  constructor(projectRoot) {
1278
1278
  this.ansiToHtml = new AnsiToHtml({ fg: "var(--snippet-color)" });
@@ -1317,7 +1317,7 @@ var TestResultProcessor = class {
1317
1317
  testId: `${filePath}:${projectName}:${title}`
1318
1318
  };
1319
1319
  attachFiles(
1320
- test.id,
1320
+ path2.join(test.id, `retry-${result.retry}`),
1321
1321
  result,
1322
1322
  testResult,
1323
1323
  ortoniConfig,
@@ -1343,7 +1343,7 @@ var ServerManager = class {
1343
1343
  constructor(ortoniConfig) {
1344
1344
  this.ortoniConfig = ortoniConfig;
1345
1345
  }
1346
- startServer(folderPath, outputFilename, overAllStatus) {
1346
+ async startServer(folderPath, outputFilename, overAllStatus) {
1347
1347
  const openOption = this.ortoniConfig.open || "never";
1348
1348
  const hasFailures = overAllStatus === "failed";
1349
1349
  if (openOption === "always" || openOption === "on-failure" && hasFailures) {
@@ -1353,6 +1353,8 @@ var ServerManager = class {
1353
1353
  this.ortoniConfig.port,
1354
1354
  openOption
1355
1355
  );
1356
+ await new Promise((_resolve) => {
1357
+ });
1356
1358
  }
1357
1359
  }
1358
1360
  };
@@ -1391,7 +1393,7 @@ var OrtoniReport = class {
1391
1393
  await this.dbManager.initialize(
1392
1394
  path3.join(this.folderPath, "ortoni-data-history.sqlite")
1393
1395
  );
1394
- this.config = config?.shard;
1396
+ this.shardConfig = config?.shard;
1395
1397
  }
1396
1398
  onStdOut(chunk, _test, _result) {
1397
1399
  if (this.reportsCount == 1 && this.showConsoleLogs) {
@@ -1408,7 +1410,7 @@ var OrtoniReport = class {
1408
1410
  );
1409
1411
  this.results.push(testResult);
1410
1412
  } catch (error) {
1411
- console.error("OrtoniReport: Error processing test end:", error);
1413
+ console.error("Ortoni Report: Error processing test end:", error);
1412
1414
  }
1413
1415
  }
1414
1416
  printsToStdio() {
@@ -1427,12 +1429,12 @@ var OrtoniReport = class {
1427
1429
  (r) => r.status !== "skipped"
1428
1430
  );
1429
1431
  const totalDuration = result.duration;
1430
- if (this.config && this.config.total > 1) {
1431
- const shard = this.config;
1432
+ if (this.shardConfig && this.shardConfig.total > 1) {
1433
+ const shard = this.shardConfig;
1432
1434
  const shardFile = `ortoni-shard-${shard.current}-of-${shard.total}.json`;
1433
1435
  const shardData = {
1434
- status: result.status,
1435
- duration: totalDuration,
1436
+ // status: result.status,
1437
+ totalDuration,
1436
1438
  results: this.results,
1437
1439
  projectSet: Array.from(this.projectSet),
1438
1440
  userConfig: {
@@ -1445,8 +1447,11 @@ var OrtoniReport = class {
1445
1447
  meta: this.ortoniConfig.meta
1446
1448
  }
1447
1449
  };
1448
- this.fileManager.writeRawFile(shardFile, shardData);
1449
- console.log(`\u{1F4E6} OrtoniReport wrote shard file: ${shardFile}`);
1450
+ const shardFilePath = this.fileManager.writeRawFile(
1451
+ shardFile,
1452
+ shardData
1453
+ );
1454
+ console.info(`Ortoni Report: Shard data written to ${shardFilePath}`);
1450
1455
  this.shouldGenerateReport = false;
1451
1456
  return;
1452
1457
  }
@@ -1459,21 +1464,21 @@ var OrtoniReport = class {
1459
1464
  this.results,
1460
1465
  this.projectSet
1461
1466
  );
1462
- this.outputPath = this.fileManager.writeReportFile(
1467
+ this.outputPath = await this.fileManager.writeReportFile(
1463
1468
  this.outputFilename,
1464
1469
  finalReportData
1465
1470
  );
1466
1471
  } else {
1467
- console.error("OrtoniReport: Error saving test run to database");
1472
+ console.error("Ortoni Report: Error saving test run to database");
1468
1473
  }
1469
1474
  } else {
1470
1475
  console.error(
1471
- "OrtoniReport: Report generation skipped due to error in Playwright worker!"
1476
+ "Ortoni Report: Report generation skipped due to error in Playwright worker!"
1472
1477
  );
1473
1478
  }
1474
1479
  } catch (error) {
1475
1480
  this.shouldGenerateReport = false;
1476
- console.error("OrtoniReport: Error generating report:", error);
1481
+ console.error("Ortoni Report: Error generating report:", error);
1477
1482
  }
1478
1483
  }
1479
1484
  async onExit() {
@@ -1481,17 +1486,15 @@ var OrtoniReport = class {
1481
1486
  await this.dbManager.close();
1482
1487
  if (this.shouldGenerateReport) {
1483
1488
  this.fileManager.copyTraceViewerAssets(this.skipTraceViewer);
1484
- console.info(`Ortoni HTML report generated at ${this.outputPath}`);
1485
- this.serverManager.startServer(
1489
+ console.info(`Ortoni Report generated at ${this.outputPath}`);
1490
+ await this.serverManager.startServer(
1486
1491
  this.folderPath,
1487
1492
  this.outputFilename,
1488
1493
  this.overAllStatus
1489
1494
  );
1490
- await new Promise((_resolve) => {
1491
- });
1492
1495
  }
1493
1496
  } catch (error) {
1494
- console.error("OrtoniReport: Error in onExit:", error);
1497
+ console.error("Ortoni Report: Error in onExit:", error);
1495
1498
  }
1496
1499
  }
1497
1500
  };
@@ -1,9 +1,7 @@
1
- import { Reporter, FullConfig, Suite, TestCase, TestResult, TestError, FullResult } from '@playwright/test/reporter';
2
-
3
1
  /**
4
2
  * Configuration options for OrtoniReport.
5
3
  */
6
- interface OrtoniReportConfig {
4
+ export interface OrtoniReportConfig {
7
5
  /**
8
6
  * Open the report in local host (Trace viewer is accessible only in localhost)
9
7
  * @example "always"| "never"| "on-failure";
@@ -78,34 +76,11 @@ interface OrtoniReportConfig {
78
76
  * @example { "key": "value" } as string
79
77
  */
80
78
  meta?: Record<string, string>;
79
+ /**
80
+ * Save the history of the reports in a SQL file to be used in future reports.
81
+ * The history file will be saved in the report folder.
82
+ * @default true
83
+ * @example false (to disable)
84
+ */
85
+ saveHistory?: boolean;
81
86
  }
82
-
83
- declare class OrtoniReport implements Reporter {
84
- private ortoniConfig;
85
- private testResultProcessor;
86
- private htmlGenerator;
87
- private fileManager;
88
- private serverManager;
89
- private results;
90
- private projectSet;
91
- private overAllStatus;
92
- private folderPath;
93
- private outputFilename;
94
- private outputPath;
95
- private dbManager;
96
- private shouldGenerateReport;
97
- private showConsoleLogs;
98
- private skipTraceViewer;
99
- private config;
100
- constructor(ortoniConfig?: OrtoniReportConfig);
101
- private reportsCount;
102
- onBegin(config: FullConfig, _suite: Suite): Promise<void>;
103
- onStdOut(chunk: string | Buffer, _test: void | TestCase, _result: void | TestResult): void;
104
- onTestEnd(test: TestCase, result: TestResult): void;
105
- printsToStdio(): boolean;
106
- onError(error: TestError): void;
107
- onEnd(result: FullResult): Promise<void>;
108
- onExit(): Promise<void>;
109
- }
110
-
111
- export { OrtoniReport, type OrtoniReportConfig, OrtoniReport as default };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ export interface Steps {
2
+ snippet: string | undefined;
3
+ title: string;
4
+ location: string;
5
+ }
6
+ export interface TestResultData {
7
+ suiteHierarchy: string;
8
+ key: string;
9
+ annotations: any[];
10
+ testTags: string[];
11
+ location: string;
12
+ retryAttemptCount: number;
13
+ projectName: any;
14
+ suite: any;
15
+ title: string;
16
+ status: "passed" | "failed" | "timedOut" | "skipped" | "interrupted" | "expected" | "unexpected" | "flaky";
17
+ flaky: string;
18
+ duration: number;
19
+ errors: any[];
20
+ steps: Steps[];
21
+ logs: string;
22
+ screenshotPath?: string | null | undefined;
23
+ screenshots?: string[];
24
+ filePath: string;
25
+ filters: Set<string>;
26
+ tracePath?: string;
27
+ videoPath?: string[];
28
+ markdownPath?: string;
29
+ base64Image: boolean | undefined;
30
+ testId: string;
31
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { TestResult } from "@playwright/test/reporter";
2
+ import { Steps, TestResultData } from "../types/testResults";
3
+ import { OrtoniReportConfig } from "../types/reporterConfig";
4
+ export declare function attachFiles(subFolder: string, result: TestResult, testResult: TestResultData, config: OrtoniReportConfig, steps?: Steps[], errors?: string[]): void;
@@ -0,0 +1,87 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { convertMarkdownToHtml } from "../helpers/markdownConverter";
4
+ export function attachFiles(subFolder, result, testResult, config, steps, errors) {
5
+ const folderPath = config.folderPath || "ortoni-report";
6
+ const attachmentsFolder = path.join(folderPath, "ortoni-data", "attachments", subFolder);
7
+ if (!fs.existsSync(attachmentsFolder)) {
8
+ fs.mkdirSync(attachmentsFolder, { recursive: true });
9
+ }
10
+ if (!result.attachments)
11
+ return;
12
+ const { base64Image } = config;
13
+ testResult.screenshots = [];
14
+ testResult.videoPath = [];
15
+ result.attachments.forEach((attachment) => {
16
+ const { contentType, name, path: attachmentPath, body } = attachment;
17
+ if (!attachmentPath && !body)
18
+ return;
19
+ const fileName = attachmentPath
20
+ ? path.basename(attachmentPath)
21
+ : `${name}.${getFileExtension(contentType)}`;
22
+ const relativePath = path.join("ortoni-data", "attachments", subFolder, fileName);
23
+ const fullPath = path.join(attachmentsFolder, fileName);
24
+ if (contentType === "image/png") {
25
+ handleImage(attachmentPath, body, base64Image, fullPath, relativePath, testResult);
26
+ }
27
+ else if (name === "video") {
28
+ handleAttachment(attachmentPath, fullPath, relativePath, "videoPath", testResult);
29
+ }
30
+ else if (name === "trace") {
31
+ handleAttachment(attachmentPath, fullPath, relativePath, "tracePath", testResult);
32
+ }
33
+ else if (name === "error-context") {
34
+ handleAttachment(attachmentPath, fullPath, relativePath, "markdownPath", testResult, steps, errors);
35
+ }
36
+ });
37
+ }
38
+ function handleImage(attachmentPath, body, base64Image, fullPath, relativePath, testResult) {
39
+ let screenshotPath = "";
40
+ if (attachmentPath) {
41
+ try {
42
+ const screenshotContent = fs.readFileSync(attachmentPath, base64Image ? "base64" : undefined);
43
+ screenshotPath = base64Image
44
+ ? `data:image/png;base64,${screenshotContent}`
45
+ : relativePath;
46
+ if (!base64Image) {
47
+ fs.copyFileSync(attachmentPath, fullPath);
48
+ }
49
+ }
50
+ catch (error) {
51
+ console.error(`OrtoniReport: Failed to read screenshot file: ${attachmentPath}`, error);
52
+ }
53
+ }
54
+ else if (body) {
55
+ screenshotPath = `data:image/png;base64,${body.toString("base64")}`;
56
+ }
57
+ if (screenshotPath) {
58
+ testResult.screenshots?.push(screenshotPath);
59
+ }
60
+ }
61
+ function handleAttachment(attachmentPath, fullPath, relativePath, resultKey, testResult, steps, errors) {
62
+ if (attachmentPath) {
63
+ fs.copyFileSync(attachmentPath, fullPath);
64
+ if (resultKey === "videoPath") {
65
+ testResult[resultKey]?.push(relativePath);
66
+ }
67
+ else if (resultKey === "tracePath") {
68
+ testResult[resultKey] = relativePath;
69
+ }
70
+ }
71
+ if (resultKey === "markdownPath" && errors) {
72
+ const htmlPath = fullPath.replace(/\.md$/, ".html");
73
+ const htmlRelativePath = relativePath.replace(/\.md$/, ".html");
74
+ convertMarkdownToHtml(fullPath, htmlPath);
75
+ testResult[resultKey] = htmlRelativePath;
76
+ return;
77
+ }
78
+ }
79
+ function getFileExtension(contentType) {
80
+ const extensions = {
81
+ "image/png": "png",
82
+ "video/webm": "webm",
83
+ "application/zip": "zip",
84
+ "text/markdown": "md",
85
+ };
86
+ return extensions[contentType] || "unknown";
87
+ }
@@ -0,0 +1 @@
1
+ export declare function startReportServer(reportFolder: string, reportFilename: string, port: number, open: string | undefined): void;
@@ -0,0 +1,61 @@
1
+ import express from "express";
2
+ import path from "path";
3
+ import { spawn } from "child_process";
4
+ export function startReportServer(reportFolder, reportFilename, port = 2004, open) {
5
+ const app = express();
6
+ app.use(express.static(reportFolder, { index: false }));
7
+ app.get("/", (_req, res) => {
8
+ try {
9
+ res.sendFile(path.resolve(reportFolder, reportFilename));
10
+ }
11
+ catch (error) {
12
+ console.error("Ortoni Report: Error sending report file:", error);
13
+ res.status(500).send("Error loading report");
14
+ }
15
+ });
16
+ try {
17
+ const server = app.listen(port, () => {
18
+ console.log(`Server is running at http://localhost:${port} \nPress Ctrl+C to stop.`);
19
+ if (open === "always" || open === "on-failure") {
20
+ try {
21
+ openBrowser(`http://localhost:${port}`);
22
+ }
23
+ catch (error) {
24
+ console.error("Ortoni Report: Error opening browser:", error);
25
+ }
26
+ }
27
+ });
28
+ server.on("error", (error) => {
29
+ if (error.code === "EADDRINUSE") {
30
+ console.error(`Ortoni Report: Port ${port} is already in use. Trying a different port...`);
31
+ }
32
+ else {
33
+ console.error("Ortoni Report: Server error:", error);
34
+ }
35
+ });
36
+ }
37
+ catch (error) {
38
+ console.error("Ortoni Report: Error starting the server:", error);
39
+ }
40
+ }
41
+ function openBrowser(url) {
42
+ const platform = process.platform;
43
+ let command;
44
+ try {
45
+ if (platform === "win32") {
46
+ command = "cmd";
47
+ spawn(command, ["/c", "start", url]);
48
+ }
49
+ else if (platform === "darwin") {
50
+ command = "open";
51
+ spawn(command, [url]);
52
+ }
53
+ else {
54
+ command = "xdg-open";
55
+ spawn(command, [url]);
56
+ }
57
+ }
58
+ catch (error) {
59
+ console.error("Ortoni Report: Error opening the browser:", error);
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ import { OrtoniReportConfig } from "../types/reporterConfig";
2
+ import { TestResultData } from "../types/testResults";
3
+ export declare function groupResults(config: OrtoniReportConfig, results: TestResultData[]): any;
@@ -0,0 +1,30 @@
1
+ export function groupResults(config, results) {
2
+ if (config.showProject) {
3
+ // Group by filePath, suite, and projectName
4
+ const groupedResults = results.reduce((acc, result, index) => {
5
+ const testId = `${result.filePath}:${result.projectName}:${result.title}`;
6
+ const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
7
+ const { filePath, suite, projectName } = result;
8
+ acc[filePath] = acc[filePath] || {};
9
+ acc[filePath][suite] = acc[filePath][suite] || {};
10
+ acc[filePath][suite][projectName] =
11
+ acc[filePath][suite][projectName] || [];
12
+ acc[filePath][suite][projectName].push({ ...result, index, testId, key });
13
+ return acc;
14
+ }, {});
15
+ return groupedResults;
16
+ }
17
+ else {
18
+ // Group by filePath and suite, ignoring projectName
19
+ const groupedResults = results.reduce((acc, result, index) => {
20
+ const testId = `${result.filePath}:${result.projectName}:${result.title}`;
21
+ const key = `${testId}-${result.key}-${result.retryAttemptCount}`;
22
+ const { filePath, suite } = result;
23
+ acc[filePath] = acc[filePath] || {};
24
+ acc[filePath][suite] = acc[filePath][suite] || [];
25
+ acc[filePath][suite].push({ ...result, index, testId, key });
26
+ return acc;
27
+ }, {});
28
+ return groupedResults;
29
+ }
30
+ }
@@ -0,0 +1,15 @@
1
+ export declare function normalizeFilePath(filePath: string): string;
2
+ export declare function formatDate(date: Date): string;
3
+ export declare function safeStringify(obj: any, indent?: number): string;
4
+ export declare function ensureHtmlExtension(filename: string): string;
5
+ export declare function escapeHtml(unsafe: string): string;
6
+ export declare function formatDateUTC(date: Date): string;
7
+ export declare function formatDateLocal(dateInput: Date | string): string;
8
+ export declare function formatDateNoTimezone(isoString: string): string;
9
+ type SuiteAndTitle = {
10
+ hierarchy: string;
11
+ topLevelSuite: string;
12
+ parentSuite: string;
13
+ };
14
+ export declare function extractSuites(titlePath: string[]): SuiteAndTitle;
15
+ export {};
@@ -0,0 +1,84 @@
1
+ import path from "path";
2
+ export function normalizeFilePath(filePath) {
3
+ // Normalize the path to handle different separators
4
+ const normalizedPath = path.normalize(filePath);
5
+ // Get the base name of the file (removes any leading directories)
6
+ return path.basename(normalizedPath);
7
+ }
8
+ export function formatDate(date) {
9
+ const day = String(date.getDate()).padStart(2, "0");
10
+ const month = date.toLocaleString("default", { month: "short" });
11
+ const year = date.getFullYear();
12
+ const time = date.toLocaleTimeString();
13
+ return `${day}-${month}-${year} ${time}`;
14
+ }
15
+ export function safeStringify(obj, indent = 2) {
16
+ const cache = new Set();
17
+ const json = JSON.stringify(obj, (key, value) => {
18
+ if (typeof value === "object" && value !== null) {
19
+ if (cache.has(value)) {
20
+ return;
21
+ }
22
+ cache.add(value);
23
+ }
24
+ return value;
25
+ }, indent);
26
+ cache.clear();
27
+ return json;
28
+ }
29
+ export function ensureHtmlExtension(filename) {
30
+ const ext = path.extname(filename);
31
+ if (ext && ext.toLowerCase() === ".html") {
32
+ return filename;
33
+ }
34
+ return `${filename}.html`;
35
+ }
36
+ export function escapeHtml(unsafe) {
37
+ if (typeof unsafe !== "string") {
38
+ return String(unsafe);
39
+ }
40
+ return unsafe.replace(/[&<"']/g, function (match) {
41
+ const escapeMap = {
42
+ "&": "&amp;",
43
+ "<": "&lt;",
44
+ ">": "&gt;",
45
+ '"': "&quot;",
46
+ "'": "&#039;",
47
+ };
48
+ return escapeMap[match] || match;
49
+ });
50
+ }
51
+ export function formatDateUTC(date) {
52
+ return date.toISOString();
53
+ }
54
+ export function formatDateLocal(dateInput) {
55
+ const date = typeof dateInput === "string" ? new Date(dateInput) : dateInput;
56
+ const options = {
57
+ year: "numeric",
58
+ month: "short",
59
+ day: "2-digit",
60
+ hour: "2-digit",
61
+ minute: "2-digit",
62
+ hour12: true,
63
+ timeZoneName: "short", // or "Asia/Kolkata"
64
+ };
65
+ return new Intl.DateTimeFormat(undefined, options).format(date);
66
+ }
67
+ export function formatDateNoTimezone(isoString) {
68
+ const date = new Date(isoString);
69
+ return date.toLocaleString("en-US", {
70
+ dateStyle: "medium",
71
+ timeStyle: "short",
72
+ });
73
+ }
74
+ export function extractSuites(titlePath) {
75
+ const tagPattern = /@[\w]+/g;
76
+ const suiteParts = titlePath
77
+ .slice(3, titlePath.length - 1)
78
+ .map((p) => p.replace(tagPattern, "").trim());
79
+ return {
80
+ hierarchy: suiteParts.join(" > "),
81
+ topLevelSuite: suiteParts[0] ?? "",
82
+ parentSuite: suiteParts[suiteParts.length - 1] ?? "", // last suite
83
+ };
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ortoni-report",
3
- "version": "4.0.2-beta.1",
3
+ "version": "4.0.3",
4
4
  "description": "Playwright Report By LetCode with Koushik",
5
5
  "scripts": {
6
6
  "tsc": "tsc",
@@ -8,10 +8,11 @@
8
8
  "release": "npm publish"
9
9
  },
10
10
  "bin": {
11
- "ortoni-report": "./dist/cli/cli.js"
11
+ "ortoni-report": "./dist/cli.js"
12
12
  },
13
13
  "files": [
14
- "dist",
14
+ "dist/",
15
+ "dist/index.html",
15
16
  "README.md",
16
17
  "CHANGELOG.md"
17
18
  ],
@@ -52,5 +53,11 @@
52
53
  },
53
54
  "main": "dist/ortoni-report.js",
54
55
  "module": "dist/ortoni-report.mjs",
55
- "types": "dist/ortoni-report.d.ts"
56
+ "types": "dist/ortoni-report.d.ts",
57
+ "exports": {
58
+ ".": {
59
+ "require": "./dist/ortoni-report.js",
60
+ "import": "./dist/ortoni-report.mjs"
61
+ }
62
+ }
56
63
  }