ortoni-report 1.1.2 → 1.1.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.
- package/changelog.md +37 -0
- package/dist/css/main.css +22445 -0
- package/dist/icon/flaky.png +0 -0
- package/dist/ortoni-report.d.ts +24 -6
- package/dist/ortoni-report.js +49 -37
- package/dist/ortoni-report.mjs +49 -37
- package/dist/report-template.hbs +483 -345
- package/dist/types/reporterConfig.js +2 -0
- package/dist/types/testResults.js +2 -0
- package/dist/utils/modal.js +12 -72
- package/dist/utils/utils.js +44 -0
- package/package.json +47 -49
- package/readme.md +52 -14
- package/dist/css/pico.css +0 -2802
|
Binary file
|
package/dist/ortoni-report.d.ts
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
1
|
+
import { TestStep, Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
2
|
|
|
3
|
-
interface
|
|
3
|
+
interface OrtoniReportConfig {
|
|
4
4
|
projectName?: string;
|
|
5
5
|
authorName?: string;
|
|
6
6
|
testType?: string;
|
|
7
|
+
preferredTheme?: 'light' | 'dark';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface TestResultData {
|
|
11
|
+
retry: string;
|
|
12
|
+
isRetry: 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: string;
|
|
19
|
+
errors: any[];
|
|
20
|
+
steps: TestStep[];
|
|
21
|
+
logs: string;
|
|
22
|
+
screenshotPath: string | null;
|
|
23
|
+
filePath: any;
|
|
24
|
+
projects: Set<string>;
|
|
7
25
|
}
|
|
8
26
|
|
|
9
27
|
declare class OrtoniReport implements Reporter {
|
|
@@ -11,13 +29,13 @@ declare class OrtoniReport implements Reporter {
|
|
|
11
29
|
private groupedResults;
|
|
12
30
|
private suiteName;
|
|
13
31
|
private config;
|
|
14
|
-
constructor(config?:
|
|
32
|
+
constructor(config?: OrtoniReportConfig);
|
|
15
33
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
16
34
|
onTestBegin(test: TestCase, result: TestResult): void;
|
|
35
|
+
private projectSet;
|
|
17
36
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
18
|
-
private _successRate;
|
|
19
37
|
onEnd(result: FullResult): void;
|
|
20
|
-
generateHTML(): string;
|
|
38
|
+
generateHTML(filteredResults: TestResultData[], totalDuration: string): string;
|
|
21
39
|
}
|
|
22
40
|
|
|
23
|
-
export { OrtoniReport as default };
|
|
41
|
+
export { OrtoniReportConfig, OrtoniReport as default };
|
package/dist/ortoni-report.js
CHANGED
|
@@ -30,7 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/ortoni-report.ts
|
|
31
31
|
var ortoni_report_exports = {};
|
|
32
32
|
__export(ortoni_report_exports, {
|
|
33
|
-
default: () =>
|
|
33
|
+
default: () => OrtoniReport
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(ortoni_report_exports);
|
|
36
36
|
var import_fs = __toESM(require("fs"));
|
|
@@ -45,11 +45,22 @@ function msToTime(duration) {
|
|
|
45
45
|
const seconds = Math.floor(duration / 1e3 % 60);
|
|
46
46
|
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
47
47
|
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
let result = "";
|
|
49
|
+
if (hours > 0) {
|
|
50
|
+
result += `${hours}h:`;
|
|
51
|
+
}
|
|
52
|
+
if (minutes > 0 || hours > 0) {
|
|
53
|
+
result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
|
|
54
|
+
}
|
|
55
|
+
if (seconds > 0 || minutes > 0 || hours > 0) {
|
|
56
|
+
result += `${seconds < 10 ? "0" + seconds : seconds}s`;
|
|
57
|
+
}
|
|
58
|
+
if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
|
|
59
|
+
result += `${milliseconds}ms`;
|
|
60
|
+
} else if (milliseconds > 0) {
|
|
61
|
+
result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
53
64
|
}
|
|
54
65
|
function normalizeFilePath(filePath) {
|
|
55
66
|
const normalizedPath = import_path.default.normalize(filePath);
|
|
@@ -67,16 +78,11 @@ function formatDate(date) {
|
|
|
67
78
|
var OrtoniReport = class {
|
|
68
79
|
constructor(config = {}) {
|
|
69
80
|
this.results = [];
|
|
70
|
-
this.
|
|
81
|
+
this.projectSet = /* @__PURE__ */ new Set();
|
|
71
82
|
this.config = config;
|
|
72
83
|
}
|
|
73
84
|
onBegin(config, suite) {
|
|
74
85
|
this.results = [];
|
|
75
|
-
const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots");
|
|
76
|
-
if (import_fs.default.existsSync(screenshotsDir)) {
|
|
77
|
-
import_fs.default.rmSync(screenshotsDir, { recursive: true, force: true });
|
|
78
|
-
}
|
|
79
|
-
import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
|
|
80
86
|
}
|
|
81
87
|
onTestBegin(test, result) {
|
|
82
88
|
}
|
|
@@ -85,46 +91,45 @@ var OrtoniReport = class {
|
|
|
85
91
|
if (test.outcome() === "flaky") {
|
|
86
92
|
status = "flaky";
|
|
87
93
|
}
|
|
94
|
+
this.projectSet.add(test.titlePath()[1]);
|
|
88
95
|
const testResult = {
|
|
96
|
+
retry: result.retry > 0 ? "retry" : "",
|
|
89
97
|
isRetry: result.retry,
|
|
90
|
-
totalDuration: "",
|
|
91
98
|
projectName: test.titlePath()[1],
|
|
92
|
-
// Get the project name
|
|
93
99
|
suite: test.titlePath()[3],
|
|
94
|
-
// Adjust the index based on your suite hierarchy
|
|
95
100
|
title: test.title,
|
|
96
101
|
status,
|
|
97
102
|
flaky: test.outcome(),
|
|
98
103
|
duration: msToTime(result.duration),
|
|
99
104
|
errors: result.errors.map((e) => import_safe.default.strip(e.message || e.toString())),
|
|
100
105
|
steps: result.steps.map((step) => ({
|
|
101
|
-
|
|
106
|
+
titlePath: step.titlePath,
|
|
102
107
|
category: step.category,
|
|
103
108
|
duration: step.duration,
|
|
104
|
-
|
|
109
|
+
error: step.error,
|
|
110
|
+
location: step.location,
|
|
111
|
+
parent: step.parent,
|
|
112
|
+
startTime: step.startTime,
|
|
113
|
+
steps: step.steps,
|
|
114
|
+
title: step.title
|
|
105
115
|
})),
|
|
106
116
|
logs: import_safe.default.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
|
|
107
117
|
screenshotPath: null,
|
|
108
|
-
filePath: normalizeFilePath(test.titlePath()[2])
|
|
118
|
+
filePath: normalizeFilePath(test.titlePath()[2]),
|
|
119
|
+
projects: this.projectSet
|
|
109
120
|
};
|
|
110
121
|
if (result.attachments) {
|
|
111
|
-
const screenshotsDir = import_path2.default.resolve(process.cwd(), "screenshots", test.id);
|
|
112
|
-
if (!import_fs.default.existsSync(screenshotsDir)) {
|
|
113
|
-
import_fs.default.mkdirSync(screenshotsDir, { recursive: true });
|
|
114
|
-
}
|
|
115
122
|
const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
|
|
116
123
|
if (screenshot && screenshot.path) {
|
|
117
124
|
const screenshotContent = import_fs.default.readFileSync(screenshot.path, "base64");
|
|
118
|
-
|
|
119
|
-
import_fs.default.writeFileSync(import_path2.default.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
|
|
120
|
-
testResult.screenshotPath = screenshotFileName;
|
|
125
|
+
testResult.screenshotPath = screenshotContent;
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
this.results.push(testResult);
|
|
124
129
|
}
|
|
125
130
|
onEnd(result) {
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
|
|
132
|
+
const totalDuration = msToTime(result.duration);
|
|
128
133
|
this.groupedResults = this.results.reduce((acc, result2, index) => {
|
|
129
134
|
const filePath = result2.filePath;
|
|
130
135
|
const suiteName = result2.suite;
|
|
@@ -160,29 +165,37 @@ var OrtoniReport = class {
|
|
|
160
165
|
import_handlebars.default.registerHelper("gt", function(a, b) {
|
|
161
166
|
return a > b;
|
|
162
167
|
});
|
|
163
|
-
const html = this.generateHTML();
|
|
168
|
+
const html = this.generateHTML(filteredResults, totalDuration);
|
|
164
169
|
const outputPath = import_path2.default.resolve(process.cwd(), "ortoni-report.html");
|
|
165
170
|
import_fs.default.writeFileSync(outputPath, html);
|
|
166
171
|
console.log(`Ortoni HTML report generated at ${outputPath}`);
|
|
167
172
|
}
|
|
168
|
-
generateHTML() {
|
|
173
|
+
generateHTML(filteredResults, totalDuration) {
|
|
174
|
+
const totalTests = filteredResults.length;
|
|
175
|
+
const passedTests = this.results.filter((r) => r.status === "passed").length;
|
|
176
|
+
const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
|
|
177
|
+
const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
|
|
178
|
+
const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
169
179
|
const templateSource = import_fs.default.readFileSync(import_path2.default.resolve(__dirname, "report-template.hbs"), "utf-8");
|
|
170
180
|
const template = import_handlebars.default.compile(templateSource);
|
|
171
181
|
const data = {
|
|
172
|
-
totalDuration
|
|
182
|
+
totalDuration,
|
|
173
183
|
suiteName: this.suiteName,
|
|
174
184
|
results: this.results,
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
retryCount: this.results.filter((r) => r.isRetry).length,
|
|
186
|
+
passCount: passedTests,
|
|
187
|
+
failCount: failed,
|
|
177
188
|
skipCount: this.results.filter((r) => r.status === "skipped").length,
|
|
178
|
-
flakyCount:
|
|
179
|
-
totalCount:
|
|
189
|
+
flakyCount: flakyTests,
|
|
190
|
+
totalCount: filteredResults.length,
|
|
180
191
|
groupedResults: this.groupedResults,
|
|
181
192
|
projectName: this.config.projectName,
|
|
182
193
|
authorName: this.config.authorName,
|
|
183
194
|
testType: this.config.testType,
|
|
184
|
-
|
|
185
|
-
|
|
195
|
+
preferredTheme: this.config.preferredTheme,
|
|
196
|
+
successRate,
|
|
197
|
+
lastRunDate: formatDate(/* @__PURE__ */ new Date()),
|
|
198
|
+
projects: this.projectSet
|
|
186
199
|
};
|
|
187
200
|
return template(data);
|
|
188
201
|
}
|
|
@@ -201,4 +214,3 @@ function safeStringify(obj, indent = 2) {
|
|
|
201
214
|
cache.clear();
|
|
202
215
|
return json;
|
|
203
216
|
}
|
|
204
|
-
var ortoni_report_default = OrtoniReport;
|
package/dist/ortoni-report.mjs
CHANGED
|
@@ -11,11 +11,22 @@ function msToTime(duration) {
|
|
|
11
11
|
const seconds = Math.floor(duration / 1e3 % 60);
|
|
12
12
|
const minutes = Math.floor(duration / (1e3 * 60) % 60);
|
|
13
13
|
const hours = Math.floor(duration / (1e3 * 60 * 60) % 24);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
let result = "";
|
|
15
|
+
if (hours > 0) {
|
|
16
|
+
result += `${hours}h:`;
|
|
17
|
+
}
|
|
18
|
+
if (minutes > 0 || hours > 0) {
|
|
19
|
+
result += `${minutes < 10 ? "0" + minutes : minutes}m:`;
|
|
20
|
+
}
|
|
21
|
+
if (seconds > 0 || minutes > 0 || hours > 0) {
|
|
22
|
+
result += `${seconds < 10 ? "0" + seconds : seconds}s`;
|
|
23
|
+
}
|
|
24
|
+
if (milliseconds > 0 && !(seconds > 0 || minutes > 0 || hours > 0)) {
|
|
25
|
+
result += `${milliseconds}ms`;
|
|
26
|
+
} else if (milliseconds > 0) {
|
|
27
|
+
result += `:${milliseconds < 100 ? "0" + milliseconds : milliseconds}ms`;
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
19
30
|
}
|
|
20
31
|
function normalizeFilePath(filePath) {
|
|
21
32
|
const normalizedPath = path.normalize(filePath);
|
|
@@ -33,16 +44,11 @@ function formatDate(date) {
|
|
|
33
44
|
var OrtoniReport = class {
|
|
34
45
|
constructor(config = {}) {
|
|
35
46
|
this.results = [];
|
|
36
|
-
this.
|
|
47
|
+
this.projectSet = /* @__PURE__ */ new Set();
|
|
37
48
|
this.config = config;
|
|
38
49
|
}
|
|
39
50
|
onBegin(config, suite) {
|
|
40
51
|
this.results = [];
|
|
41
|
-
const screenshotsDir = path2.resolve(process.cwd(), "screenshots");
|
|
42
|
-
if (fs.existsSync(screenshotsDir)) {
|
|
43
|
-
fs.rmSync(screenshotsDir, { recursive: true, force: true });
|
|
44
|
-
}
|
|
45
|
-
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
46
52
|
}
|
|
47
53
|
onTestBegin(test, result) {
|
|
48
54
|
}
|
|
@@ -51,46 +57,45 @@ var OrtoniReport = class {
|
|
|
51
57
|
if (test.outcome() === "flaky") {
|
|
52
58
|
status = "flaky";
|
|
53
59
|
}
|
|
60
|
+
this.projectSet.add(test.titlePath()[1]);
|
|
54
61
|
const testResult = {
|
|
62
|
+
retry: result.retry > 0 ? "retry" : "",
|
|
55
63
|
isRetry: result.retry,
|
|
56
|
-
totalDuration: "",
|
|
57
64
|
projectName: test.titlePath()[1],
|
|
58
|
-
// Get the project name
|
|
59
65
|
suite: test.titlePath()[3],
|
|
60
|
-
// Adjust the index based on your suite hierarchy
|
|
61
66
|
title: test.title,
|
|
62
67
|
status,
|
|
63
68
|
flaky: test.outcome(),
|
|
64
69
|
duration: msToTime(result.duration),
|
|
65
70
|
errors: result.errors.map((e) => colors.strip(e.message || e.toString())),
|
|
66
71
|
steps: result.steps.map((step) => ({
|
|
67
|
-
|
|
72
|
+
titlePath: step.titlePath,
|
|
68
73
|
category: step.category,
|
|
69
74
|
duration: step.duration,
|
|
70
|
-
|
|
75
|
+
error: step.error,
|
|
76
|
+
location: step.location,
|
|
77
|
+
parent: step.parent,
|
|
78
|
+
startTime: step.startTime,
|
|
79
|
+
steps: step.steps,
|
|
80
|
+
title: step.title
|
|
71
81
|
})),
|
|
72
82
|
logs: colors.strip(result.stdout.concat(result.stderr).map((log) => log).join("\n")),
|
|
73
83
|
screenshotPath: null,
|
|
74
|
-
filePath: normalizeFilePath(test.titlePath()[2])
|
|
84
|
+
filePath: normalizeFilePath(test.titlePath()[2]),
|
|
85
|
+
projects: this.projectSet
|
|
75
86
|
};
|
|
76
87
|
if (result.attachments) {
|
|
77
|
-
const screenshotsDir = path2.resolve(process.cwd(), "screenshots", test.id);
|
|
78
|
-
if (!fs.existsSync(screenshotsDir)) {
|
|
79
|
-
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
80
|
-
}
|
|
81
88
|
const screenshot = result.attachments.find((attachment) => attachment.name === "screenshot");
|
|
82
89
|
if (screenshot && screenshot.path) {
|
|
83
90
|
const screenshotContent = fs.readFileSync(screenshot.path, "base64");
|
|
84
|
-
|
|
85
|
-
fs.writeFileSync(path2.resolve(process.cwd(), screenshotFileName), screenshotContent, "base64");
|
|
86
|
-
testResult.screenshotPath = screenshotFileName;
|
|
91
|
+
testResult.screenshotPath = screenshotContent;
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
this.results.push(testResult);
|
|
90
95
|
}
|
|
91
96
|
onEnd(result) {
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
const filteredResults = this.results.filter((r) => r.status !== "skipped" && !r.isRetry);
|
|
98
|
+
const totalDuration = msToTime(result.duration);
|
|
94
99
|
this.groupedResults = this.results.reduce((acc, result2, index) => {
|
|
95
100
|
const filePath = result2.filePath;
|
|
96
101
|
const suiteName = result2.suite;
|
|
@@ -126,29 +131,37 @@ var OrtoniReport = class {
|
|
|
126
131
|
Handlebars.registerHelper("gt", function(a, b) {
|
|
127
132
|
return a > b;
|
|
128
133
|
});
|
|
129
|
-
const html = this.generateHTML();
|
|
134
|
+
const html = this.generateHTML(filteredResults, totalDuration);
|
|
130
135
|
const outputPath = path2.resolve(process.cwd(), "ortoni-report.html");
|
|
131
136
|
fs.writeFileSync(outputPath, html);
|
|
132
137
|
console.log(`Ortoni HTML report generated at ${outputPath}`);
|
|
133
138
|
}
|
|
134
|
-
generateHTML() {
|
|
139
|
+
generateHTML(filteredResults, totalDuration) {
|
|
140
|
+
const totalTests = filteredResults.length;
|
|
141
|
+
const passedTests = this.results.filter((r) => r.status === "passed").length;
|
|
142
|
+
const flakyTests = this.results.filter((r) => r.flaky === "flaky").length;
|
|
143
|
+
const failed = filteredResults.filter((r) => r.status === "failed" || r.status === "timedOut").length;
|
|
144
|
+
const successRate = ((passedTests + flakyTests) / totalTests * 100).toFixed(2);
|
|
135
145
|
const templateSource = fs.readFileSync(path2.resolve(__dirname, "report-template.hbs"), "utf-8");
|
|
136
146
|
const template = Handlebars.compile(templateSource);
|
|
137
147
|
const data = {
|
|
138
|
-
totalDuration
|
|
148
|
+
totalDuration,
|
|
139
149
|
suiteName: this.suiteName,
|
|
140
150
|
results: this.results,
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
retryCount: this.results.filter((r) => r.isRetry).length,
|
|
152
|
+
passCount: passedTests,
|
|
153
|
+
failCount: failed,
|
|
143
154
|
skipCount: this.results.filter((r) => r.status === "skipped").length,
|
|
144
|
-
flakyCount:
|
|
145
|
-
totalCount:
|
|
155
|
+
flakyCount: flakyTests,
|
|
156
|
+
totalCount: filteredResults.length,
|
|
146
157
|
groupedResults: this.groupedResults,
|
|
147
158
|
projectName: this.config.projectName,
|
|
148
159
|
authorName: this.config.authorName,
|
|
149
160
|
testType: this.config.testType,
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
preferredTheme: this.config.preferredTheme,
|
|
162
|
+
successRate,
|
|
163
|
+
lastRunDate: formatDate(/* @__PURE__ */ new Date()),
|
|
164
|
+
projects: this.projectSet
|
|
152
165
|
};
|
|
153
166
|
return template(data);
|
|
154
167
|
}
|
|
@@ -167,7 +180,6 @@ function safeStringify(obj, indent = 2) {
|
|
|
167
180
|
cache.clear();
|
|
168
181
|
return json;
|
|
169
182
|
}
|
|
170
|
-
var ortoni_report_default = OrtoniReport;
|
|
171
183
|
export {
|
|
172
|
-
|
|
184
|
+
OrtoniReport as default
|
|
173
185
|
};
|