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.
- package/changelog.md +27 -0
- package/dist/{chunk-INS3E7E6.mjs → chunk-4RZ5C7KY.mjs} +402 -296
- package/dist/{cli/cli.js → cli.js} +405 -190
- package/dist/cli.mjs +208 -0
- package/dist/helpers/HTMLGenerator.d.ts +89 -0
- package/dist/helpers/HTMLGenerator.js +163 -0
- package/dist/helpers/databaseManager.d.ts +35 -0
- package/dist/helpers/databaseManager.js +268 -0
- package/dist/helpers/fileManager.d.ts +8 -0
- package/dist/helpers/fileManager.js +60 -0
- package/dist/helpers/markdownConverter.d.ts +1 -0
- package/dist/helpers/markdownConverter.js +14 -0
- package/dist/helpers/resultProcessor.d.ts +10 -0
- package/dist/helpers/resultProcessor.js +60 -0
- package/dist/helpers/serverManager.d.ts +6 -0
- package/dist/helpers/serverManager.js +15 -0
- package/dist/helpers/templateLoader.d.ts +15 -0
- package/dist/helpers/templateLoader.js +88 -0
- package/dist/index.html +1 -1
- package/dist/mergeData.d.ts +13 -0
- package/dist/mergeData.js +182 -0
- package/dist/ortoni-report.d.mts +8 -1
- package/dist/ortoni-report.d.ts +8 -1
- package/dist/ortoni-report.js +211 -96
- package/dist/ortoni-report.mjs +25 -22
- package/dist/{ortoni-report.d.cts → types/reporterConfig.d.ts} +8 -33
- package/dist/types/reporterConfig.js +1 -0
- package/dist/types/testResults.d.ts +31 -0
- package/dist/types/testResults.js +1 -0
- package/dist/utils/attachFiles.d.ts +4 -0
- package/dist/utils/attachFiles.js +87 -0
- package/dist/utils/expressServer.d.ts +1 -0
- package/dist/utils/expressServer.js +61 -0
- package/dist/utils/groupProjects.d.ts +3 -0
- package/dist/utils/groupProjects.js +30 -0
- package/dist/utils/utils.d.ts +15 -0
- package/dist/utils/utils.js +84 -0
- package/package.json +11 -4
- package/readme.md +60 -75
- 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/{cli/cli.d.mts → cli.d.mts} +0 -0
- /package/dist/{cli/cli.d.ts → cli.d.ts} +0 -0
package/dist/ortoni-report.mjs
CHANGED
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
extractSuites,
|
|
9
9
|
normalizeFilePath,
|
|
10
10
|
startReportServer
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-4RZ5C7KY.mjs";
|
|
12
12
|
|
|
13
|
-
// src/helpers/resultProcessor
|
|
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
|
|
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.
|
|
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("
|
|
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.
|
|
1431
|
-
const shard = this.
|
|
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
|
-
|
|
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(
|
|
1449
|
-
|
|
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("
|
|
1472
|
+
console.error("Ortoni Report: Error saving test run to database");
|
|
1468
1473
|
}
|
|
1469
1474
|
} else {
|
|
1470
1475
|
console.error(
|
|
1471
|
-
"
|
|
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("
|
|
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
|
|
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("
|
|
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,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
|
+
"&": "&",
|
|
43
|
+
"<": "<",
|
|
44
|
+
">": ">",
|
|
45
|
+
'"': """,
|
|
46
|
+
"'": "'",
|
|
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.
|
|
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
|
|
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
|
}
|