ortoni-report 2.0.4 → 2.0.5

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,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startReportServer = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ /**
9
+ * Starts an Express server to serve the HTML report and keeps it running.
10
+ * @param {string} reportPath - Path to the folder where the report is stored.
11
+ * @param {string} reportFilename - Name of the HTML report file to serve.
12
+ * @param {number} port - Port number to serve the report on (default is 8080).
13
+ */
14
+ function startReportServer(reportPath, reportFilename, port = 8080) {
15
+ const app = (0, express_1.default)();
16
+ // Serve static files from the report directory
17
+ app.use(express_1.default.static(reportPath));
18
+ // Start the server and keep it running
19
+ const server = app.listen(port, () => {
20
+ const reportUrl = `http://localhost:${port}/${reportFilename}`;
21
+ console.log(`Report is available at ${reportUrl}`);
22
+ });
23
+ // Ensure that the process doesn't exit prematurely
24
+ process.on('SIGINT', () => {
25
+ console.log('Shutting down the server...');
26
+ server.close(() => {
27
+ console.log('Server closed');
28
+ process.exit(0);
29
+ });
30
+ });
31
+ return server;
32
+ }
33
+ exports.startReportServer = startReportServer;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.ensureHtmlExtension = exports.safeStringify = exports.formatDate = exports.normalizeFilePath = exports.msToTime = void 0;
6
+ exports.escapeHtml = exports.ensureHtmlExtension = exports.safeStringify = exports.formatDate = exports.normalizeFilePath = exports.msToTime = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  function msToTime(duration) {
9
9
  const milliseconds = Math.floor(duration % 1000);
@@ -67,3 +67,19 @@ function ensureHtmlExtension(filename) {
67
67
  return `${filename}.html`;
68
68
  }
69
69
  exports.ensureHtmlExtension = ensureHtmlExtension;
70
+ function escapeHtml(unsafe) {
71
+ if (typeof unsafe !== 'string') {
72
+ return String(unsafe);
73
+ }
74
+ return unsafe.replace(/[&<"']/g, function (match) {
75
+ const escapeMap = {
76
+ '&': '&amp;',
77
+ '<': '&lt;',
78
+ '>': '&gt;',
79
+ '"': '&quot;',
80
+ "'": '&#039;'
81
+ };
82
+ return escapeMap[match] || match;
83
+ });
84
+ }
85
+ exports.escapeHtml = escapeHtml;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const http_1 = __importDefault(require("http"));
30
+ const ws_1 = __importStar(require("ws"));
31
+ const utils_1 = require("./utils");
32
+ class WebSocketHelper {
33
+ constructor(port) {
34
+ this.port = port;
35
+ this.wss = null;
36
+ }
37
+ setupWebSocket() {
38
+ const server = http_1.default.createServer();
39
+ this.wss = new ws_1.WebSocketServer({ server });
40
+ this.wss.on('connection', (ws) => {
41
+ ws.send((0, utils_1.safeStringify)({ type: 'initial', data: 'Connected to WS service' }));
42
+ });
43
+ server.listen(this.port, () => {
44
+ console.log(`WebSocket server is running on http://localhost:${this.port}`);
45
+ });
46
+ }
47
+ setupCleanup() {
48
+ const gracefulShutdown = () => {
49
+ console.log('Shutting down WebSocket server...');
50
+ this.closeWebSocket();
51
+ process.exit();
52
+ };
53
+ process.on('exit', gracefulShutdown);
54
+ process.on('SIGINT', () => {
55
+ console.log('Received SIGINT. Closing WebSocket server...');
56
+ gracefulShutdown();
57
+ });
58
+ }
59
+ broadcastUpdate(tests) {
60
+ if (this.wss) {
61
+ this.wss.clients.forEach((client) => {
62
+ if (client.readyState === ws_1.default.OPEN) {
63
+ client.send((0, utils_1.safeStringify)({ type: 'update', data: tests }));
64
+ }
65
+ });
66
+ }
67
+ }
68
+ closeWebSocket() {
69
+ if (this.wss) {
70
+ this.wss.clients.forEach((client) => {
71
+ if (client.readyState === ws_1.default.OPEN) {
72
+ client.close(1000, 'Test run completed');
73
+ }
74
+ });
75
+ this.wss.close(() => {
76
+ console.log('WebSocket server closed');
77
+ });
78
+ }
79
+ }
80
+ testComplete() {
81
+ if (this.wss) {
82
+ this.wss.clients.forEach((client) => {
83
+ if (client.readyState === ws_1.default.OPEN) {
84
+ client.send((0, utils_1.safeStringify)({ type: 'complete', data: 'Test run completed' }));
85
+ }
86
+ });
87
+ }
88
+ setTimeout(() => {
89
+ this.closeWebSocket();
90
+ }, 1000);
91
+ }
92
+ }
93
+ exports.default = WebSocketHelper;
@@ -1,10 +1,9 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en" data-theme="{{preferredTheme}}">
3
-
4
3
  <head>
5
4
  <meta charset="UTF-8">
6
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <meta name="description" content="Playwright HTML report by LetCode Koushik - V2.0.4">
6
+ <meta name="description" content="Playwright HTML report by LetCode Koushik - V2.0.5">
8
7
  <title>{{title}}</title>
9
8
  <link rel="icon" href="https://raw.githubusercontent.com/ortoniKC/ortoni-report/refs/heads/main/favicon.png"
10
9
  type="image/x-icon">
@@ -15,7 +14,6 @@
15
14
  <style>
16
15
  {{{inlineCss}}}
17
16
  </style>
18
-
19
17
  <body>
20
18
  {{> navbar }}
21
19
  <section class="section">
@@ -26,7 +24,6 @@
26
24
  {{> project}}
27
25
  </aside>
28
26
  <section class="column is-three-fifths">
29
- {{!-- Overall summary --}}
30
27
  <div id="summary">
31
28
  <div class="columns is-multiline has-text-centered">
32
29
  {{> summaryCard bg="primary" status="all" statusHeader="All Tests" statusCount=totalCount}}
@@ -40,13 +37,7 @@
40
37
  {{> userInfo}}
41
38
  </div>
42
39
  </div>
43
- {{!-- Test details --}}
44
- <div id="testDetails" style="display: none;">
45
- <!-- Back button should be outside the dynamic content -->
46
- <button class="button content" id="back-to-summary" onclick="showSummary()">Back to
47
- Summary</button>
48
- <!-- Test Details will be displayed here -->
49
- </div>
40
+ <div id="testDetails" style="display: none;"></div>
50
41
  </section>
51
42
  </div>
52
43
  </main>
@@ -95,11 +86,18 @@
95
86
  function showSummary() {
96
87
  summary.style.display = 'block';
97
88
  testDetails.style.display = 'none';
98
- backButton.style.display = 'none';
99
89
  }
100
90
  window.showSummary = showSummary;
101
91
 
102
92
  function displayTestDetails(test) {
93
+ const summary = document.getElementById('summary');
94
+ const testDetails = document.getElementById('testDetails');
95
+ summary.style.display = 'none';
96
+ testDetails.style.opacity = '0';
97
+ testDetails.style.display = 'block';
98
+ setTimeout(() => {
99
+ testDetails.style.opacity = '1';
100
+ }, 50);
103
101
  let currentScreenshotIndex = 0;
104
102
  function changeScreenshot(direction) {
105
103
  const screenshots = test.screenshots;
@@ -125,85 +123,83 @@
125
123
  }
126
124
  });
