@arghajit/dummy 0.1.0 → 0.1.2-beta-1

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,714 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as fs from "fs/promises";
4
+ import path from "path";
5
+
6
+ // Use dynamic import for chalk as it's ESM only
7
+ let chalk;
8
+ try {
9
+ chalk = (await import("chalk")).default;
10
+ } catch (e) {
11
+ console.warn("Chalk could not be imported. Using plain console logs.");
12
+ chalk = {
13
+ green: (text) => text,
14
+ red: (text) => text,
15
+ yellow: (text) => text,
16
+ blue: (text) => text,
17
+ bold: (text) => text,
18
+ gray: (text) => text,
19
+ };
20
+ }
21
+
22
+ const DEFAULT_OUTPUT_DIR = "pulse-report";
23
+ const DEFAULT_JSON_FILE = "playwright-pulse-report.json";
24
+ const MINIFIED_HTML_FILE = "pulse-email-summary.html"; // New minified report
25
+
26
+ function sanitizeHTML(str) {
27
+ if (str === null || str === undefined) return "";
28
+ return String(str).replace(/[&<>"']/g, (match) => {
29
+ const replacements = {
30
+ "&": "&", // Changed to & for HTML context
31
+ "<": "<",
32
+ ">": ">",
33
+ '"': '"',
34
+ "'": "'",
35
+ };
36
+ return replacements[match] || match;
37
+ });
38
+ }
39
+ function capitalize(str) {
40
+ if (!str) return "";
41
+ return str[0].toUpperCase() + str.slice(1).toLowerCase();
42
+ }
43
+ function formatDuration(ms, options = {}) {
44
+ const {
45
+ precision = 1,
46
+ invalidInputReturn = "N/A",
47
+ defaultForNullUndefinedNegative = null,
48
+ } = options;
49
+
50
+ const validPrecision = Math.max(0, Math.floor(precision));
51
+ const zeroWithPrecision = (0).toFixed(validPrecision) + "s";
52
+ const resolvedNullUndefNegReturn =
53
+ defaultForNullUndefinedNegative === null
54
+ ? zeroWithPrecision
55
+ : defaultForNullUndefinedNegative;
56
+
57
+ if (ms === undefined || ms === null) {
58
+ return resolvedNullUndefNegReturn;
59
+ }
60
+
61
+ const numMs = Number(ms);
62
+
63
+ if (Number.isNaN(numMs) || !Number.isFinite(numMs)) {
64
+ return invalidInputReturn;
65
+ }
66
+
67
+ if (numMs < 0) {
68
+ return resolvedNullUndefNegReturn;
69
+ }
70
+
71
+ if (numMs === 0) {
72
+ return zeroWithPrecision;
73
+ }
74
+
75
+ const MS_PER_SECOND = 1000;
76
+ const SECONDS_PER_MINUTE = 60;
77
+ const MINUTES_PER_HOUR = 60;
78
+ const SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
79
+
80
+ const totalRawSeconds = numMs / MS_PER_SECOND;
81
+
82
+ // Decision: Are we going to display hours or minutes?
83
+ // This happens if the duration is inherently >= 1 minute OR
84
+ // if it's < 1 minute but ceiling the seconds makes it >= 1 minute.
85
+ if (
86
+ totalRawSeconds < SECONDS_PER_MINUTE &&
87
+ Math.ceil(totalRawSeconds) < SECONDS_PER_MINUTE
88
+ ) {
89
+ // Strictly seconds-only display, use precision.
90
+ return `${totalRawSeconds.toFixed(validPrecision)}s`;
91
+ } else {
92
+ // Display will include minutes and/or hours, or seconds round up to a minute.
93
+ // Seconds part should be an integer (ceiling).
94
+ // Round the total milliseconds UP to the nearest full second.
95
+ const totalMsRoundedUpToSecond =
96
+ Math.ceil(numMs / MS_PER_SECOND) * MS_PER_SECOND;
97
+
98
+ let remainingMs = totalMsRoundedUpToSecond;
99
+
100
+ const h = Math.floor(remainingMs / (MS_PER_SECOND * SECONDS_PER_HOUR));
101
+ remainingMs %= MS_PER_SECOND * SECONDS_PER_HOUR;
102
+
103
+ const m = Math.floor(remainingMs / (MS_PER_SECOND * SECONDS_PER_MINUTE));
104
+ remainingMs %= MS_PER_SECOND * SECONDS_PER_MINUTE;
105
+
106
+ const s = Math.floor(remainingMs / MS_PER_SECOND); // This will be an integer
107
+
108
+ const parts = [];
109
+ if (h > 0) {
110
+ parts.push(`${h}h`);
111
+ }
112
+
113
+ // Show minutes if:
114
+ // - hours are present (e.g., "1h 0m 5s")
115
+ // - OR minutes themselves are > 0 (e.g., "5m 10s")
116
+ // - OR the original duration was >= 1 minute (ensures "1m 0s" for 60000ms)
117
+ if (h > 0 || m > 0 || numMs >= MS_PER_SECOND * SECONDS_PER_MINUTE) {
118
+ parts.push(`${m}m`);
119
+ }
120
+
121
+ parts.push(`${s}s`);
122
+
123
+ return parts.join(" ");
124
+ }
125
+ }
126
+ function formatDate(dateStrOrDate) {
127
+ if (!dateStrOrDate) return "N/A";
128
+ try {
129
+ const date = new Date(dateStrOrDate);
130
+ if (isNaN(date.getTime())) return "Invalid Date";
131
+ return (
132
+ date.toLocaleDateString(undefined, {
133
+ year: "2-digit",
134
+ month: "2-digit",
135
+ day: "2-digit",
136
+ }) +
137
+ " " +
138
+ date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })
139
+ );
140
+ } catch (e) {
141
+ return "Invalid Date Format";
142
+ }
143
+ }
144
+ function getStatusClass(status) {
145
+ switch (String(status).toLowerCase()) {
146
+ case "passed":
147
+ return "status-passed";
148
+ case "failed":
149
+ return "status-failed";
150
+ case "skipped":
151
+ return "status-skipped";
152
+ default:
153
+ return "status-unknown";
154
+ }
155
+ }
156
+ function getStatusIcon(status) {
157
+ switch (String(status).toLowerCase()) {
158
+ case "passed":
159
+ return "✅";
160
+ case "failed":
161
+ return "❌";
162
+ case "skipped":
163
+ return "⏭️";
164
+ default:
165
+ return "❓";
166
+ }
167
+ }
168
+ function generateMinifiedHTML(reportData) {
169
+ const { run, results } = reportData;
170
+ const runSummary = run || {
171
+ totalTests: 0,
172
+ passed: 0,
173
+ failed: 0,
174
+ skipped: 0,
175
+ duration: 0,
176
+ timestamp: new Date().toISOString(),
177
+ };
178
+
179
+ const testsByBrowser = new Map();
180
+ const allBrowsers = new Set();
181
+ if (results && results.length > 0) {
182
+ results.forEach((test) => {
183
+ const browser = test.browser || "unknown";
184
+ allBrowsers.add(browser);
185
+ if (!testsByBrowser.has(browser)) {
186
+ testsByBrowser.set(browser, []);
187
+ }
188
+ testsByBrowser.get(browser).push(test);
189
+ });
190
+ }
191
+
192
+ function generateTestListHTML() {
193
+ if (testsByBrowser.size === 0) {
194
+ return '<p class="no-tests">No test results found in this run.</p>';
195
+ }
196
+
197
+ let html = "";
198
+ testsByBrowser.forEach((tests, browser) => {
199
+ html += `
200
+ <div class="browser-section" data-browser-group="${sanitizeHTML(
201
+ browser.toLowerCase()
202
+ )}">
203
+ <h2 class="browser-title">${sanitizeHTML(capitalize(browser))}</h2>
204
+ <ul class="test-list">
205
+ `;
206
+ tests.forEach((test) => {
207
+ const testFileParts = test.name.split(" > ");
208
+ const testTitle =
209
+ testFileParts[testFileParts.length - 1] || "Unnamed Test";
210
+ html += `
211
+ <li class="test-item ${getStatusClass(test.status)}"
212
+ data-test-name-min="${sanitizeHTML(testTitle.toLowerCase())}"
213
+ data-status-min="${sanitizeHTML(
214
+ String(test.status).toLowerCase()
215
+ )}"
216
+ data-browser-min="${sanitizeHTML(browser.toLowerCase())}">
217
+ <span class="test-status-icon">${getStatusIcon(
218
+ test.status
219
+ )}</span>
220
+ <span class="test-title-text" title="${sanitizeHTML(
221
+ test.name
222
+ )}">${sanitizeHTML(testTitle)}</span>
223
+ <span class="test-status-label">${String(
224
+ test.status
225
+ ).toUpperCase()}</span>
226
+ </li>
227
+ `;
228
+ });
229
+ html += `
230
+ </ul>
231
+ </div>
232
+ `;
233
+ });
234
+ return html;
235
+ }
236
+
237
+ return `
238
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
239
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
240
+ <head>
241
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
242
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
243
+ <link rel="icon" type="image/png" href="https://i.postimg.cc/v817w4sg/logo.png">
244
+ <link rel="apple-touch-icon" href="https://i.postimg.cc/v817w4sg/logo.png">
245
+ <title>Playwright Pulse Summary Report</title>
246
+ <style>
247
+ :root {
248
+ --primary-color: #2c3e50; /* Dark Blue/Grey */
249
+ --secondary-color: #3498db; /* Bright Blue */
250
+ --success-color: #2ecc71; /* Green */
251
+ --danger-color: #e74c3c; /* Red */
252
+ --warning-color: #f39c12; /* Orange */
253
+ --light-gray-color: #ecf0f1; /* Light Grey */
254
+ --medium-gray-color: #bdc3c7; /* Medium Grey */
255
+ --dark-gray-color: #7f8c8d; /* Dark Grey */
256
+ --text-color: #34495e; /* Dark Grey/Blue for text */
257
+ --background-color: #f8f9fa;
258
+ --card-background-color: #ffffff;
259
+ --border-color: #dfe6e9;
260
+ --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
261
+ --border-radius: 6px;
262
+ --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
263
+ }
264
+ body {
265
+ font-family: var(--font-family);
266
+ margin: 0;
267
+ background-color: var(--background-color);
268
+ color: var(--text-color);
269
+ line-height: 1.6;
270
+ font-size: 16px;
271
+ padding: 20px;
272
+ }
273
+ .container {
274
+ max-width: 900px;
275
+ margin: 0 auto;
276
+ background-color: var(--card-background-color);
277
+ padding: 25px;
278
+ border-radius: var(--border-radius);
279
+ box-shadow: var(--box-shadow);
280
+ }
281
+ .report-header {
282
+ display: flex;
283
+ justify-content: space-between;
284
+ align-items: center;
285
+ padding-bottom: 20px;
286
+ border-bottom: 1px solid var(--border-color);
287
+ margin-bottom: 25px;
288
+ flex-wrap: wrap;
289
+ }
290
+ .report-header-title {
291
+ display: flex;
292
+ align-items: center;
293
+ gap: 12px;
294
+ }
295
+ .report-header h1 {
296
+ margin: 0;
297
+ font-size: 1.75em;
298
+ font-weight: 600;
299
+ color: var(--primary-color);
300
+ }
301
+ #report-logo {
302
+ height: 40px;
303
+ width: 55px;
304
+ }
305
+ .run-info {
306
+ font-size: 0.9em;
307
+ text-align: right;
308
+ color: var(--dark-gray-color);
309
+ }
310
+ .run-info strong {
311
+ color: var(--text-color);
312
+ }
313
+ .summary-stats {
314
+ display: grid;
315
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
316
+ gap: 20px;
317
+ margin-bottom: 30px;
318
+ }
319
+ .stat-card {
320
+ background-color: var(--card-background-color);
321
+ border: 1px solid var(--border-color);
322
+ border-left-width: 5px;
323
+ border-left-color: var(--primary-color);
324
+ border-radius: var(--border-radius);
325
+ padding: 18px;
326
+ text-align: center;
327
+ }
328
+ .stat-card h3 {
329
+ margin: 0 0 8px;
330
+ font-size: 1em;
331
+ font-weight: 500;
332
+ color: var(--dark-gray-color);
333
+ text-transform: uppercase;
334
+ }
335
+ .stat-card .value {
336
+ font-size: 2em;
337
+ font-weight: 700;
338
+ color: var(--primary-color);
339
+ }
340
+ .stat-card.passed { border-left-color: var(--success-color); }
341
+ .stat-card.passed .value { color: var(--success-color); }
342
+ .stat-card.failed { border-left-color: var(--danger-color); }
343
+ .stat-card.failed .value { color: var(--danger-color); }
344
+ .stat-card.skipped { border-left-color: var(--warning-color); }
345
+ .stat-card.skipped .value { color: var(--warning-color); }
346
+
347
+ .section-title {
348
+ font-size: 1.5em;
349
+ color: var(--primary-color);
350
+ margin-top: 30px;
351
+ margin-bottom: 15px;
352
+ padding-bottom: 10px;
353
+ border-bottom: 2px solid var(--secondary-color);
354
+ }
355
+
356
+ /* Filters Section */
357
+ .filters-section {
358
+ display: flex;
359
+ flex-wrap: wrap;
360
+ gap: 15px;
361
+ margin-bottom: 20px;
362
+ padding: 15px;
363
+ background-color: var(--light-gray-color);
364
+ border-radius: var(--border-radius);
365
+ border: 1px solid var(--border-color);
366
+ }
367
+ .filters-section input[type="text"],
368
+ .filters-section select {
369
+ padding: 8px 12px;
370
+ border: 1px solid var(--medium-gray-color);
371
+ border-radius: 4px;
372
+ font-size: 0.95em;
373
+ flex-grow: 1;
374
+ }
375
+ .filters-section select {
376
+ min-width: 150px;
377
+ }
378
+ .filters-section button {
379
+ padding: 8px 15px;
380
+ font-size: 0.95em;
381
+ background-color: var(--secondary-color);
382
+ color: white;
383
+ border: none;
384
+ border-radius: 4px;
385
+ cursor: pointer;
386
+ transition: background-color 0.2s ease;
387
+ }
388
+ .filters-section button:hover {
389
+ background-color: var(--primary-color);
390
+ }
391
+
392
+ .browser-section {
393
+ margin-bottom: 25px;
394
+ }
395
+ .browser-title {
396
+ font-size: 1.25em;
397
+ color: var(--text-color);
398
+ margin-bottom: 10px;
399
+ padding: 8px 0;
400
+ border-bottom: 1px dashed var(--medium-gray-color);
401
+ }
402
+ .test-list {
403
+ list-style-type: none;
404
+ padding-left: 0;
405
+ }
406
+ .test-item {
407
+ display: flex;
408
+ align-items: center;
409
+ padding: 10px 12px;
410
+ margin-bottom: 8px;
411
+ border: 1px solid var(--border-color);
412
+ border-radius: var(--border-radius);
413
+ background-color: #fff;
414
+ transition: background-color 0.2s ease, display 0.3s ease-out;
415
+ }
416
+ .test-item:hover {
417
+ background-color: var(--light-gray-color);
418
+ }
419
+ .test-status-icon {
420
+ font-size: 1.1em;
421
+ margin-right: 10px;
422
+ }
423
+ .test-title-text {
424
+ flex-grow: 1;
425
+ font-size: 0.95em;
426
+ }
427
+ .test-status-label {
428
+ font-size: 0.8em;
429
+ font-weight: 600;
430
+ padding: 3px 8px;
431
+ border-radius: 4px;
432
+ color: #fff;
433
+ margin-left: 10px;
434
+ min-width: 60px;
435
+ text-align: center;
436
+ }
437
+ .test-item.status-passed .test-status-label { background-color: var(--success-color); }
438
+ .test-item.status-failed .test-status-label { background-color: var(--danger-color); }
439
+ .test-item.status-skipped .test-status-label { background-color: var(--warning-color); }
440
+ .test-item.status-unknown .test-status-label { background-color: var(--dark-gray-color); }
441
+
442
+ .no-tests {
443
+ padding: 20px;
444
+ text-align: center;
445
+ color: var(--dark-gray-color);
446
+ background-color: var(--light-gray-color);
447
+ border-radius: var(--border-radius);
448
+ font-style: italic;
449
+ }
450
+ .report-footer {
451
+ padding: 15px 0;
452
+ margin-top: 30px;
453
+ border-top: 1px solid var(--border-color);
454
+ text-align: center;
455
+ font-size: 0.85em;
456
+ color: var(--dark-gray-color);
457
+ }
458
+ .report-footer a {
459
+ color: var(--secondary-color);
460
+ text-decoration: none;
461
+ font-weight: 600;
462
+ }
463
+ .report-footer a:hover {
464
+ text-decoration: underline;
465
+ }
466
+
467
+ @media (max-width: 768px) {
468
+ body { padding: 10px; font-size: 15px; }
469
+ .container { padding: 20px; }
470
+ .report-header { flex-direction: column; align-items: flex-start; gap: 10px; }
471
+ .report-header h1 { font-size: 1.5em; }
472
+ .run-info { text-align: left; }
473
+ .summary-stats { grid-template-columns: 1fr 1fr; }
474
+ .filters-section { flex-direction: column; }
475
+ }
476
+ @media (max-width: 480px) {
477
+ .summary-stats { grid-template-columns: 1fr; }
478
+ }
479
+ </style>
480
+ </head>
481
+ <body>
482
+ <div class="container">
483
+ <header class="report-header">
484
+ <div class="report-header-title">
485
+ <img id="report-logo" src="https://i.postimg.cc/v817w4sg/logo.png" alt="Report Logo">
486
+ <h1>Playwright Pulse Summary</h1>
487
+ </div>
488
+ <div class="run-info">
489
+ <strong>Run Date:</strong> ${formatDate(
490
+ runSummary.timestamp
491
+ )}<br>
492
+ <strong>Total Duration:</strong> ${formatDuration(
493
+ runSummary.duration
494
+ )}
495
+ </div>
496
+ </header>
497
+
498
+ <section class="summary-section">
499
+ <div class="summary-stats">
500
+ <div class="stat-card">
501
+ <h3>Total Tests</h3>
502
+ <div class="value">${runSummary.totalTests}</div>
503
+ </div>
504
+ <div class="stat-card passed">
505
+ <h3>Passed</h3>
506
+ <div class="value">${runSummary.passed}</div>
507
+ </div>
508
+ <div class="stat-card failed">
509
+ <h3>Failed</h3>
510
+ <div class="value">${runSummary.failed}</div>
511
+ </div>
512
+ <div class="stat-card skipped">
513
+ <h3>Skipped</h3>
514
+ <div class="value">${runSummary.skipped || 0}</div>
515
+ </div>
516
+ </div>
517
+ </section>
518
+
519
+ <section class="test-results-section">
520
+ <h1 class="section-title">Test Case Summary</h1>
521
+
522
+ <div class="filters-section">
523
+ <input type="text" id="filter-min-name" placeholder="Search by test name...">
524
+ <select id="filter-min-status">
525
+ <option value="">All Statuses</option>
526
+ <option value="passed">Passed</option>
527
+ <option value="failed">Failed</option>
528
+ <option value="skipped">Skipped</option>
529
+ <option value="unknown">Unknown</option>
530
+ </select>
531
+ <select id="filter-min-browser">
532
+ <option value="">All Browsers</option>
533
+ ${Array.from(allBrowsers)
534
+ .map(
535
+ (browser) =>
536
+ `<option value="${sanitizeHTML(
537
+ browser.toLowerCase()
538
+ )}">${sanitizeHTML(capitalize(browser))}</option>`
539
+ )
540
+ .join("")}
541
+ </select>
542
+ <button id="clear-min-filters">Clear Filters</button>
543
+ </div>
544
+
545
+ ${generateTestListHTML()}
546
+ </section>
547
+
548
+ <footer class="report-footer">
549
+ <div style="display: inline-flex; align-items: center; gap: 0.5rem;">
550
+ <span>Created for</span>
551
+ <a href="https://playwright-pulse-report.netlify.app/" target="_blank" rel="noopener noreferrer">
552
+ Pulse Email Report
553
+ </a>
554
+ </div>
555
+ <div style="margin-top: 0.3rem; font-size: 0.7rem;">Crafted with precision</div>
556
+ </footer>
557
+ </div>
558
+ <script>
559
+ document.addEventListener('DOMContentLoaded', function() {
560
+ const nameFilterMin = document.getElementById('filter-min-name');
561
+ const statusFilterMin = document.getElementById('filter-min-status');
562
+ const browserFilterMin = document.getElementById('filter-min-browser');
563
+ const clearMinFiltersBtn = document.getElementById('clear-min-filters');
564
+ const testItemsMin = document.querySelectorAll('.test-results-section .test-item');
565
+ const browserSections = document.querySelectorAll('.test-results-section .browser-section');
566
+
567
+ function filterMinifiedTests() {
568
+ const nameValue = nameFilterMin.value.toLowerCase();
569
+ const statusValue = statusFilterMin.value;
570
+ const browserValue = browserFilterMin.value;
571
+ let anyBrowserSectionVisible = false;
572
+
573
+ browserSections.forEach(section => {
574
+ let sectionHasVisibleTests = false;
575
+ const testsInThisSection = section.querySelectorAll('.test-item');
576
+
577
+ testsInThisSection.forEach(testItem => {
578
+ const testName = testItem.getAttribute('data-test-name-min');
579
+ const testStatus = testItem.getAttribute('data-status-min');
580
+ const testBrowser = testItem.getAttribute('data-browser-min');
581
+
582
+ const nameMatch = testName.includes(nameValue);
583
+ const statusMatch = !statusValue || testStatus === statusValue;
584
+ const browserMatch = !browserValue || testBrowser === browserValue;
585
+
586
+ if (nameMatch && statusMatch && browserMatch) {
587
+ testItem.style.display = 'flex';
588
+ sectionHasVisibleTests = true;
589
+ anyBrowserSectionVisible = true;
590
+ } else {
591
+ testItem.style.display = 'none';
592
+ }
593
+ });
594
+ // Hide browser section if no tests match OR if a specific browser is selected and it's not this one
595
+ if (!sectionHasVisibleTests || (browserValue && section.getAttribute('data-browser-group') !== browserValue)) {
596
+ section.style.display = 'none';
597
+ } else {
598
+ section.style.display = '';
599
+ }
600
+ });
601
+
602
+ // Show "no tests" message if all sections are hidden
603
+ const noTestsMessage = document.querySelector('.test-results-section .no-tests');
604
+ if (noTestsMessage) {
605
+ noTestsMessage.style.display = anyBrowserSectionVisible ? 'none' : 'block';
606
+ }
607
+
608
+ }
609
+
610
+ if (nameFilterMin) nameFilterMin.addEventListener('input', filterMinifiedTests);
611
+ if (statusFilterMin) statusFilterMin.addEventListener('change', filterMinifiedTests);
612
+ if (browserFilterMin) browserFilterMin.addEventListener('change', filterMinifiedTests);
613
+
614
+ if (clearMinFiltersBtn) {
615
+ clearMinFiltersBtn.addEventListener('click', () => {
616
+ nameFilterMin.value = '';
617
+ statusFilterMin.value = '';
618
+ browserFilterMin.value = '';
619
+ filterMinifiedTests();
620
+ });
621
+ }
622
+ // Initial filter call in case of pre-filled values (though unlikely here)
623
+ if (testItemsMin.length > 0) { // Only filter if there are items
624
+ filterMinifiedTests();
625
+ }
626
+ });
627
+
628
+ // Fallback helper functions (though ideally not needed client-side for this minified report)
629
+ if (typeof formatDuration === 'undefined') {
630
+ function formatDuration(ms) {
631
+ if (ms === undefined || ms === null || ms < 0) return "0.0s";
632
+ return (ms / 1000).toFixed(1) + "s";
633
+ }
634
+ }
635
+ if (typeof formatDate === 'undefined') {
636
+ function formatDate(dateStrOrDate) {
637
+ if (!dateStrOrDate) return "N/A";
638
+ try {
639
+ const date = new Date(dateStrOrDate);
640
+ if (isNaN(date.getTime())) return "Invalid Date";
641
+ return (
642
+ date.toLocaleDateString(undefined, { year: "2-digit", month: "2-digit", day: "2-digit" }) +
643
+ " " +
644
+ date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })
645
+ );
646
+ } catch (e) { return "Invalid Date Format"; }
647
+ }
648
+ }
649
+ </script>
650
+ </body>
651
+ </html>
652
+ `;
653
+ }
654
+ async function main() {
655
+ const outputDir = path.resolve(process.cwd(), DEFAULT_OUTPUT_DIR);
656
+ const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE);
657
+ const minifiedReportHtmlPath = path.resolve(outputDir, MINIFIED_HTML_FILE); // Path for the new minified HTML
658
+
659
+ // Step 2: Load current run's data
660
+ let currentRunReportData;
661
+ try {
662
+ const jsonData = await fs.readFile(reportJsonPath, "utf-8");
663
+ currentRunReportData = JSON.parse(jsonData);
664
+ if (
665
+ !currentRunReportData ||
666
+ typeof currentRunReportData !== "object" ||
667
+ !currentRunReportData.results
668
+ ) {
669
+ throw new Error(
670
+ "Invalid report JSON structure. 'results' field is missing or invalid."
671
+ );
672
+ }
673
+ if (!Array.isArray(currentRunReportData.results)) {
674
+ currentRunReportData.results = [];
675
+ console.warn(
676
+ chalk.yellow(
677
+ "Warning: 'results' field in current run JSON was not an array. Treated as empty."
678
+ )
679
+ );
680
+ }
681
+ } catch (error) {
682
+ console.error(
683
+ chalk.red(
684
+ `Critical Error: Could not read or parse main report JSON at ${reportJsonPath}: ${error.message}`
685
+ )
686
+ );
687
+ process.exit(1);
688
+ }
689
+
690
+ // Step 3: Generate and write Minified HTML
691
+ try {
692
+ const htmlContent = generateMinifiedHTML(currentRunReportData); // Use the new generator
693
+ await fs.writeFile(minifiedReportHtmlPath, htmlContent, "utf-8");
694
+ console.log(
695
+ chalk.green.bold(
696
+ `🎉 Minified Pulse summary report generated successfully at: ${minifiedReportHtmlPath}`
697
+ )
698
+ );
699
+ console.log(chalk.gray(`(This HTML file is designed to be lightweight)`));
700
+ } catch (error) {
701
+ console.error(
702
+ chalk.red(`Error generating minified HTML report: ${error.message}`)
703
+ );
704
+ console.error(chalk.red(error.stack));
705
+ process.exit(1);
706
+ }
707
+ }
708
+ main().catch((err) => {
709
+ console.error(
710
+ chalk.red.bold(`Unhandled error during script execution: ${err.message}`)
711
+ );
712
+ console.error(err.stack);
713
+ process.exit(1);
714
+ });