ortoni-report 3.0.1 → 3.0.2
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 +13 -0
- package/dist/ortoni-report.d.mts +4 -0
- package/dist/ortoni-report.d.ts +4 -0
- package/dist/ortoni-report.js +144 -1
- package/dist/ortoni-report.mjs +144 -1
- package/dist/views/analytics.hbs +105 -0
- package/dist/views/head.hbs +1 -1
- package/dist/views/main.hbs +5 -0
- package/dist/views/sidebar.hbs +8 -0
- package/dist/views/userInfo.hbs +22 -33
- package/package.json +1 -1
- package/readme.md +35 -23
package/changelog.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Change Log:
|
|
2
2
|
|
|
3
|
+
## v3.0.2
|
|
4
|
+
|
|
5
|
+
#### 📦 New Features
|
|
6
|
+
1. Test Analytics dashboard
|
|
7
|
+
- Shows summary of total runs, totals test, passed, failed, pass rate and average duration
|
|
8
|
+
- Trends over time - upto 30 runs
|
|
9
|
+
- Top flaky test list
|
|
10
|
+
- slowest test list with average duration
|
|
11
|
+
|
|
12
|
+
#### 👌 Improvements
|
|
13
|
+
1. Set chart option as pie or doughnut for summary and projects
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
## v3.0.1
|
|
4
17
|
|
|
5
18
|
#### 👌 Improvements
|
package/dist/ortoni-report.d.mts
CHANGED
|
@@ -84,6 +84,10 @@ interface OrtoniReportConfig {
|
|
|
84
84
|
* @example { "key": "value" } as string
|
|
85
85
|
*/
|
|
86
86
|
meta?: Record<string, string>;
|
|
87
|
+
/**
|
|
88
|
+
* Chart type on the dashboard.
|
|
89
|
+
* @example "doughnut" | "pie" */
|
|
90
|
+
chartType?: "doughnut" | "pie";
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
declare class OrtoniReport implements Reporter {
|
package/dist/ortoni-report.d.ts
CHANGED
|
@@ -84,6 +84,10 @@ interface OrtoniReportConfig {
|
|
|
84
84
|
* @example { "key": "value" } as string
|
|
85
85
|
*/
|
|
86
86
|
meta?: Record<string, string>;
|
|
87
|
+
/**
|
|
88
|
+
* Chart type on the dashboard.
|
|
89
|
+
* @example "doughnut" | "pie" */
|
|
90
|
+
chartType?: "doughnut" | "pie";
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
declare class OrtoniReport implements Reporter {
|
package/dist/ortoni-report.js
CHANGED
|
@@ -194,6 +194,14 @@ function formatDateLocal(isoString) {
|
|
|
194
194
|
};
|
|
195
195
|
return new Intl.DateTimeFormat(void 0, options).format(date);
|
|
196
196
|
}
|
|
197
|
+
function formatDateNoTimezone(dateStr) {
|
|
198
|
+
const date = new Date(dateStr);
|
|
199
|
+
return date.toLocaleString("en-US", {
|
|
200
|
+
dateStyle: "medium",
|
|
201
|
+
timeStyle: "short",
|
|
202
|
+
timeZoneName: void 0
|
|
203
|
+
});
|
|
204
|
+
}
|
|
197
205
|
|
|
198
206
|
// src/helpers/HTMLGenerator.ts
|
|
199
207
|
var import_fs2 = __toESM(require("fs"));
|
|
@@ -219,6 +227,22 @@ var HTMLGenerator = class {
|
|
|
219
227
|
const template = import_handlebars.default.compile(templateSource);
|
|
220
228
|
return template({ ...data, inlineCss: cssContent });
|
|
221
229
|
}
|
|
230
|
+
async getReportData() {
|
|
231
|
+
return {
|
|
232
|
+
summary: await this.dbManager.getSummaryData(),
|
|
233
|
+
trends: await this.dbManager.getTrends(),
|
|
234
|
+
flakyTests: await this.dbManager.getFlakyTests(),
|
|
235
|
+
slowTests: await this.dbManager.getSlowTests()
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async chartTrendData() {
|
|
239
|
+
return {
|
|
240
|
+
labels: (await this.getReportData()).trends.map((t) => formatDateNoTimezone(t.run_date)),
|
|
241
|
+
passed: (await this.getReportData()).trends.map((t) => t.passed),
|
|
242
|
+
failed: (await this.getReportData()).trends.map((t) => t.failed),
|
|
243
|
+
avgDuration: (await this.getReportData()).trends.map((t) => t.avg_duration)
|
|
244
|
+
};
|
|
245
|
+
}
|
|
222
246
|
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
223
247
|
const totalTests = filteredResults.length;
|
|
224
248
|
const passedTests = results.filter((r) => r.status === "passed").length;
|
|
@@ -273,6 +297,9 @@ var HTMLGenerator = class {
|
|
|
273
297
|
allTags: Array.from(allTags),
|
|
274
298
|
showProject: this.ortoniConfig.showProject || false,
|
|
275
299
|
title: this.ortoniConfig.title || "Ortoni Playwright Test Report",
|
|
300
|
+
chartType: this.ortoniConfig.chartType || "pie",
|
|
301
|
+
reportAnalyticsData: await this.getReportData(),
|
|
302
|
+
chartData: await this.chartTrendData(),
|
|
276
303
|
...this.extractProjectStats(projectResults)
|
|
277
304
|
};
|
|
278
305
|
}
|
|
@@ -337,7 +364,8 @@ var HTMLGenerator = class {
|
|
|
337
364
|
"userInfo",
|
|
338
365
|
"project",
|
|
339
366
|
"testStatus",
|
|
340
|
-
"testIcons"
|
|
367
|
+
"testIcons",
|
|
368
|
+
"analytics"
|
|
341
369
|
].forEach((partialName) => {
|
|
342
370
|
import_handlebars.default.registerPartial(
|
|
343
371
|
partialName,
|
|
@@ -684,6 +712,121 @@ var DatabaseManager = class {
|
|
|
684
712
|
}
|
|
685
713
|
}
|
|
686
714
|
}
|
|
715
|
+
async getSummaryData() {
|
|
716
|
+
if (!this.db) {
|
|
717
|
+
console.error("OrtoniReport: Database not initialized");
|
|
718
|
+
return {
|
|
719
|
+
totalRuns: 0,
|
|
720
|
+
totalTests: 0,
|
|
721
|
+
passed: 0,
|
|
722
|
+
failed: 0,
|
|
723
|
+
passRate: 0,
|
|
724
|
+
avgDuration: 0
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
try {
|
|
728
|
+
const summary = await this.db.get(`
|
|
729
|
+
SELECT
|
|
730
|
+
(SELECT COUNT(*) FROM test_runs) as totalRuns,
|
|
731
|
+
(SELECT COUNT(*) FROM test_results) as totalTests,
|
|
732
|
+
(SELECT COUNT(*) FROM test_results WHERE status = 'passed') as passed,
|
|
733
|
+
(SELECT COUNT(*) FROM test_results WHERE status = 'failed') as failed,
|
|
734
|
+
(SELECT AVG(CAST(duration AS FLOAT)) FROM test_results) as avgDuration
|
|
735
|
+
`);
|
|
736
|
+
const passRate = summary.totalTests ? (summary.passed / summary.totalTests * 100).toFixed(2) : 0;
|
|
737
|
+
return {
|
|
738
|
+
totalRuns: summary.totalRuns,
|
|
739
|
+
totalTests: summary.totalTests,
|
|
740
|
+
passed: summary.passed,
|
|
741
|
+
failed: summary.failed,
|
|
742
|
+
passRate: parseFloat(passRate.toString()),
|
|
743
|
+
avgDuration: Math.round(summary.avgDuration || 0)
|
|
744
|
+
};
|
|
745
|
+
} catch (error) {
|
|
746
|
+
console.error("OrtoniReport: Error getting summary data:", error);
|
|
747
|
+
return {
|
|
748
|
+
totalRuns: 0,
|
|
749
|
+
totalTests: 0,
|
|
750
|
+
passed: 0,
|
|
751
|
+
failed: 0,
|
|
752
|
+
passRate: 0,
|
|
753
|
+
avgDuration: 0
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async getTrends(limit = 30) {
|
|
758
|
+
if (!this.db) {
|
|
759
|
+
console.error("OrtoniReport: Database not initialized");
|
|
760
|
+
return [];
|
|
761
|
+
}
|
|
762
|
+
try {
|
|
763
|
+
const rows = await this.db.all(`
|
|
764
|
+
SELECT trun.run_date,
|
|
765
|
+
SUM(CASE WHEN tr.status = 'passed' THEN 1 ELSE 0 END) AS passed,
|
|
766
|
+
SUM(CASE WHEN tr.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
767
|
+
AVG(CAST(tr.duration AS FLOAT)) AS avg_duration
|
|
768
|
+
FROM test_results tr
|
|
769
|
+
JOIN test_runs trun ON tr.run_id = trun.id
|
|
770
|
+
GROUP BY trun.run_date
|
|
771
|
+
ORDER BY trun.run_date DESC
|
|
772
|
+
LIMIT ?
|
|
773
|
+
`, [limit]);
|
|
774
|
+
return rows.map((row) => ({
|
|
775
|
+
...row,
|
|
776
|
+
run_date: formatDateLocal(row.run_date),
|
|
777
|
+
avg_duration: Math.round(row.avg_duration || 0)
|
|
778
|
+
}));
|
|
779
|
+
} catch (error) {
|
|
780
|
+
console.error("OrtoniReport: Error getting trends data:", error);
|
|
781
|
+
return [];
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async getFlakyTests(limit = 10) {
|
|
785
|
+
if (!this.db) {
|
|
786
|
+
console.error("OrtoniReport: Database not initialized");
|
|
787
|
+
return [];
|
|
788
|
+
}
|
|
789
|
+
try {
|
|
790
|
+
return await this.db.all(`
|
|
791
|
+
SELECT
|
|
792
|
+
test_id,
|
|
793
|
+
COUNT(*) AS total,
|
|
794
|
+
SUM(CASE WHEN status = 'flaky' THEN 1 ELSE 0 END) AS flaky
|
|
795
|
+
FROM test_results
|
|
796
|
+
GROUP BY test_id
|
|
797
|
+
HAVING flaky > 0
|
|
798
|
+
ORDER BY flaky DESC
|
|
799
|
+
LIMIT ?
|
|
800
|
+
`, [limit]);
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error("OrtoniReport: Error getting flaky tests:", error);
|
|
803
|
+
return [];
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
async getSlowTests(limit = 10) {
|
|
807
|
+
if (!this.db) {
|
|
808
|
+
console.error("OrtoniReport: Database not initialized");
|
|
809
|
+
return [];
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
const rows = await this.db.all(`
|
|
813
|
+
SELECT
|
|
814
|
+
test_id,
|
|
815
|
+
AVG(CAST(duration AS FLOAT)) AS avg_duration
|
|
816
|
+
FROM test_results
|
|
817
|
+
GROUP BY test_id
|
|
818
|
+
ORDER BY avg_duration DESC
|
|
819
|
+
LIMIT ?
|
|
820
|
+
`, [limit]);
|
|
821
|
+
return rows.map((row) => ({
|
|
822
|
+
test_id: row.test_id,
|
|
823
|
+
avg_duration: Math.round(row.avg_duration || 0)
|
|
824
|
+
}));
|
|
825
|
+
} catch (error) {
|
|
826
|
+
console.error("OrtoniReport: Error getting slow tests:", error);
|
|
827
|
+
return [];
|
|
828
|
+
}
|
|
829
|
+
}
|
|
687
830
|
};
|
|
688
831
|
|
|
689
832
|
// src/ortoni-report.ts
|
package/dist/ortoni-report.mjs
CHANGED
|
@@ -162,6 +162,14 @@ function formatDateLocal(isoString) {
|
|
|
162
162
|
};
|
|
163
163
|
return new Intl.DateTimeFormat(void 0, options).format(date);
|
|
164
164
|
}
|
|
165
|
+
function formatDateNoTimezone(dateStr) {
|
|
166
|
+
const date = new Date(dateStr);
|
|
167
|
+
return date.toLocaleString("en-US", {
|
|
168
|
+
dateStyle: "medium",
|
|
169
|
+
timeStyle: "short",
|
|
170
|
+
timeZoneName: void 0
|
|
171
|
+
});
|
|
172
|
+
}
|
|
165
173
|
|
|
166
174
|
// src/helpers/HTMLGenerator.ts
|
|
167
175
|
import fs2 from "fs";
|
|
@@ -187,6 +195,22 @@ var HTMLGenerator = class {
|
|
|
187
195
|
const template = Handlebars.compile(templateSource);
|
|
188
196
|
return template({ ...data, inlineCss: cssContent });
|
|
189
197
|
}
|
|
198
|
+
async getReportData() {
|
|
199
|
+
return {
|
|
200
|
+
summary: await this.dbManager.getSummaryData(),
|
|
201
|
+
trends: await this.dbManager.getTrends(),
|
|
202
|
+
flakyTests: await this.dbManager.getFlakyTests(),
|
|
203
|
+
slowTests: await this.dbManager.getSlowTests()
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async chartTrendData() {
|
|
207
|
+
return {
|
|
208
|
+
labels: (await this.getReportData()).trends.map((t) => formatDateNoTimezone(t.run_date)),
|
|
209
|
+
passed: (await this.getReportData()).trends.map((t) => t.passed),
|
|
210
|
+
failed: (await this.getReportData()).trends.map((t) => t.failed),
|
|
211
|
+
avgDuration: (await this.getReportData()).trends.map((t) => t.avg_duration)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
190
214
|
async prepareReportData(filteredResults, totalDuration, results, projectSet) {
|
|
191
215
|
const totalTests = filteredResults.length;
|
|
192
216
|
const passedTests = results.filter((r) => r.status === "passed").length;
|
|
@@ -241,6 +265,9 @@ var HTMLGenerator = class {
|
|
|
241
265
|
allTags: Array.from(allTags),
|
|
242
266
|
showProject: this.ortoniConfig.showProject || false,
|
|
243
267
|
title: this.ortoniConfig.title || "Ortoni Playwright Test Report",
|
|
268
|
+
chartType: this.ortoniConfig.chartType || "pie",
|
|
269
|
+
reportAnalyticsData: await this.getReportData(),
|
|
270
|
+
chartData: await this.chartTrendData(),
|
|
244
271
|
...this.extractProjectStats(projectResults)
|
|
245
272
|
};
|
|
246
273
|
}
|
|
@@ -305,7 +332,8 @@ var HTMLGenerator = class {
|
|
|
305
332
|
"userInfo",
|
|
306
333
|
"project",
|
|
307
334
|
"testStatus",
|
|
308
|
-
"testIcons"
|
|
335
|
+
"testIcons",
|
|
336
|
+
"analytics"
|
|
309
337
|
].forEach((partialName) => {
|
|
310
338
|
Handlebars.registerPartial(
|
|
311
339
|
partialName,
|
|
@@ -595,6 +623,121 @@ var DatabaseManager = class {
|
|
|
595
623
|
}
|
|
596
624
|
}
|
|
597
625
|
}
|
|
626
|
+
async getSummaryData() {
|
|
627
|
+
if (!this.db) {
|
|
628
|
+
console.error("OrtoniReport: Database not initialized");
|
|
629
|
+
return {
|
|
630
|
+
totalRuns: 0,
|
|
631
|
+
totalTests: 0,
|
|
632
|
+
passed: 0,
|
|
633
|
+
failed: 0,
|
|
634
|
+
passRate: 0,
|
|
635
|
+
avgDuration: 0
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
const summary = await this.db.get(`
|
|
640
|
+
SELECT
|
|
641
|
+
(SELECT COUNT(*) FROM test_runs) as totalRuns,
|
|
642
|
+
(SELECT COUNT(*) FROM test_results) as totalTests,
|
|
643
|
+
(SELECT COUNT(*) FROM test_results WHERE status = 'passed') as passed,
|
|
644
|
+
(SELECT COUNT(*) FROM test_results WHERE status = 'failed') as failed,
|
|
645
|
+
(SELECT AVG(CAST(duration AS FLOAT)) FROM test_results) as avgDuration
|
|
646
|
+
`);
|
|
647
|
+
const passRate = summary.totalTests ? (summary.passed / summary.totalTests * 100).toFixed(2) : 0;
|
|
648
|
+
return {
|
|
649
|
+
totalRuns: summary.totalRuns,
|
|
650
|
+
totalTests: summary.totalTests,
|
|
651
|
+
passed: summary.passed,
|
|
652
|
+
failed: summary.failed,
|
|
653
|
+
passRate: parseFloat(passRate.toString()),
|
|
654
|
+
avgDuration: Math.round(summary.avgDuration || 0)
|
|
655
|
+
};
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.error("OrtoniReport: Error getting summary data:", error);
|
|
658
|
+
return {
|
|
659
|
+
totalRuns: 0,
|
|
660
|
+
totalTests: 0,
|
|
661
|
+
passed: 0,
|
|
662
|
+
failed: 0,
|
|
663
|
+
passRate: 0,
|
|
664
|
+
avgDuration: 0
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async getTrends(limit = 30) {
|
|
669
|
+
if (!this.db) {
|
|
670
|
+
console.error("OrtoniReport: Database not initialized");
|
|
671
|
+
return [];
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
const rows = await this.db.all(`
|
|
675
|
+
SELECT trun.run_date,
|
|
676
|
+
SUM(CASE WHEN tr.status = 'passed' THEN 1 ELSE 0 END) AS passed,
|
|
677
|
+
SUM(CASE WHEN tr.status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
678
|
+
AVG(CAST(tr.duration AS FLOAT)) AS avg_duration
|
|
679
|
+
FROM test_results tr
|
|
680
|
+
JOIN test_runs trun ON tr.run_id = trun.id
|
|
681
|
+
GROUP BY trun.run_date
|
|
682
|
+
ORDER BY trun.run_date DESC
|
|
683
|
+
LIMIT ?
|
|
684
|
+
`, [limit]);
|
|
685
|
+
return rows.map((row) => ({
|
|
686
|
+
...row,
|
|
687
|
+
run_date: formatDateLocal(row.run_date),
|
|
688
|
+
avg_duration: Math.round(row.avg_duration || 0)
|
|
689
|
+
}));
|
|
690
|
+
} catch (error) {
|
|
691
|
+
console.error("OrtoniReport: Error getting trends data:", error);
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
async getFlakyTests(limit = 10) {
|
|
696
|
+
if (!this.db) {
|
|
697
|
+
console.error("OrtoniReport: Database not initialized");
|
|
698
|
+
return [];
|
|
699
|
+
}
|
|
700
|
+
try {
|
|
701
|
+
return await this.db.all(`
|
|
702
|
+
SELECT
|
|
703
|
+
test_id,
|
|
704
|
+
COUNT(*) AS total,
|
|
705
|
+
SUM(CASE WHEN status = 'flaky' THEN 1 ELSE 0 END) AS flaky
|
|
706
|
+
FROM test_results
|
|
707
|
+
GROUP BY test_id
|
|
708
|
+
HAVING flaky > 0
|
|
709
|
+
ORDER BY flaky DESC
|
|
710
|
+
LIMIT ?
|
|
711
|
+
`, [limit]);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
console.error("OrtoniReport: Error getting flaky tests:", error);
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
async getSlowTests(limit = 10) {
|
|
718
|
+
if (!this.db) {
|
|
719
|
+
console.error("OrtoniReport: Database not initialized");
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
const rows = await this.db.all(`
|
|
724
|
+
SELECT
|
|
725
|
+
test_id,
|
|
726
|
+
AVG(CAST(duration AS FLOAT)) AS avg_duration
|
|
727
|
+
FROM test_results
|
|
728
|
+
GROUP BY test_id
|
|
729
|
+
ORDER BY avg_duration DESC
|
|
730
|
+
LIMIT ?
|
|
731
|
+
`, [limit]);
|
|
732
|
+
return rows.map((row) => ({
|
|
733
|
+
test_id: row.test_id,
|
|
734
|
+
avg_duration: Math.round(row.avg_duration || 0)
|
|
735
|
+
}));
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.error("OrtoniReport: Error getting slow tests:", error);
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
}
|
|
598
741
|
};
|
|
599
742
|
|
|
600
743
|
// src/ortoni-report.ts
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<h1 class="title is-3">Analytics</h1>
|
|
3
|
+
<div class="columns is-multiline has-text-centered">
|
|
4
|
+
{{> summaryCard bg="hsl(var(--bulma-primary-h), var(--bulma-primary-s), var(--bulma-primary-l)) !important"
|
|
5
|
+
statusHeader="Total Runs" statusCount=reportAnalyticsData.summary.totalRuns}}
|
|
6
|
+
{{> summaryCard bg="hsl(var(--bulma-primary-h), var(--bulma-primary-s), var(--bulma-primary-l)) !important"
|
|
7
|
+
statusHeader="Total Tests" statusCount=reportAnalyticsData.summary.totalTests}}
|
|
8
|
+
{{> summaryCard bg="hsl(var(--bulma-success-h), var(--bulma-success-s), var(--bulma-success-l)) !important"
|
|
9
|
+
status="Passed" statusHeader="Passed" statusCount=reportAnalyticsData.summary.passed}}
|
|
10
|
+
{{> summaryCard bg="hsl(var(--bulma-danger-h), var(--bulma-danger-s), var(--bulma-danger-l)) !important"
|
|
11
|
+
status="Failed"
|
|
12
|
+
statusHeader="Failed" statusCount=reportAnalyticsData.summary.failed}}
|
|
13
|
+
{{> summaryCard bg="hsl(var(--bulma-info-h), var(--bulma-info-s), var(--bulma-info-l)) !important"
|
|
14
|
+
statusHeader="Pass Rate: %" statusCount=reportAnalyticsData.summary.passRate}}
|
|
15
|
+
{{> summaryCard bg="#69748c" statusHeader="Avg Duration (ms)" statusCount=reportAnalyticsData.summary.avgDuration}}
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<section class="box">
|
|
19
|
+
<h2 class="title is-4">Trends Over Time</h2>
|
|
20
|
+
<canvas id="trendChart" height="100"></canvas>
|
|
21
|
+
</section>
|
|
22
|
+
|
|
23
|
+
<section class="box">
|
|
24
|
+
<h2 class="title is-4">Top Flaky Tests</h2>
|
|
25
|
+
<table class="table is-striped is-fullwidth">
|
|
26
|
+
<thead>
|
|
27
|
+
<tr>
|
|
28
|
+
<th>Test ID</th>
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody>
|
|
32
|
+
{{#each reportAnalyticsData.flakyTests}}
|
|
33
|
+
<tr>
|
|
34
|
+
<td>{{this.test_id}}</td>
|
|
35
|
+
</tr>
|
|
36
|
+
{{/each}}
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
39
|
+
</section>
|
|
40
|
+
|
|
41
|
+
<section class="box">
|
|
42
|
+
<h2 class="title is-4">Slowest Tests</h2>
|
|
43
|
+
<table class="table is-striped is-fullwidth">
|
|
44
|
+
<thead>
|
|
45
|
+
<tr>
|
|
46
|
+
<th>Test ID</th>
|
|
47
|
+
<th>Avg Duration (ms)</th>
|
|
48
|
+
</tr>
|
|
49
|
+
</thead>
|
|
50
|
+
<tbody>
|
|
51
|
+
{{#each reportAnalyticsData.slowTests}}
|
|
52
|
+
<tr>
|
|
53
|
+
<td>{{this.test_id}}</td>
|
|
54
|
+
<td>{{this.avg_duration}}</td>
|
|
55
|
+
</tr>
|
|
56
|
+
{{/each}}
|
|
57
|
+
</tbody>
|
|
58
|
+
</table>
|
|
59
|
+
</section>
|
|
60
|
+
<script>
|
|
61
|
+
const ctx = document.getElementById('trendChart').getContext('2d');
|
|
62
|
+
const trendChart = new Chart(ctx, {
|
|
63
|
+
type: 'line',
|
|
64
|
+
data: {
|
|
65
|
+
labels: {{{ json chartData.labels }}},
|
|
66
|
+
datasets: [
|
|
67
|
+
{
|
|
68
|
+
label: 'Passed',
|
|
69
|
+
data: {{ json chartData.passed }},
|
|
70
|
+
borderColor: 'green',
|
|
71
|
+
fill: false
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
label: 'Failed',
|
|
75
|
+
data: {{ json chartData.failed }},
|
|
76
|
+
borderColor: 'red',
|
|
77
|
+
fill: false
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: 'Avg Duration (ms)',
|
|
81
|
+
data: {{ json chartData.avgDuration }},
|
|
82
|
+
borderColor: 'blue',
|
|
83
|
+
fill: false,
|
|
84
|
+
yAxisID: 'y1'
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
options: {
|
|
89
|
+
responsive: true,
|
|
90
|
+
scales: {
|
|
91
|
+
y: {
|
|
92
|
+
beginAtZero: true,
|
|
93
|
+
title: { display: true, text: 'Count' }
|
|
94
|
+
},
|
|
95
|
+
y1: {
|
|
96
|
+
beginAtZero: true,
|
|
97
|
+
position: 'right',
|
|
98
|
+
title: { display: true, text: 'Duration (ms)' },
|
|
99
|
+
grid: { drawOnChartArea: false }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
</script>
|
|
105
|
+
</div>
|
package/dist/views/head.hbs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<head>
|
|
2
2
|
<meta charset="UTF-8" />
|
|
3
3
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
4
|
-
<meta name="description" content="Playwright HTML report by LetCode Koushik - V3.0.
|
|
4
|
+
<meta name="description" content="Playwright HTML report by LetCode Koushik - V3.0.2" />
|
|
5
5
|
<title>{{title}}</title>
|
|
6
6
|
<link rel="icon" href="https://raw.githubusercontent.com/ortoniKC/ortoni-report/refs/heads/main/favicon.png"
|
|
7
7
|
type="image/x-icon" />
|
package/dist/views/main.hbs
CHANGED
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
</div>
|
|
49
49
|
</div>
|
|
50
50
|
</div>
|
|
51
|
+
<div id="analytics-section" class="content-section" style="display: none;">
|
|
52
|
+
{{> analytics}}
|
|
53
|
+
</div>
|
|
51
54
|
</main>
|
|
52
55
|
</div>
|
|
53
56
|
<script>
|
|
@@ -135,6 +138,7 @@
|
|
|
135
138
|
this.sidebarLinks = document.querySelectorAll('.sidebar-menu-link');
|
|
136
139
|
this.sections = document.querySelectorAll('.content-section');
|
|
137
140
|
this.testDetailsSection = document.getElementById('testDetails');
|
|
141
|
+
this.analyticsSection = document.getElementById('analytics-section');
|
|
138
142
|
this.sidebar = document.getElementById('sidebar');
|
|
139
143
|
this.mainContent = document.querySelector('.main-content');
|
|
140
144
|
this.init();
|
|
@@ -178,6 +182,7 @@
|
|
|
178
182
|
showSection(sectionId) {
|
|
179
183
|
// Hide test details if showing a main section
|
|
180
184
|
this.testDetailsSection.style.display = 'none';
|
|
185
|
+
this.analyticsSection.style.display = 'none';
|
|
181
186
|
|
|
182
187
|
// Show the selected section
|
|
183
188
|
this.sections.forEach(section => {
|
package/dist/views/sidebar.hbs
CHANGED
|
@@ -222,6 +222,14 @@
|
|
|
222
222
|
<span class="sidebar-menu-text">Tests</span>
|
|
223
223
|
</a>
|
|
224
224
|
</li>
|
|
225
|
+
<li class="sidebar-menu-item">
|
|
226
|
+
<a class="sidebar-menu-link" data-section="analytics">
|
|
227
|
+
<span class="icon">
|
|
228
|
+
<i class="fa fa-chart-line"></i>
|
|
229
|
+
</span>
|
|
230
|
+
<span class="sidebar-menu-text">Analytics</span>
|
|
231
|
+
</a>
|
|
232
|
+
</li>
|
|
225
233
|
</ul>
|
|
226
234
|
<a class="theme" data-theme-status="{{preferredTheme}}" id="toggle-theme">
|
|
227
235
|
<span class="icon">
|
package/dist/views/userInfo.hbs
CHANGED
|
@@ -113,49 +113,38 @@
|
|
|
113
113
|
<script>
|
|
114
114
|
const overallChart = document.getElementById('testChart');
|
|
115
115
|
new Chart(overallChart, {
|
|
116
|
-
type:
|
|
116
|
+
type: '{{ chartType }}',
|
|
117
117
|
data: {
|
|
118
|
-
labels: ['Passed', 'Failed', 'Skipped', 'Flaky'],
|
|
118
|
+
labels: ['Passed', 'Failed', 'Skipped', 'Flaky', 'Retry'],
|
|
119
119
|
datasets: [{
|
|
120
120
|
data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}, {{ flakyCount }}, {{ retryCount }}],
|
|
121
121
|
backgroundColor: ['#28a745', '#ff6685', '#66d1ff', '#ffb70f', '#69748c']}]
|
|
122
122
|
},
|
|
123
123
|
options: {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
responsive: true,
|
|
135
|
-
maintainAspectRatio: false,
|
|
136
|
-
borderJoinStyle: 'bevel',
|
|
137
|
-
plugins: {
|
|
138
|
-
legend: {
|
|
139
|
-
position: 'bottom',
|
|
140
|
-
labels: {
|
|
141
|
-
filter: function (legendItem, chartData) {
|
|
142
|
-
const value = chartData.datasets[0].data[legendItem.index];
|
|
143
|
-
return value !== 0;
|
|
124
|
+
responsive: true,
|
|
125
|
+
maintainAspectRatio: false,
|
|
126
|
+
plugins: {
|
|
127
|
+
legend: {
|
|
128
|
+
position: 'bottom',
|
|
129
|
+
labels: {
|
|
130
|
+
filter: function (legendItem, chartData) {
|
|
131
|
+
const value = chartData.datasets[0].data[legendItem.index];
|
|
132
|
+
return value !== 0;
|
|
133
|
+
}
|
|
144
134
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
135
|
+
},
|
|
136
|
+
tooltip: {
|
|
137
|
+
callbacks: {
|
|
138
|
+
label: function (tooltipItem) {
|
|
139
|
+
const total = tooltipItem.dataset.data.reduce((a, b) => a + b, 0);
|
|
140
|
+
const value = tooltipItem.raw;
|
|
141
|
+
const percentage = ((value / total) * 100).toFixed(2);
|
|
142
|
+
return `${tooltipItem.label}: ${value} tests (${percentage}%)`;
|
|
143
|
+
}
|
|
154
144
|
}
|
|
155
145
|
}
|
|
156
146
|
}
|
|
157
147
|
}
|
|
158
|
-
}
|
|
159
148
|
});
|
|
160
149
|
|
|
161
150
|
const projectChart = document.getElementById('projectChart');
|
|
@@ -173,7 +162,7 @@
|
|
|
173
162
|
}
|
|
174
163
|
const backgroundColors = totalTests.map(() => generateRandomColor());
|
|
175
164
|
new Chart(projectChart, {
|
|
176
|
-
type: '
|
|
165
|
+
type: '{{ chartType }}',
|
|
177
166
|
data: {
|
|
178
167
|
labels: projectNames,
|
|
179
168
|
datasets: [{
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -12,41 +12,51 @@ A comprehensive and visually appealing HTML report generator tailored for Playwr
|
|
|
12
12
|
|
|
13
13
|
### 1. **Organization & Navigation**
|
|
14
14
|
|
|
15
|
-
- **Sidebar Navigation**:
|
|
16
|
-
- **
|
|
17
|
-
- **Dashboard**:
|
|
18
|
-
- **Tests**:
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
15
|
+
- **Sidebar Navigation**: Enjoy a clean and structured layout for seamless navigation.
|
|
16
|
+
- **Sections**:
|
|
17
|
+
- **Dashboard**: High-level overview of test analytics and trends.
|
|
18
|
+
- **Tests**: Dive into individual test details including logs, screenshots, and errors.
|
|
19
|
+
- **Analytics**: Track overall execution metrics, trends, and flaky/slow test insights.
|
|
20
|
+
- **Hierarchical Grouping**: Tests are organized by filename, suite, and project for better traceability.
|
|
21
|
+
- **Test History View**: Access up to 10 recent executions, categorized by suite and project.
|
|
22
|
+
- **Configurable Integration**: Easy setup with Playwright using TypeScript/JavaScript, along with customizable preferences.
|
|
23
|
+
- **Advanced Filtering**: Filter by project, tags, and status — with a reset option for full visibility.
|
|
23
24
|
|
|
24
25
|
### 2. **Detailed Reporting**
|
|
25
26
|
|
|
26
|
-
- **Comprehensive Test
|
|
27
|
-
- **
|
|
28
|
-
- **Selected Status Display**: The UI highlights the active status filter for clarity.
|
|
27
|
+
- **Comprehensive Test Data**: Includes status, duration, tags, logs, errors, screenshots, videos, and trace viewer.
|
|
28
|
+
- **Native Trace Viewer**: Directly open the trace viewer within the reporter.
|
|
29
29
|
|
|
30
30
|
### 3. **Visualization & Insights**
|
|
31
31
|
|
|
32
|
-
- **
|
|
33
|
-
-
|
|
34
|
-
- **
|
|
32
|
+
- **Test Analytics Dashboard** 🌟 **(New!)**
|
|
33
|
+
- Summary of total test runs, passed/failed counts, pass rate, and average duration.
|
|
34
|
+
- **Trends Over Time**: Line chart showing test results across the last 30 runs.
|
|
35
|
+
- **Top Flaky Tests**: Identify unstable tests quickly.
|
|
36
|
+
- **Slowest Tests**: View tests with highest average durations.
|
|
37
|
+
|
|
38
|
+
- **Chart Visualizations**:
|
|
39
|
+
- Pie or doughnut charts for test summary and per-project breakdowns **(Improved!)**
|
|
40
|
+
- Bar charts for project-specific comparisons.
|
|
41
|
+
- **Line Chart for Trends**: Visualize execution status progression over time.
|
|
42
|
+
|
|
43
|
+
- **Colorful UI**: Redesigned with vibrant, high-contrast visuals for improved readability and engagement.
|
|
35
44
|
|
|
36
45
|
### 4. **Customization & Personalization**
|
|
37
46
|
|
|
38
|
-
- **
|
|
39
|
-
- **Add
|
|
40
|
-
- **
|
|
41
|
-
- **Custom
|
|
47
|
+
- **Theme Support**: Switch between **light** and **dark** themes for a comfortable viewing experience.
|
|
48
|
+
- **Custom Branding**: Add your company or project logo for a branded look.
|
|
49
|
+
- **Flexible Attachments**: Choose between Base64 or file path formats for media files.
|
|
50
|
+
- **Custom Paths**: Define report filenames and output folders as needed.
|
|
42
51
|
|
|
43
52
|
### 5. **User Experience & Usability**
|
|
44
53
|
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **Meta**: Add user
|
|
54
|
+
- **Search & Reset**: Quickly search by keyword or status, with easy reset controls.
|
|
55
|
+
- **Skip Management**: Skipped tests are hidden by default to declutter views.
|
|
56
|
+
- **Self-Contained Reports**: Easily share and review offline-friendly reports.
|
|
57
|
+
- **Multi-Filters**: Combine filters for targeted test analysis.
|
|
58
|
+
- **Meta Information**: Add custom user or environment metadata to reports.
|
|
59
|
+
- **CLI**: Open the reporter anytime using the builin CLI
|
|
50
60
|
|
|
51
61
|
---
|
|
52
62
|
|
|
@@ -81,6 +91,7 @@ const reportConfig: OrtoniReportConfig = {
|
|
|
81
91
|
base64Image: false,
|
|
82
92
|
stdIO: false,
|
|
83
93
|
preferredTheme: "light",
|
|
94
|
+
chartType: "doughnut" | "pie";
|
|
84
95
|
meta: {
|
|
85
96
|
project: "Playwright",
|
|
86
97
|
version: "3.0.0",
|
|
@@ -115,6 +126,7 @@ const reportConfig = {
|
|
|
115
126
|
base64Image: false,
|
|
116
127
|
stdIO: false,
|
|
117
128
|
preferredTheme: "light",
|
|
129
|
+
chartType: "doughnut" | "pie";
|
|
118
130
|
meta: {
|
|
119
131
|
project: "Playwright",
|
|
120
132
|
version: "3.0.0",
|