127
125
  }
128
- const summary = document.getElementById('summary');
129
- const testDetails = document.getElementById('testDetails');
130
- const backButton = document.querySelector('button#back-to-summary');
131
- summary.style.display = 'none';
132
- testDetails.style.opacity = '0';
133
- testDetails.style.display = 'block';
134
- setTimeout(() => {
135
- testDetails.style.opacity = '1';
136
- backButton.style.opacity = '1';
137
- }, 50);
138
-
139
126
  let statusClass = '';
127
+ let statusIcon = '';
140
128
  let statusText = test.status.toUpperCase();
129
+
141
130
  if (test.status.startsWith('passed')) {
142
- statusClass = 'tag is-success';
131
+ statusClass = 'success';
132
+ statusIcon = 'check-circle';
143
133
  } else if (test.status === 'flaky') {
144
- statusClass = 'tag is-warning';
134
+ statusClass = 'warning';
135
+ statusIcon = 'exclamation-triangle';
145
136
  } else if (test.status === 'failed') {
146
- statusClass = 'tag is-danger';
137
+ statusClass = 'danger';
138
+ statusIcon = 'times-circle';
147
139
  } else {
148
- statusClass = 'tag is-info';
140
+ statusClass = 'info';
141
+ statusIcon = 'question-circle';
149
142
  }
