ortoni-report 1.0.8 → 1.0.9
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 +26 -0
- package/dist/css/pico.css +2802 -0
- package/dist/icon/32.png +0 -0
- package/dist/ortoni-report.d.ts +8 -1
- package/dist/ortoni-report.js +49 -18
- package/dist/ortoni-report.mjs +49 -18
- package/dist/report-template.hbs +113 -52
- package/package.json +13 -6
- package/readme.md +62 -15
- package/.prettierignore +0 -1
- package/report-template.hbs +0 -251
package/dist/icon/32.png
ADDED
|
Binary file
|
package/dist/ortoni-report.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
2
|
|
|
3
|
+
interface ReporterConfig {
|
|
4
|
+
projectName?: string;
|
|
5
|
+
authorName?: string;
|
|
6
|
+
testType?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
declare class OrtoniReport implements Reporter {
|
|
4
10
|
private results;
|
|
5
11
|
private groupedResults;
|
|
6
12
|
private suiteName;
|
|
7
|
-
|
|
13
|
+
private config;
|
|
14
|
+
constructor(config?: ReporterConfig);
|
|
8
15
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
9
16
|
onTestBegin(test: TestCase, result: TestResult): void;
|
|
10
17
|
onTestEnd(test: TestCase, result: TestResult): void;
|
package/dist/ortoni-report.js
CHANGED
|
@@ -34,31 +34,57 @@ __export(ortoni_report_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(ortoni_report_exports);
|
|
36
36
|
var import_fs = __toESM(require("fs"));
|
|
37
|
-
var
|
|
37
|
+
var import_path2 = __toESM(require("path"));
|
|
38
38
|
var import_handlebars = __toESM(require("handlebars"));
|
|
39
39
|
var import_safe = __toESM(require("colors/safe"));
|
|
40
|
+
|
|
41
|
+
// src/utils/time.ts
|
|
42
|
+
var import_path = __toESM(require("path"));
|
|
43
|
+
function msToTime(duration) {
|
|
44
|
+
const seconds = Math.floor(duration / 1e3 % 60);
|
|
45
|
+
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
46
|
+
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
47
|
+
const parts = [];
|
|
48
|
+
if (hours > 0)
|
|
49
|
+
parts.push(hours + "h");
|
|
50
|
+
if (minutes > 0)
|
|
51
|
+
parts.push(minutes + "m");
|
|
52
|
+
if (seconds > 0 || parts.length === 0)
|
|
53
|
+
parts.push(seconds + "s");
|
|
54
|
+
return parts.join(" ");
|
|
55
|
+
}
|
|
56
|
+
function normalizeFilePath(filePath) {
|
|
57
|
+
const normalizedPath = import_path.default.normalize(filePath);
|
|
58
|
+
return import_path.default.basename(normalizedPath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/ortoni-report.ts
|
|
40
62
|
var OrtoniReport = class {
|
|
41
|
-
constructor() {
|
|
63
|
+
constructor(config = {}) {
|
|
42
64
|
this.results = [];
|
|
65
|
+
this.config = config;
|
|
43
66
|
}
|
|
44
67
|
onBegin(config, suite) {
|
|
45
68
|
this.results = [];
|
|
46
|
-
const screenshotsDir =
|
|
47
|
-
if (
|
|
48
|
-
import_fs.default.
|
|
69
|
+
const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots");
|
|
70
|
+
if (import_fs.default.existsSync(screenshotsDir)) {
|
|
71
|
+
import_fs.default.rmSync(screenshotsDir, { recursive: true, force: true });
|
|
49
72
|
}
|
|
73
|
+
import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
|
|
50
74
|
}
|
|
51
75
|
onTestBegin(test, result) {
|
|
52
76
|
}
|
|
53
77
|
onTestEnd(test, result) {
|
|
54
78
|
const testResult = {
|
|
79
|
+
totalDuration: "",
|
|
55
80
|
projectName: test.titlePath()[1],
|
|
56
81
|
// Get the project name
|
|
57
82
|
suite: test.titlePath()[3],
|
|
58
83
|
// Adjust the index based on your suite hierarchy
|
|
59
84
|
title: test.title,
|
|
60
85
|
status: result.status,
|
|
61
|
-
|
|
86
|
+
flaky: test.outcome(),
|
|
87
|
+
duration: msToTime(result.duration),
|
|
62
88
|
errors: result.errors.map((e) => import_safe.default.strip(e.message || e.toString())),
|
|
63
89
|
steps: result.steps.map((step) => ({
|
|
64
90
|
title: step.title,
|
|
@@ -68,24 +94,25 @@ var OrtoniReport = class {
|
|
|
68
94
|
})),
|
|
69
95
|
logs: import_safe.default.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
|
|
70
96
|
screenshotPath: null,
|
|
71
|
-
filePath: test.titlePath()[2]
|
|
97
|
+
filePath: normalizeFilePath(test.titlePath()[2])
|
|
72
98
|
};
|
|
73
99
|
if (result.attachments) {
|
|
74
|
-
const screenshotsDir =
|
|
100
|
+
const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots", test.id);
|
|
75
101
|
if (!import_fs.default.existsSync(screenshotsDir)) {
|
|
76
|
-
import_fs.default.mkdirSync(screenshotsDir);
|
|
102
|
+
import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
|
|
77
103
|
}
|
|
78
104
|
const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
|
|
79
105
|
if (screenshot && screenshot.path) {
|
|
80
106
|
const screenshotContent = import_fs.default.readFileSync(screenshot.path, "base64");
|
|
81
|
-
const screenshotFileName =
|
|
82
|
-
import_fs.default.writeFileSync(
|
|
107
|
+
const screenshotFileName = import_path2.default.join("screenshots", test.id, import_path2.default.basename(screenshot.path));
|
|
108
|
+
import_fs.default.writeFileSync(import_path2.default.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
|
|
83
109
|
testResult.screenshotPath = screenshotFileName;
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
112
|
this.results.push(testResult);
|
|
87
113
|
}
|
|
88
114
|
onEnd(result) {
|
|
115
|
+
this.results[0].totalDuration = msToTime(result.duration);
|
|
89
116
|
this.groupedResults = this.results.reduce((acc, result2, index) => {
|
|
90
117
|
const filePath = result2.filePath;
|
|
91
118
|
const suiteName = result2.suite;
|
|
@@ -105,26 +132,30 @@ var OrtoniReport = class {
|
|
|
105
132
|
import_handlebars.default.registerHelper("json", function(context) {
|
|
106
133
|
return safeStringify(context);
|
|
107
134
|
});
|
|
108
|
-
import_handlebars.default.registerHelper("splitSuiteName", function(suiteName) {
|
|
109
|
-
return suiteName.split(" - ");
|
|
110
|
-
});
|
|
111
135
|
const html = this.generateHTML();
|
|
112
|
-
const outputPath =
|
|
136
|
+
const outputPath = import_path2.default.resolve(process.cwd(), "ortoni-report.html");
|
|
113
137
|
import_fs.default.writeFileSync(outputPath, html);
|
|
114
138
|
console.log(`Ortoni HTML report generated at ${outputPath}`);
|
|
115
139
|
}
|
|
116
140
|
generateHTML() {
|
|
117
|
-
const templateSource = import_fs.default.readFileSync(
|
|
141
|
+
const templateSource = import_fs.default.readFileSync(import_path2.default.resolve(__dirname, "report-template.hbs"), "utf-8");
|
|
118
142
|
const template = import_handlebars.default.compile(templateSource);
|
|
119
143
|
const data = {
|
|
144
|
+
totalDuration: this.results[0].totalDuration,
|
|
120
145
|
suiteName: this.suiteName,
|
|
121
146
|
results: this.results,
|
|
122
147
|
passCount: this.results.filter((r) => r.status === "passed").length,
|
|
123
148
|
failCount: this.results.filter((r) => r.status === "failed").length,
|
|
124
149
|
skipCount: this.results.filter((r) => r.status === "skipped").length,
|
|
125
|
-
|
|
150
|
+
flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
|
|
126
151
|
totalCount: this.results.length,
|
|
127
|
-
groupedResults: this.groupedResults
|
|
152
|
+
groupedResults: this.groupedResults,
|
|
153
|
+
projectName: this.config.projectName,
|
|
154
|
+
// Include project name
|
|
155
|
+
authorName: this.config.authorName,
|
|
156
|
+
// Include author name
|
|
157
|
+
testType: this.config.testType
|
|
158
|
+
// Include test type
|
|
128
159
|
};
|
|
129
160
|
return template(data);
|
|
130
161
|
}
|
package/dist/ortoni-report.mjs
CHANGED
|
@@ -1,30 +1,56 @@
|
|
|
1
1
|
// src/ortoni-report.ts
|
|
2
2
|
import fs from "fs";
|
|
3
|
-
import
|
|
3
|
+
import path2 from "path";
|
|
4
4
|
import Handlebars from "handlebars";
|
|
5
5
|
import colors from "colors/safe";
|
|
6
|
+
|
|
7
|
+
// src/utils/time.ts
|
|
8
|
+
import path from "path";
|
|
9
|
+
function msToTime(duration) {
|
|
10
|
+
const seconds = Math.floor(duration / 1e3 % 60);
|
|
11
|
+
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
12
|
+
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
13
|
+
const parts = [];
|
|
14
|
+
if (hours > 0)
|
|
15
|
+
parts.push(hours + "h");
|
|
16
|
+
if (minutes > 0)
|
|
17
|
+
parts.push(minutes + "m");
|
|
18
|
+
if (seconds > 0 || parts.length === 0)
|
|
19
|
+
parts.push(seconds + "s");
|
|
20
|
+
return parts.join(" ");
|
|
21
|
+
}
|
|
22
|
+
function normalizeFilePath(filePath) {
|
|
23
|
+
const normalizedPath = path.normalize(filePath);
|
|
24
|
+
return path.basename(normalizedPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/ortoni-report.ts
|
|
6
28
|
var OrtoniReport = class {
|
|
7
|
-
constructor() {
|
|
29
|
+
constructor(config = {}) {
|
|
8
30
|
this.results = [];
|
|
31
|
+
this.config = config;
|
|
9
32
|
}
|
|
10
33
|
onBegin(config, suite) {
|
|
11
34
|
this.results = [];
|
|
12
|
-
const screenshotsDir =
|
|
13
|
-
if (
|
|
14
|
-
fs.
|
|
35
|
+
const screenshotsDir = path2.resolve(process.cwd(), "screenshots");
|
|
36
|
+
if (fs.existsSync(screenshotsDir)) {
|
|
37
|
+
fs.rmSync(screenshotsDir, { recursive: true, force: true });
|
|
15
38
|
}
|
|
39
|
+
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
16
40
|
}
|
|
17
41
|
onTestBegin(test, result) {
|
|
18
42
|
}
|
|
19
43
|
onTestEnd(test, result) {
|
|
20
44
|
const testResult = {
|
|
45
|
+
totalDuration: "",
|
|
21
46
|
projectName: test.titlePath()[1],
|
|
22
47
|
// Get the project name
|
|
23
48
|
suite: test.titlePath()[3],
|
|
24
49
|
// Adjust the index based on your suite hierarchy
|
|
25
50
|
title: test.title,
|
|
26
51
|
status: result.status,
|
|
27
|
-
|
|
52
|
+
flaky: test.outcome(),
|
|
53
|
+
duration: msToTime(result.duration),
|
|
28
54
|
errors: result.errors.map((e) => colors.strip(e.message || e.toString())),
|
|
29
55
|
steps: result.steps.map((step) => ({
|
|
30
56
|
title: step.title,
|
|
@@ -34,24 +60,25 @@ var OrtoniReport = class {
|
|
|
34
60
|
})),
|
|
35
61
|
logs: colors.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
|
|
36
62
|
screenshotPath: null,
|
|
37
|
-
filePath: test.titlePath()[2]
|
|
63
|
+
filePath: normalizeFilePath(test.titlePath()[2])
|
|
38
64
|
};
|
|
39
65
|
if (result.attachments) {
|
|
40
|
-
const screenshotsDir =
|
|
66
|
+
const screenshotsDir = path2.resolve(process.cwd(), "screenshots", test.id);
|
|
41
67
|
if (!fs.existsSync(screenshotsDir)) {
|
|
42
|
-
fs.mkdirSync(screenshotsDir);
|
|
68
|
+
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
43
69
|
}
|
|
44
70
|
const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
|
|
45
71
|
if (screenshot && screenshot.path) {
|
|
46
72
|
const screenshotContent = fs.readFileSync(screenshot.path, "base64");
|
|
47
|
-
const screenshotFileName =
|
|
48
|
-
fs.writeFileSync(
|
|
73
|
+
const screenshotFileName = path2.join("screenshots", test.id, path2.basename(screenshot.path));
|
|
74
|
+
fs.writeFileSync(path2.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
|
|
49
75
|
testResult.screenshotPath = screenshotFileName;
|
|
50
76
|
}
|
|
51
77
|
}
|
|
52
78
|
this.results.push(testResult);
|
|
53
79
|
}
|
|
54
80
|
onEnd(result) {
|
|
81
|
+
this.results[0].totalDuration = msToTime(result.duration);
|
|
55
82
|
this.groupedResults = this.results.reduce((acc, result2, index) => {
|
|
56
83
|
const filePath = result2.filePath;
|
|
57
84
|
const suiteName = result2.suite;
|
|
@@ -71,26 +98,30 @@ var OrtoniReport = class {
|
|
|
71
98
|
Handlebars.registerHelper("json", function(context) {
|
|
72
99
|
return safeStringify(context);
|
|
73
100
|
});
|
|
74
|
-
Handlebars.registerHelper("splitSuiteName", function(suiteName) {
|
|
75
|
-
return suiteName.split(" - ");
|
|
76
|
-
});
|
|
77
101
|
const html = this.generateHTML();
|
|
78
|
-
const outputPath =
|
|
102
|
+
const outputPath = path2.resolve(process.cwd(), "ortoni-report.html");
|
|
79
103
|
fs.writeFileSync(outputPath, html);
|
|
80
104
|
console.log(`Ortoni HTML report generated at ${outputPath}`);
|
|
81
105
|
}
|
|
82
106
|
generateHTML() {
|
|
83
|
-
const templateSource = fs.readFileSync(
|
|
107
|
+
const templateSource = fs.readFileSync(path2.resolve(__dirname, "report-template.hbs"), "utf-8");
|
|
84
108
|
const template = Handlebars.compile(templateSource);
|
|
85
109
|
const data = {
|
|
110
|
+
totalDuration: this.results[0].totalDuration,
|
|
86
111
|
suiteName: this.suiteName,
|
|
87
112
|
results: this.results,
|
|
88
113
|
passCount: this.results.filter((r) => r.status === "passed").length,
|
|
89
114
|
failCount: this.results.filter((r) => r.status === "failed").length,
|
|
90
115
|
skipCount: this.results.filter((r) => r.status === "skipped").length,
|
|
91
|
-
|
|
116
|
+
flakyCount: this.results.filter((r) => r.flaky === "flaky").length,
|
|
92
117
|
totalCount: this.results.length,
|
|
93
|
-
groupedResults: this.groupedResults
|
|
118
|
+
groupedResults: this.groupedResults,
|
|
119
|
+
projectName: this.config.projectName,
|
|
120
|
+
// Include project name
|
|
121
|
+
authorName: this.config.authorName,
|
|
122
|
+
// Include author name
|
|
123
|
+
testType: this.config.testType
|
|
124
|
+
// Include test type
|
|
94
125
|
};
|
|
95
126
|
return template(data);
|
|
96
127
|
}
|
package/dist/report-template.hbs
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>Playwright Test Report</title>
|
|
8
|
-
<link rel="
|
|
9
|
-
<link rel="stylesheet" href="
|
|
8
|
+
<link rel="icon" href="node_modules/ortoni-report/dist/icon/32.png" type="image/x-icon">
|
|
9
|
+
<link rel="stylesheet" href="node_modules/ortoni-report/dist/css/pico.css">
|
|
10
|
+
{{!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.css"> --}}
|
|
10
11
|
<style>
|
|
11
12
|
main.container {
|
|
12
13
|
display: grid;
|
|
@@ -24,27 +25,31 @@
|
|
|
24
25
|
justify-content: space-evenly;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
.highlight {
|
|
29
|
+
background-color: var(--pico-primary-background)
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
.text-success {
|
|
28
33
|
color: #28a745;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
.text-
|
|
36
|
+
.text-failure {
|
|
32
37
|
color: #dc3545;
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
.text-skip {
|
|
41
|
+
color: #d5d4a1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.text-flaky {
|
|
45
|
+
color: #d5d4a1;
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
.sidebar {
|
|
36
49
|
border-right: 1px solid #ddd;
|
|
37
50
|
padding-right: 10px;
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
.card {
|
|
41
|
-
padding: 1rem;
|
|
42
|
-
border: 1px solid #ddd;
|
|
43
|
-
border-radius: 0.5rem;
|
|
44
|
-
background-color: #fff;
|
|
45
|
-
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
53
|
pre {
|
|
49
54
|
background-color: #f8f9fa;
|
|
50
55
|
padding: 1rem;
|
|
@@ -62,9 +67,11 @@
|
|
|
62
67
|
<body>
|
|
63
68
|
<header class="container">
|
|
64
69
|
<div class="header">
|
|
70
|
+
{{!-- Custom Project Name --}}
|
|
65
71
|
<div>
|
|
66
|
-
<h1>
|
|
72
|
+
{{#if projectName}}<h1>{{projectName}}</h1>{{/if}}
|
|
67
73
|
</div>
|
|
74
|
+
{{!-- Dummy for now --}}
|
|
68
75
|
<div>
|
|
69
76
|
<form role="search">
|
|
70
77
|
<input name="search" type="search" placeholder="Search" />
|
|
@@ -74,6 +81,7 @@
|
|
|
74
81
|
</div>
|
|
75
82
|
</header>
|
|
76
83
|
<main class="container">
|
|
84
|
+
{{!-- Test Scripts --}}
|
|
77
85
|
<aside class="sidebar">
|
|
78
86
|
<h2>Tests</h2>
|
|
79
87
|
<div class="">
|
|
@@ -104,15 +112,13 @@
|
|
|
104
112
|
{{/each}}
|
|
105
113
|
</div>
|
|
106
114
|
</aside>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
115
|
<section>
|
|
116
|
+
{{!-- Overall summar --}}
|
|
111
117
|
<div id="summary">
|
|
112
118
|
<section class="grid">
|
|
113
119
|
<div>
|
|
114
120
|
<article>
|
|
115
|
-
<header>
|
|
121
|
+
<header>All Tests</header>
|
|
116
122
|
<p>{{totalCount}}</p>
|
|
117
123
|
</article>
|
|
118
124
|
</div>
|
|
@@ -125,7 +131,7 @@
|
|
|
125
131
|
<div>
|
|
126
132
|
<article>
|
|
127
133
|
<header>Failed</header>
|
|
128
|
-
<p class="text-
|
|
134
|
+
<p class="text-failure">{{failCount}}</p>
|
|
129
135
|
</article>
|
|
130
136
|
</div>
|
|
131
137
|
</section>
|
|
@@ -133,24 +139,34 @@
|
|
|
133
139
|
<div>
|
|
134
140
|
<article>
|
|
135
141
|
<header>Skipped</header>
|
|
136
|
-
<p>{{skipCount}}</p>
|
|
142
|
+
<p class="text-skip">{{skipCount}}</p>
|
|
137
143
|
</article>
|
|
138
144
|
</div>
|
|
139
145
|
<div>
|
|
140
146
|
<article>
|
|
141
|
-
<header>
|
|
142
|
-
<p class="text-
|
|
147
|
+
<header>Flaky</header>
|
|
148
|
+
<p class="text-flaky">{{flakyCount}}</p>
|
|
143
149
|
</article>
|
|
144
150
|
</div>
|
|
145
151
|
</section>
|
|
152
|
+
{{!-- Suite details with chart --}}
|
|
146
153
|
<section>
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
<
|
|
150
|
-
|
|
154
|
+
<article>
|
|
155
|
+
<header>Suite</header>
|
|
156
|
+
<div class="grid">
|
|
157
|
+
<div>
|
|
158
|
+
{{#if authorName}}<h4>Author: {{authorName}}</h4>{{/if}}
|
|
159
|
+
{{#if testType}}<h4>Test Type: {{testType}}</h4>{{/if}}
|
|
160
|
+
{{#if totalDuration}}<h4>Duration: {{totalDuration}}</h4>{{/if}}
|
|
161
|
+
</div>
|
|
162
|
+
<div class="chart-container">
|
|
163
|
+
<canvas id="testChart"></canvas>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</article>
|
|
151
167
|
</section>
|
|
152
168
|
</div>
|
|
153
|
-
|
|
169
|
+
{{!-- Test details --}}
|
|
154
170
|
<div id="testDetails" style="display: none;">
|
|
155
171
|
<!-- Back button should be outside the dynamic content -->
|
|
156
172
|
<button class="back-button" onclick="showSummary()">Back to Summary</button>
|
|
@@ -161,16 +177,36 @@
|
|
|
161
177
|
|
|
162
178
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"></script>
|
|
163
179
|
<script>
|
|
180
|
+
function escapeHtml(unsafe) {
|
|
181
|
+
return unsafe.replace(/[&<"']/g, function (match) {
|
|
182
|
+
const escapeMap = {
|
|
183
|
+
'&': '&',
|
|
184
|
+
'<': '<',
|
|
185
|
+
'>': '>',
|
|
186
|
+
'"': '"',
|
|
187
|
+
"'": '''
|
|
188
|
+
};
|
|
189
|
+
return escapeMap[match];
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
164
193
|
document.addEventListener('DOMContentLoaded', () => {
|
|
165
|
-
|
|
194
|
+
const testData = {{{ json results }}};
|
|
166
195
|
const testDetails = document.getElementById('testDetails');
|
|
167
196
|
const summary = document.getElementById('summary');
|
|
168
197
|
const backButton = document.querySelector('.back-button');
|
|
198
|
+
const testItems = document.querySelectorAll('[data-test-id]');
|
|
199
|
+
const searchInput = document.querySelector('input[name="search"]');
|
|
200
|
+
const searchForm = document.querySelector('form[role="search"]');
|
|
169
201
|
|
|
170
202
|
function showSummary() {
|
|
171
203
|
summary.style.display = 'block';
|
|
172
204
|
testDetails.style.display = 'none';
|
|
173
205
|
backButton.style.display = 'none';
|
|
206
|
+
clearHighlights();
|
|
207
|
+
}
|
|
208
|
+
function clearHighlights() {
|
|
209
|
+
testItems.forEach(item => item.classList.remove('highlight'));
|
|
174
210
|
}
|
|
175
211
|
|
|
176
212
|
window.showSummary = showSummary;
|
|
@@ -181,38 +217,71 @@
|
|
|
181
217
|
backButton.style.display = 'block';
|
|
182
218
|
testDetails.innerHTML = `
|
|
183
219
|
<button class="back-button" style="display: block" onclick="showSummary()">Back to Summary</button>
|
|
184
|
-
<
|
|
220
|
+
<h3>${test.title}</h3>
|
|
185
221
|
<div class="grid">
|
|
186
222
|
<div>
|
|
187
|
-
<
|
|
188
|
-
<p class="${test.status === 'passed' ? 'text-success' : 'text-
|
|
189
|
-
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
<h3>Errors</h3>
|
|
193
|
-
<pre>${test.errors.join('\n')}</pre>
|
|
194
|
-
` : ''}
|
|
223
|
+
<h4>Status</h4>
|
|
224
|
+
<p class="${test.status === 'passed' ? 'text-success' : 'text-failure'}">${test.status.toUpperCase()}</p>
|
|
225
|
+
${test.duration != '0s' ? `
|
|
226
|
+
<h4>Duration</h4>
|
|
227
|
+
<p>${test.duration}</p>` : ""}
|
|
195
228
|
</div>
|
|
196
229
|
<div>
|
|
197
230
|
${test.screenshotPath ? `
|
|
198
|
-
<
|
|
231
|
+
<h4>Screenshot</h4>
|
|
199
232
|
<img src="${test.screenshotPath}" alt="Screenshot">
|
|
200
233
|
` : ''}
|
|
201
|
-
${test.logs ? `
|
|
202
|
-
<h3>Logs</h3>
|
|
203
|
-
<pre>${test.logs}</pre>
|
|
204
|
-
` : ''}
|
|
205
234
|
</div>
|
|
206
235
|
</div>
|
|
207
|
-
|
|
236
|
+
<div>
|
|
237
|
+
${test.errors.length ? `
|
|
238
|
+
<h4>Errors</h4>
|
|
239
|
+
<pre>${escapeHtml(test.errors.join('\n'))}</pre>
|
|
240
|
+
` : ''}
|
|
241
|
+
</div>
|
|
242
|
+
<div>
|
|
243
|
+
${test.logs ? `
|
|
244
|
+
<h4>Logs</h4>
|
|
245
|
+
<pre>${escapeHtml(test.logs)}</pre>
|
|
246
|
+
` : ''}
|
|
247
|
+
</div>
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function searchTests(query) {
|
|
252
|
+
const lowerCaseQuery = query.toLowerCase();
|
|
253
|
+
if (lowerCaseQuery === '') {
|
|
254
|
+
testItems.forEach(item => {
|
|
255
|
+
item.style.display = 'block';
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
testItems.forEach(item => {
|
|
259
|
+
const title = item.textContent.toLowerCase();
|
|
260
|
+
if (title.includes(lowerCaseQuery)) {
|
|
261
|
+
item.style.display = 'block';
|
|
262
|
+
} else {
|
|
263
|
+
item.style.display = 'none';
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
208
267
|
}
|
|
209
268
|
|
|
210
|
-
|
|
269
|
+
searchForm.addEventListener('submit', event => {
|
|
270
|
+
event.preventDefault();
|
|
271
|
+
searchTests(searchInput.value);
|
|
272
|
+
});
|
|
273
|
+
searchInput.addEventListener('input', () => {
|
|
274
|
+
searchTests(searchInput.value);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
|
|
211
278
|
testItems.forEach(item => {
|
|
212
279
|
item.addEventListener('click', () => {
|
|
213
280
|
const testId = item.getAttribute('data-test-id');
|
|
214
281
|
const test = testData[testId];
|
|
215
282
|
displayTestDetails(test);
|
|
283
|
+
clearHighlights();
|
|
284
|
+
item.classList.add('highlight');
|
|
216
285
|
});
|
|
217
286
|
});
|
|
218
287
|
|
|
@@ -223,23 +292,15 @@
|
|
|
223
292
|
labels: ['Passed', 'Failed', 'Skipped'],
|
|
224
293
|
datasets: [{
|
|
225
294
|
data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}],
|
|
226
|
-
backgroundColor: ['#28a745', '#dc3545', '#
|
|
295
|
+
backgroundColor: ['#28a745', '#dc3545', '#d5d4a1']
|
|
227
296
|
}]
|
|
228
297
|
},
|
|
229
298
|
options: {
|
|
230
299
|
responsive: true,
|
|
231
300
|
maintainAspectRatio: false,
|
|
232
301
|
plugins: {
|
|
233
|
-
title: {
|
|
234
|
-
display: true,
|
|
235
|
-
text: 'Overall',
|
|
236
|
-
font: {
|
|
237
|
-
size: 18,
|
|
238
|
-
weight: 'bold'
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
302
|
legend: {
|
|
242
|
-
position: '
|
|
303
|
+
position: 'bottom'
|
|
243
304
|
}
|
|
244
305
|
}
|
|
245
306
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ortoni-report",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Playwright Report By LetCode with Koushik",
|
|
5
5
|
"scripts": {
|
|
6
|
+
"test": "npx playwright test",
|
|
6
7
|
"build": "tsup",
|
|
7
8
|
"release": "npm publish",
|
|
8
9
|
"lint": "tsc"
|
|
9
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"CHANGELOG.md"
|
|
15
|
+
],
|
|
10
16
|
"repository": {
|
|
11
17
|
"type": "git",
|
|
12
18
|
"url": "git+https://github.com/ortoniKC/ortoni-report"
|
|
@@ -19,20 +25,21 @@
|
|
|
19
25
|
"ortoni"
|
|
20
26
|
],
|
|
21
27
|
"author": "Koushik Chatterjee (LetCode with Koushik)",
|
|
22
|
-
"license": "
|
|
28
|
+
"license": "GPL-3.0-only",
|
|
23
29
|
"bugs": {
|
|
24
30
|
"url": "https://github.com/ortoniKC/ortoni-report/issues"
|
|
25
31
|
},
|
|
26
32
|
"homepage": "https://github.com/ortoniKC/ortoni-report#readme",
|
|
27
|
-
|
|
28
|
-
"hiq": "^4.2.3",
|
|
33
|
+
"dependencies": {
|
|
29
34
|
"colors": "^1.4.0",
|
|
30
|
-
"handlebars": "^4.7.8"
|
|
35
|
+
"handlebars": "^4.7.8",
|
|
36
|
+
"hiq": "^4.2.3",
|
|
37
|
+
"rimraf": "^5.0.7"
|
|
31
38
|
},
|
|
32
39
|
"devDependencies": {
|
|
40
|
+
"@changesets/cli": "^2.26.0",
|
|
33
41
|
"@playwright/test": "^1.44.1",
|
|
34
42
|
"@types/node": "^20.14.2",
|
|
35
|
-
"@changesets/cli": "^2.26.0",
|
|
36
43
|
"tsup": "^6.5.0",
|
|
37
44
|
"typescript": "^4.9.4"
|
|
38
45
|
},
|