ortoni-report 3.0.0 → 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.
@@ -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.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" />
@@ -11,7 +11,11 @@
11
11
  {{> sidebar}}
12
12
  <main class="main-content">
13
13
  <div id="dashboard-section" class="content-section">
14
+ {{#if logo}}
15
+ <img src="{{logo}}" alt="{{projectName}}" class="logoimage" />
16
+ {{else}}
14
17
  <h1 class="title is-3">Dashboard</h1>
18
+ {{/if}}
15
19
  <div class="columns is-multiline has-text-centered">
16
20
  {{> summaryCard bg="hsl(var(--bulma-primary-h), var(--bulma-primary-s), var(--bulma-primary-l)) !important" status="all" statusHeader="All Tests" statusCount=totalCount}}
17
21
  {{> summaryCard bg="hsl(var(--bulma-success-h), var(--bulma-success-s), var(--bulma-success-l)) !important" status="passed" statusHeader="Passed" statusCount=passCount}}
@@ -44,6 +48,9 @@
44
48
  </div>
45
49
  </div>
46
50
  </div>
51
+ <div id="analytics-section" class="content-section" style="display: none;">
52
+ {{> analytics}}
53
+ </div>
47
54
  </main>
48
55
  </div>
49
56
  <script>
@@ -131,6 +138,7 @@
131
138
  this.sidebarLinks = document.querySelectorAll('.sidebar-menu-link');
132
139
  this.sections = document.querySelectorAll('.content-section');
133
140
  this.testDetailsSection = document.getElementById('testDetails');
141
+ this.analyticsSection = document.getElementById('analytics-section');
134
142
  this.sidebar = document.getElementById('sidebar');
135
143
  this.mainContent = document.querySelector('.main-content');
136
144
  this.init();
@@ -174,6 +182,7 @@
174
182
  showSection(sectionId) {
175
183
  // Hide test details if showing a main section
176
184
  this.testDetailsSection.style.display = 'none';
185
+ this.analyticsSection.style.display = 'none';
177
186
 
178
187
  // Show the selected section
179
188
  this.sections.forEach(section => {
@@ -916,19 +925,20 @@
916
925
  }
917
926
 
918
927
  /**
919
- * ====================================
920
- * HISTORY MANAGER
921
- * ====================================
922
- */
928
+ * ====================================
929
+ * HISTORY MANAGER
930
+ * ====================================
931
+ */
923
932
  class HistoryManager {
924
933
  /**
925
934
  * Initialize the history manager
926
935
  * @param {Array} testHistory - Test history data
927
936
  */
928
937
  constructor(testHistory) {
929
- this.testHistory = testHistory;
930
- this.testHistoriesMap = null;
931
- this.testHistoryTitle = '';
938
+ this.testHistory = testHistory
939
+ this.testHistoriesMap = null
940
+ this.testHistoryTitle = ""
941
+ this.activeErrorModal = null
932
942
  }
933
943
 
934
944
  /**
@@ -937,68 +947,188 @@
937
947
  * @param {string} title - Test title
938
948
  */
939
949
  setCurrentHistory(historyMap, title) {
940
- this.testHistoriesMap = historyMap;
941
- this.testHistoryTitle = title;
950
+ this.testHistoriesMap = historyMap
951
+ this.testHistoryTitle = title
942
952
  }
943
953
 
944
954
  /**
945
955
  * Open history modal
946
956
  */
947
957
  openHistory() {
948
- const historyElement = document.getElementById("historyModal");
949
- historyElement.classList.add('is-active');
958
+ const historyElement = document.getElementById("historyModal")
959
+ historyElement.classList.add("is-active")
950
960
 
951
- let historyContent = '';
961
+ let historyContent = ""
952
962
  if (this.testHistoriesMap && this.testHistoriesMap.length > 0) {
953
- historyContent = this.testHistoriesMap.map((h, index) => `
954
- <tr>
955
- <td>${h.run_date}</td>
956
- <td>${h.status}</td>
957
- <td>${h.duration}</td>
958
- ${h.error_message ? `<td><div class="modal" id="${index}">
963
+ historyContent = this.testHistoriesMap
964
+ .map(
965
+ (h, index) => `
966
+ <tr>
967
+ <td>${h.run_date}</td>
968
+ <td>
969
+ <span class="tag is-${this.getStatusClass(h.status)}">
970
+ ${h.status}
971
+ </span>
972
+ </td>
973
+ <td>${h.duration}</td>
974
+ <td>
975
+ ${h.error_message
976
+ ? `<div class="modal" id="error-${index}">
959
977
  <div class="modal-background"></div>
960
- <div class="modal-content">
961
- <pre><code>${h.error_message}</code></pre>
978
+ <div class="modal-card">
979
+ <header class="modal-card-head">
980
+ <p class="modal-card-title">
981
+ <span class="icon-text">
982
+ <span class="icon">
983
+ <i class="fa-solid fa-exclamation-triangle"></i>
984
+ </span>
985
+ <span>Error Details</span>
986
+ </span>
987
+ </p>
988
+ <button class="delete" aria-label="close" onclick="closeErrorModal('error-${index}')"></button>
989
+ </header>
990
+ <section class="modal-card-body">
991
+ <div class="notification is-danger is-light">
992
+ <pre><code>${h.error_message}</code></pre>
993
+ </div>
994
+ </section>
995
+ <footer class="modal-card-foot">
996
+ <button class="button is-primary" onclick="closeErrorModal('error-${index}')">Close</button>
997
+ </footer>
962
998
  </div>
963
- <button class="button is-primary" onclick="closeErrorModal(${index})">Close</button>
964
- </div><a class="button is-link" onclick="showHistoryErrorMessage(${index})">Show error</a></td>` : '<td>No Error</td>'}
965
- </tr>
966
- `).join('');
999
+ </div>
1000
+ <button class="button is-small is-link" onclick="showHistoryErrorMessage('error-${index}')">
1001
+ <span class="icon">
1002
+ <i class="fa-solid fa-exclamation-triangle"></i>
1003
+ </span>
1004
+ <span>View Error</span>
1005
+ </button>`
1006
+ : '<span class="tag is-success is-light">No Error</span>'
1007
+ }
1008
+ </td>
1009
+ </tr>
1010
+ `,
1011
+ )
1012
+ .join("")
967
1013
  } else {
968
- historyContent = '<p class="title">No history available</p>';
1014
+ historyContent = `
1015
+ <tr>
1016
+ <td colspan="4">
1017
+ <div class="notification is-info is-light">
1018
+ <p class="has-text-centered">
1019
+ <span class="icon">
1020
+ <i class="fa-solid fa-info-circle"></i>
1021
+ </span>
1022
+ <span>No history available for this test.</span>
1023
+ </p>
1024
+ </div>
1025
+ </td>
1026
+ </tr>
1027
+ `
969
1028
  }
970
1029
 
971
1030
  historyElement.innerHTML = `
972
- <div class="modal-background"></div>
973
- <div class="modal-card">
974
- <header class="modal-card-head">
975
- <p class="modal-card-title">${this.testHistoryTitle}</p>
976
- <button class="button is-primary" onclick="closeHistoryModal()">Close</button>
977
- </header>
978
- <section class="modal-card-body">
979
- <table class="table is-hoverable is-fullwidth">
980
- <thead>
981
- <tr>
982
- <th title="Run Date">Run Date</th>
983
- <th title="Status">Status</th>
984
- <th title="Duration">Duration</th>
985
- <th title="Reason">Reason</th>
986
- </tr>
987
- </thead>
988
- <tbody>
989
- ${historyContent}
990
- </tbody>
991
- </table>
992
- </section>
993
- </div>
994
- `;
1031
+ <div class="modal-background"></div>
1032
+ <div class="modal-card">
1033
+ <header class="modal-card-head">
1034
+ <p class="modal-card-title">
1035
+ <span class="icon-text">
1036
+ <span class="icon">
1037
+ <i class="fa-solid fa-history"></i>
1038
+ </span>
1039
+ <span>Test History: ${this.testHistoryTitle}</span>
1040
+ </span>
1041
+ </p>
1042
+ <button class="delete" aria-label="close" onclick="closeHistoryModal()"></button>
1043
+ </header>
1044
+ <section class="modal-card-body">
1045
+ <div class="table-container">
1046
+ <table class="table is-hoverable is-fullwidth">
1047
+ <thead>
1048
+ <tr>
1049
+ <th title="Run Date">
1050
+ <span class="icon-text">
1051
+ <span class="icon">
1052
+ <i class="fa-solid fa-calendar"></i>
1053
+ </span>
1054
+ <span>Run Date</span>
1055
+ </span>
1056
+ </th>
1057
+ <th title="Status">
1058
+ <span class="icon-text">
1059
+ <span class="icon">
1060
+ <i class="fa-solid fa-check-circle"></i>
1061
+ </span>
1062
+ <span>Status</span>
1063
+ </span>
1064
+ </th>
1065
+ <th title="Duration">
1066
+ <span class="icon-text">
1067
+ <span class="icon">
1068
+ <i class="fa-solid fa-clock"></i>
1069
+ </span>
1070
+ <span>Duration</span>
1071
+ </span>
1072
+ </th>
1073
+ <th title="Details">
1074
+ <span class="icon-text">
1075
+ <span class="icon">
1076
+ <i class="fa-solid fa-info-circle"></i>
1077
+ </span>
1078
+ <span>Details</span>
1079
+ </span>
1080
+ </th>
1081
+ </tr>
1082
+ </thead>
1083
+ <tbody>
1084
+ ${historyContent}
1085
+ </tbody>
1086
+ </table>
1087
+ </div>
1088
+ </section>
1089
+ <footer class="modal-card-foot">
1090
+ <button class="button is-primary" onclick="closeHistoryModal()">Close</button>
1091
+ </footer>
1092
+ </div>
1093
+ `
1094
+
1095
+ // Add keyboard event listener for ESC key
1096
+ document.addEventListener("keydown", this.handleEscKeyPress)
1097
+ }
1098
+
1099
+ /**
1100
+ * Get status class based on test status
1101
+ * @param {string} status - Test status
1102
+ * @returns {string} CSS class
1103
+ */
1104
+ getStatusClass(status) {
1105
+ if (status && status.startsWith("passed")) return "success"
1106
+ if (status === "flaky") return "warning"
1107
+ if (status === "failed") return "danger"
1108
+ if (status === "skipped") return "info"
1109
+ if (status && status.includes("retry")) return "link"
1110
+ return "dark"
1111
+ }
1112
+
1113
+ /**
1114
+ * Handle ESC key press to close modal
1115
+ * @param {KeyboardEvent} event - Keyboard event
1116
+ */
1117
+ handleEscKeyPress = (event) => {
1118
+ if (event.key === "Escape") {
1119
+ this.closeHistoryModal()
1120
+ }
995
1121
  }
996
1122
 
997
1123
  /**
998
1124
  * Close history modal
999
1125
  */
1000
1126
  closeHistoryModal() {
1001
- document.getElementById("historyModal").classList.remove('is-active');
1127
+ const historyElement = document.getElementById("historyModal")
1128
+ historyElement.classList.remove("is-active")
1129
+
1130
+ // Remove keyboard event listener
1131
+ document.removeEventListener("keydown", this.handleEscKeyPress)
1002
1132
  }
1003
1133
 
1004
1134
  /**
@@ -1006,10 +1136,17 @@
1006
1136
  * @param {string} modalId - Modal ID
1007
1137
  */
1008
1138
  showHistoryErrorMessage(modalId) {
1009
- document.getElementById(modalId)?.classList.add('is-active');
1139
+ document.getElementById(modalId)?.classList.add("is-active")
1010
1140
  }
1011
- }
1012
1141
 
1142
+ /**
1143
+ * Close error modal
1144
+ * @param {string} modalId - Modal ID
1145
+ */
1146
+ closeErrorModal(modalId) {
1147
+ document.getElementById(modalId)?.classList.remove("is-active")
1148
+ }
1149
+ }
1013
1150
  /**
1014
1151
  * ====================================
1015
1152
  * MOBILE RESPONSIVE MANAGER
@@ -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">
@@ -1,7 +1,7 @@
1
1
  <div class="icon-text">
2
2
  {{#if isRetry}}
3
3
  <span class="icon has-text-info">
4
- <i class="fa-solid fa-rotate-right" style="color: #fff;"></i>
4
+ <i class="fa-solid fa-rotate-right"></i>
5
5
  </span>
6
6
  {{/if}}
7
7
  <span>{{title}}</span>
@@ -113,39 +113,40 @@
113
113
  <script>
114
114
  const overallChart = document.getElementById('testChart');
115
115
  new Chart(overallChart, {
116
- type: "doughnut",
116
+ type: '{{ chartType }}',
117
117
  data: {
118
- labels: ['Passed', 'Failed', 'Skipped', 'Flaky'],
118
+ labels: ['Passed', 'Failed', 'Skipped', 'Flaky', 'Retry'],
119
119
  datasets: [{
120
- data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}, {{ flakyCount }}],
121
- backgroundColor: ['#28a745', '#ff6685', '#66d1ff', '#ffb70f']}]
122
- },
120
+ data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}, {{ flakyCount }}, {{ retryCount }}],
121
+ backgroundColor: ['#28a745', '#ff6685', '#66d1ff', '#ffb70f', '#69748c']}]
122
+ },
123
123
  options: {
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;
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
+ }
133
134
  }
134
- }
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}%)`;
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
+ }
143
144
  }
144
145
  }
145
146
  }
146
147
  }
147
- }
148
- });
148
+ });
149
+
149
150
  const projectChart = document.getElementById('projectChart');