150
-
151
143
  testDetails.innerHTML = `
152
- <button class="button is-info is-light mb-4" id="back-to-summary" style="display: block"
153
- onclick="showSummary()">
154
- <span class="icon"><i class="fa-solid fa-chevron-left" style="color: #63E6BE;"></i></span>
155
- <span>Back to Summary</span></button>
156
- <div class="content has-text-centered">
157
- <p class="title">${test.title}</p>
158
- <p class="subtitle" id="filepath">${test.location}</p>
159
- </div>
160
- <div class="columns">
161
- <div class="column content">
162
- <h4 class="title is-4">Status</h4>
163
- <p class="${statusClass}">${statusText}</p>
164
- ${test.duration.length > 0 ? `
165
- <h4 class="title is-4">Duration</h4>
166
- <p class="${statusClass}">${test.duration}</p>` : ""}
167
- ${test.projectName.length > 0 ? `
168
- <div class="control">
169
- <div class="tags has-addons">
170
- <span class="tag is-dark">Project</span>
171
- <span class="tag is-info is-capitalized">${test.projectName}</span>
144
+ <div class="sticky-header">
145
+ <div class="card mb-3">
146
+ <button class="button is-info is-light mb-3" id="back-to-summary" onclick="showSummary()">
147
+ <span class="icon"><i class="fa fa-chevron-left" style="color: #63E6BE;"></i></span>
148
+ <span>Back to Summary</span>
149
+ </button>
150
+ <div class="card-content">
151
+ <div class="content has-text-centered">
152
+ <h1 class="title is-2">${test.title}</h1>
153
+ <p class="subtitle is-5" id="filepath">${test.location}</p>
154
+ </div>
155
+ </div>
156
+ <footer class="card-footer">
157
+ <div class="card-footer-item">
158
+ <div class="columns is-mobile">
159
+ <div class="column is-half">
160
+ <div class="is-flex is-align-items-center">
161
+ <span class="icon status-icon has-text-${statusClass}">
162
+ <i class="fa fa-${statusIcon}"></i>
163
+ </span>
164
+ <span class="has-text-weight-bold is-uppercase has-text-${statusClass}">${test.status}</span>
172
165
  </div>
173
- </div>`: ""}
174
- ${test.testTags.length > 0 ? `
175
- <div class="control mt-4">
176
- <div class="tags has-addons">
177
- <span class="tag is-dark">Test Tags</span>
178
- <span class="tag is-info">${test.testTags.join(" ")}</span>
179
166
  </div>
180
- </div>`: ""}
181
- ${test.videoPath ? `
182
- <div id="testVideo" class="modal">
183
- <div class="modal-background"></div>
184
- <div class="modal-content">
185
- <figure>
186
- <video controls>
187
- <source src="file://${test.videoPath}" type="video/webm">
188
- Your browser does not support the video tag.
189
- </video>
190
- </figure>
191
- </div>
192
- <button onclick="closeVideo()" class="modal-close is-large" aria-label="close"></button>
193
167
  </div>
