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 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
@@ -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 {
@@ -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 {
@@ -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
@@ -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>
@@ -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.1" />
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" />
@@ -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 => {
@@ -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">
@@ -113,49 +113,38 @@
113
113
  <script>
114
114
  const overallChart = document.getElementById('testChart');
115
115
  new Chart(overallChart, {
116
- type: "polarArea",
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
- scales: {
125
- r: {
126
- ticks: {
127
- display: false
128
- },
129
- grid: {
130
- color: '#e0e0e0'
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
- tooltip: {
148
- callbacks: {
149
- label: function (tooltipItem) {
150
- const total = tooltipItem.dataset.data.reduce((a, b) => a + b, 0);
151
- const value = tooltipItem.raw;
152
- const percentage = ((value / total) * 100).toFixed(2);
153
- return `${tooltipItem.label}: ${value} tests (${percentage}%)`;
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: 'doughnut',
165
+ type: '{{ chartType }}',
177
166
  data: {
178
167
  labels: projectNames,
179
168
  datasets: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ortoni-report",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Playwright Report By LetCode with Koushik",
5
5
  "scripts": {
6
6
  "tsc": "tsc",
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**: Offering a more structured and intuitive navigation experience.
16
- - **Sidebar Sections**:
17
- - **Dashboard**: View overall test statistics and summaries.
18
- - **Tests**: Browse detailed test results, including logs, screenshots, and errors.
19
- - **Hierarchical Grouping**: Tests are structured by filename, suite, and project, providing an organized overview.
20
- - **Test History & Detailed Breakdown**: Support for displaying up to 10 recent executions, categorized by suite and project for easy navigation.
21
- - **Integration and Configuration**: Easy integration with Playwright using TypeScript/JavaScript, with configurable preferences.
22
- - **Advanced Filtering**: Filters for project, tags, and status, with the ability to reset for a full view.
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 Details**: Status, duration, tags, errors, logs, screenshots, videos, and trace data.
27
- - **Test Attachments**: Screenshots, videos, trace viewer, steps, error stack trace, and console logs.
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
- - **Summary Statistics**: Total tests and distribution of passed, failed, skipped, and flaky tests with success rates.
33
- - **Chart Visualizations**: Plotarea chart for overall status, doghnut for projects and bar charts for project-specific comparisons.
34
- - **All-New Colorful UI**: A vibrant, redesigned interface with better contrast and readability.
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
- - **New Color Scheme**: Supports both **light and dark themes**, ensuring a comfortable viewing experience.
39
- - **Add Your Logo**: Configurable logo for brand personalization.
40
- - **Flexibility with Attachments**: Choose Base64 or file paths for screenshots.
41
- - **Custom Report Paths**: Set custom filenames and folder paths for reports.
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
- - **Advanced Search and Reset**: Search tests with keywords or criteria, with reset options.
46
- - **Hide Skipped Tests by Default**: Simplifies view by hiding skipped tests initially.
47
- - **Share Reports**: Self-contained reports for easy sharing and review.
48
- - **Comprehensive Filters**: Apply multiple filters simultaneously for focused insights.
49
- - **Meta**: Add user meta information to the reporter
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",