150
151
  const projectbarChart = document.getElementById('projectbarChart');
151
152
 
@@ -161,7 +162,7 @@
161
162
  }
162
163
  const backgroundColors = totalTests.map(() => generateRandomColor());
163
164
  new Chart(projectChart, {
164
- type: 'doughnut',
165
+ type: '{{ chartType }}',
165
166
  data: {
166
167
  labels: projectNames,
167
168
  datasets: [{
@@ -190,6 +191,8 @@
190
191
  const failedTests = {{ json failedTests }};
191
192
  const skippedTests = {{ json skippedTests }};
192
193
  const retryTests = {{ json retryTests }};
194
+ const flakyTests = {{ json flakyTests }};
195
+
193
196
  new Chart(projectbarChart, {
194
197
  type: 'bar',
195
198
  data: {
@@ -216,8 +219,13 @@
216
219
  },
217
220
  {
218
221
  label: 'Flaky',
219
- data: retryTests,
222
+ data: flakyTests,
220
223
  backgroundColor: '#ffb70f',
224
+ },
225
+ {
226
+ label: 'Retry',
227
+ data: retryTests,
228
+ backgroundColor: '#69748c',
221
229
  }
222
230
  ]
223
231
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ortoni-report",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Playwright Report By LetCode with Koushik",
5
5
  "scripts": {
6
6
  "tsc": "tsc",
@@ -20,12 +20,13 @@
20
20
  "url": "git+https://github.com/ortoniKC/ortoni-report"
21
21
  },
22
22
  "keywords": [
23
- "playwright report",
24
- "playwright letcode",
25
- "letcode koushik",
26
- "ortoni report",
23
+ "playwright",
24
+ "letcode",
25
+ "koushik",
26
+ "ortoni",
27
27
  "ortoni-report",
28
- "playwright html"
28
+ "report",
29
+ "reporter"
29
30
  ],
30
31
  "author": "Koushik Chatterjee (LetCode with Koushik)",
31
32
  "license": "GPL-3.0-only",
@@ -38,14 +39,14 @@
38
39
  "@types/express": "^5.0.0",
39
40
  "@types/node": "^22.0.2",
40
41
  "@types/sqlite3": "^3.1.11",
41
- "ansi-to-html": "^0.7.2",
42
- "commander": "^12.1.0",
43
- "express": "^4.21.1",
44
- "handlebars": "^4.7.8",
45
42
  "tsup": "^8.4.0",
46
43
  "typescript": "^4.9.4"
47
44
  },
48
45
  "peerDependencies": {
46
+ "ansi-to-html": "^0.7.2",
47
+ "commander": "^12.1.0",
48
+ "express": "^4.21.1",
49
+ "handlebars": "^4.7.8",
49
50
  "sqlite": "^5.1.1",
50
51
  "sqlite3": "^5.1.7"
51
52
  },