194
- <button class="button mt-4" onclick="openVideo()">Attachment: Video</button>
195
- `: ''}
196
- ${test.tracePath ? `
197
- <div class="trace-viewer mt-4">
198
- <a href="${test.tracePath}" target="_blank" class="button is-small is-link">
199
- View Trace
200
- </a>
168
+ </div>
169
+ ${test.duration ? `
170
+ <div class="card-footer-item">
171
+ <div class="column is-half">
172
+ <div class="is-flex is-align-items-center">
173
+ <span class="icon status-icon has-text-info">
174
+ <i class="fa fa-clock"></i>
175
+ </span>
176
+ <span class="has-text-info has-text-weight-semibold">${test.duration}</span>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ ` : ''}
181
+ ${test.projectName ? `
182
+ <div class="card-footer-item">
183
+ <div class="is-flex is-align-items-center">
184
+ <span class="icon status-icon has-text-info">
185
+ <i class="fa fa-window-maximize" style="color: #B197FC;"></i>
186
+ </span>
187
+ <span class="" style="color: #B197FC;"> ${test.projectName}</span>
188
+ </div>
201
189
  </div>
202
190
  ` : ''}
191
+ </footer>
203
192
  </div>
204
- <div class="column content">
205
- ${test.screenshots && test.screenshots.length > 0 ? `
206
- <div id="testImage" class="modal">
193
+ </div>
194
+
195
+ <div class="content-wrapper">
196
+ ${test.status != "skipped" ?
197
+ `<div class="card mb-5">
198
+ <div class="card-content">
199
+ <div class="columns is-multiline">
200
+ ${test.screenshots && test.screenshots.length > 0 ? `
201
+ <div class="column is-half">
202
+ <div id="testImage" class="modal">
207
203
  <div class="modal-background"></div>
208
204
  <div class="modal-content">
209
205
  <p class="image">
@@ -213,10 +209,10 @@
213
209
  <button onclick="closeModal()" class="modal-close is-large" aria-label="close"></button>
214
210
  </div>
215
211
 
216
- <figure class="image box">
212
+ <figure class="image">
217
213
  <img id="screenshot-main-img" onclick="openModal()" src="${test.screenshots[0]}" alt="Screenshot">
218
214
  </figure>
219
- <nav class="pagination is-small is-centered ${test.screenshots.length > 1 ? '' : 'is-hidden'}" role="navigation" aria-label="pagination">
215
+ <nav class="mt-4 pagination is-small is-centered ${test.screenshots.length > 1 ? '' : 'is-hidden'}" role="navigation" aria-label="pagination">
220
216
  <a class="pagination-previous" >Previous</a>
221
217
  <a class="pagination-next" >Next</a>
222
218
  <ul class="pagination-list">
@@ -226,55 +222,115 @@
226
222
  </li>`).join('')}
227
223
  </ul>
228
224
  </nav>
229
- ` : ''}
225
+ </div>
226
+ ` : ''}
227
+ ${test.videoPath ? `
228
+ <div class="column is-half">
229
+ <div class="video-preview" onclick="openVideo()">
230
+ <video controls width="100%" height="auto" preload="metadata">
231
+ <source src="${test.videoPath}" type="video/webm">
232
+ Your browser does not support the video tag.
233
+ </video>
234
+ </div>
235
+ </div>
236
+ ` : ''}
237
+ </div>
238
+ ${test.tracePath ? `
239
+ <div class="columns is-centered">
240
+ <div class="column is-3">
241
+ <a href="trace/index.html?trace=http://localhost:${test.port}/${test.tracePath}" target="_blank" class="button is-link is-fullwidth mt-3">
242
+ <span class="icon"><i class="fa-solid fa-tv" style="color: #FFD43B;"></i></span>
243
+ <span class="has-text-white pl-2">View Trace</span>
244
+ </a>
245
+ </div>
246
+ </div>
247
+ ` : ''}
230
248
  </div>
231
- </div>
232
- ${test.annotations && test.annotations.length ? `
233
- <article class="message">
234
- <div class="message-body">
235
- ${test.annotations
249
+ </div>` : ''}
250
+ ${test.annotations.length || test.testTags.length > 0 ? `
251
+ <div class="card mb-5">
252
+ <header class="card-header">
253
+ <p class="card-header-title">Additional Information</p>
254
+ </header>
255
+ <div class="card-content">
256
+ <div class="content">
257
+ ${test.testTags.length > 0 ? `
258
+ <div class="control mb-4">
259
+ <div class="tags is-rounded">
260
+ ${test.testTags.map(tag => `<span class="tag is-primary is-medium">${tag}</span>`).join('')}
261
+ </div>
262
+ </div>` : ""}
263
+ ${test.annotations
236
264
  .filter(annotation => annotation !== null && annotation !== undefined)
237
265
  .map(annotation => `
238
- <div>
239
- ${annotation?.type ? `<strong class="has-text-link">Type: </strong><span>${annotation?.type}</span>` : ''}
240
- <br>
241
- ${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation?.description}</span>` : ''}
242
- </div>
266
+ <div class="mb-4">
267
+ ${annotation?.type ? `<strong class="has-text-link">Type: </strong><span>${annotation.type}</span>` : ''}
243
268
  <br>
269
+ ${annotation?.description ? `<strong class="has-text-link">Description: </strong><span>${annotation.description}</span>` : ''}
270
+ </div>
244
271
  `).join('')}
245
272
  </div>
246
- </article>
247
- ` : ``}
248
- <div class="content">
273
+ </div>
274
+ </div>
275
+ ` : ''}
249
276
  ${test.steps.length > 0 ? `
250
- <details id="stepopen" open>
251
- <summary><h4 class="title is-4">Steps</h4></summary>
252
- <span id="stepDetails" class="content"></span>
253
- </details>
254
- `: ``}
255
- </div class="content">
256
- <div>
277
+ <div class="card">
278
+ <header class="card-header">
279
+ <p class="card-header-title">Steps</p>
280
+ </header>
281
+ <div class="card-content">
282
+ <div class="content">
283
+ <span id="stepDetails" class="content"></span>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ ` : ''}
257
288
  ${test.errors.length ? `
258
- <h4 class="title is-4">Errors</h4>
259
- <div class="content">
289
+ <div class="card mt-5">
290
+ <header class="card-header">
291
+ <p class="card-header-title">Errors</p>
292
+ </header>
293
+ <div class="card-content">
294
+ <div class="content">
260
295
  <pre><code class="data-lang=js">${test.errors.join('\n')}</code></pre>
261
- </div>` : ''}
262
- </div>
263
- <div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ ` : ''}
264
300
  ${test.logs ? `
265
- <h4 class="title is-4">Logs</h4>
266
- <div class="box">
301
+ <div class="card mt-5">
302
+ <header class="card-header">
303
+ <p class="card-header-title">Logs</p>
304
+ </header>
305
+ <div class="card-content">
306
+ <div class="content">
267
307
  <pre>${test.logs}</pre>
268
- </div>` : ''}
269
- </div>
270
- `;
271
- if(test.screenshots.length > 0){
308
+ </div>
309
+ </div>
310
+ </div>
311
+ ` : ''}
312
+ ${test.videoPath ? `
313
+ <div id="testVideo" class="modal">
314
+ <div class="modal-background"></div>
315
+ <div class="modal-content">
316
+ <div class="box">
317
+ <video controls style="width: 100%;">
318
+ <source src="${test.videoPath}" type="video/webm">
319
+ Your browser does not support the video tag.
320
+ </video>
321
+ </div>
322
+ </div>
323
+ <button class="modal-close is-large" aria-label="close" onclick="closeVideo()"></button>
324
+ </div>
325
+ ` : ''}
326
+ </div>`
327
+ if (test.screenshots.length > 0) {
272
328
  document.querySelector('.pagination-previous').addEventListener('click', () => changeScreenshot(-1));
273
329
  document.querySelector('.pagination-next').addEventListener('click', () => changeScreenshot(1));
274
330
  document.querySelectorAll('.pagination-link').forEach((link, index) => {
275
331
  link.addEventListener('click', () => gotoScreenshot(index));
276
332
  });
277
- }
333
+ }
278
334
  const stepDetailsDiv = document.getElementById('stepDetails');
279
335
  if (stepDetailsDiv) {
280
336
  const stepsList = attachSteps(test);
@@ -282,7 +338,6 @@
282
338
  stepDetailsDiv.appendChild(stepsList);
283
339
  }
284
340
  }
285
-
286
341
  function attachSteps(test) {
287
342
  const stepsList = document.createElement("ul");
288
343
  stepsList.setAttribute("id", "steps");
@@ -29,7 +29,6 @@
29
29
  </label>
30
30
  </div>
31
31
  {{/each}}
32
- <hr class="dropdown-divider" />
33
32
  </div>
34
33
  </div>
35
34
  </div>
@@ -30,11 +30,11 @@
30
30
  </p>
31
31
  </div>
32
32
  </div>
33
+ {{#if (or authorName testType)}}
33
34
  <div class="column is-half">
34
35
  <div class="content">
35
- <div class="content">
36
- {{#if authorName}}
37
- <div class="field"></div>
36
+ {{#if authorName}}
37
+ <div class="field">
38
38
  <label class="label" for="Tester">Tester</label>
39
39
  <p class="is-flex is-align-items-center">
40
40
  <span class="icon has-text-info mr-2">
@@ -44,9 +44,10 @@
44
44
  </p>
45
45
  </div>
46
46
  {{/if}}
47
+
47
48
  {{#if testType}}
48
49
  <div class="field">
49
- <label class="label" for="test Type">Test Type</label>
50
+ <label class="label" for="testType">Test Type</label>
50
51
  <p class="is-flex is-align-items-center">
51
52
  <span class="icon has-text-primary mr-2">
52
53
  <i class="fas fa-code-branch"></i>
@@ -57,6 +58,7 @@
57
58
  {{/if}}
58
59
  </div>
59
60
  </div>
61
+ {{/if}}
60
62
  </div>
61
63
  <div class="columns">
62
64
  <div class="column is-half">
@@ -91,9 +93,8 @@
91
93
  labels: ['Passed', 'Failed', 'Skipped', 'Flaky'],
92
94
  datasets: [{
93
95
  data: [{{ passCount }}, {{ failCount }}, {{ skipCount }}, {{ flakyCount }}],
94
- backgroundColor: ['#28a745', '#ff6685', '#66d1ff', '#ffb70f']
95
- }]
96
- },
96
+ backgroundColor: ['#28a745', '#ff6685', '#66d1ff', '#ffb70f']}]
97
+ },
97
98
  options: {
98
99
  responsive: true,
99
100
  maintainAspectRatio: false,
@@ -109,7 +110,7 @@
109
110
  },
110
111
  tooltip: {
111
112
  callbacks: {
112
- label: function(tooltipItem) {
113
+ label: function (tooltipItem) {
113
114
  const total = tooltipItem.dataset.data.reduce((a, b) => a + b, 0);
114
115
  const value = tooltipItem.raw;
115
116
  const percentage = ((value / total) * 100).toFixed(2);
@@ -187,7 +188,7 @@
187
188
  {
188
189
  label: 'Flaky',
189
190
  data: retryTests,
190
- backgroundColor: '#ffb70f',
191
+ backgroundColor: '#ffb70f',
191
192
  }
192
193
  ]
193
194
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ortoni-report",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "Playwright Report By LetCode with Koushik",
5
5
  "scripts": {
6
6
  "tsc": "tsc",
@@ -32,9 +32,11 @@
32
32
  "homepage": "https://github.com/ortoniKC/ortoni-report#readme",
33
33
  "devDependencies": {
34
34
  "@playwright/test": "^1.44.1",
35
+ "@types/express": "^5.0.0",
35
36
  "@types/node": "^22.0.2",
36
37
  "@types/ws": "^8.5.12",
37
38
  "ansi-to-html": "^0.7.2",
39
+ "express": "^4.21.1",
38
40
  "handlebars": "^4.7.8",
39
41
  "tsup": "^6.5.0",
40
42
  "typescript": "^4.9.4",