@arghajit/dummy 0.1.0-beta-19 → 0.1.0-beta-21

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/README.md CHANGED
@@ -161,8 +161,8 @@ npx generate-report
161
161
  1. Configure `.env`:
162
162
 
163
163
  ```bash
164
- SENDER_EMAIL_1=recipient1@example.com
165
- SENDER_EMAIL_2=recipient2@example.com
164
+ RECIPIENT_EMAIL_1=recipient1@example.com
165
+ RECIPIENT_EMAIL_2=recipient2@example.com
166
166
  # ... up to 5 recipients
167
167
  ```
168
168
 
@@ -277,7 +277,7 @@ npm install @arghajit/playwright-pulse-report@latest
277
277
 
278
278
  ---
279
279
 
280
- <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//pulse-logo.png" alt="pulse dashboard" title="pulse dashboard" height="50px" width="80px" align="left" padding="10px"/>
280
+ <img src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//pulse-logo.png" alt="pulse dashboard" title="pulse dashboard" height="35px" width="60px" align="left" padding="5px"/>
281
281
  <h2>Pulse Dashboard</h2>
282
282
 
283
283
  **Real-time Playwright Test Monitoring & Analysis**
@@ -300,13 +300,13 @@ npm run pulse-dashboard
300
300
 
301
301
  *(Run from project root containing `pulse-report/` directory)*
302
302
 
303
- **NPM Package**: [pulse-dashboard on npm](https://www.npmjs.com/package/pulse-dashboard)
303
+ **NPM Package**: [pulse-dashboard](https://www.npmjs.com/package/pulse-dashboard)
304
304
 
305
305
  **Tech Stack**: Next.js, TypeScript, Tailwind CSS, Playwright
306
306
 
307
- *Part of the Playwright Pulse Report ecosystem*
307
+ *Part of the Playwright Pulse Report ecosystem*
308
308
 
309
- ---
309
+ ---
310
310
 
311
311
  ## 📬 Support
312
312
 
@@ -11,6 +11,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
11
11
  private baseOutputFile;
12
12
  private isSharded;
13
13
  private shardIndex;
14
+ private resetOnEachRun;
14
15
  constructor(options?: PlaywrightPulseReporterOptions);
15
16
  printsToStdio(): boolean;
16
17
  onBegin(config: FullConfig, suite: Suite): void;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.PlaywrightPulseReporter = void 0;
37
37
  const fs = __importStar(require("fs/promises"));
38
38
  const path = __importStar(require("path"));
39
+ const fsSync = __importStar(require("fs"));
39
40
  const crypto_1 = require("crypto");
40
41
  const ua_parser_js_1 = require("ua-parser-js");
41
42
  const os = __importStar(require("os"));
@@ -62,7 +63,7 @@ const TEMP_SHARD_FILE_PREFIX = ".pulse-shard-results-";
62
63
  const ATTACHMENTS_SUBDIR = "attachments";
63
64
  class PlaywrightPulseReporter {
64
65
  constructor(options = {}) {
65
- var _a, _b;
66
+ var _a, _b, _c;
66
67
  this.results = [];
67
68
  this.baseOutputFile = "playwright-pulse-report.json";
68
69
  this.isSharded = false;
@@ -71,6 +72,7 @@ class PlaywrightPulseReporter {
71
72
  this.baseOutputFile = (_a = options.outputFile) !== null && _a !== void 0 ? _a : this.baseOutputFile;
72
73
  this.outputDir = (_b = options.outputDir) !== null && _b !== void 0 ? _b : "pulse-report";
73
74
  this.attachmentsDir = path.join(this.outputDir, ATTACHMENTS_SUBDIR);
75
+ this.resetOnEachRun = (_c = options.resetOnEachRun) !== null && _c !== void 0 ? _c : false;
74
76
  }
75
77
  printsToStdio() {
76
78
  return this.shardIndex === undefined || this.shardIndex === 0;
@@ -253,37 +255,47 @@ class PlaywrightPulseReporter {
253
255
  codeSnippet: codeSnippet,
254
256
  tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
255
257
  screenshots: [],
256
- videoPath: [], // MODIFIED: Initialized as an array
258
+ videoPath: [],
257
259
  tracePath: undefined,
258
- attachments: [], // NEW: Initialized
260
+ attachments: [],
259
261
  stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
260
262
  stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
261
263
  ...testSpecificData,
262
264
  };
263
- // --- NEW SELF-CONTAINED ATTACHMENT PROCESSING LOGIC ---
264
- for (const attachment of result.attachments) {
265
+ // --- CORRECTED ATTACHMENT PROCESSING LOGIC ---
266
+ for (const [index, attachment] of result.attachments.entries()) {
265
267
  if (!attachment.path)
266
268
  continue;
267
269
  try {
268
- const attachmentFileName = path.basename(attachment.path);
269
- const relativeDestPath = path.join(ATTACHMENTS_SUBDIR, attachmentFileName);
270
+ // Create a sanitized, unique folder name for this specific test
271
+ const testSubfolder = test.id.replace(/[^a-zA-Z0-9_-]/g, "_");
272
+ // Sanitize the original attachment name to create a safe filename
273
+ const safeAttachmentName = path
274
+ .basename(attachment.path)
275
+ .replace(/[^a-zA-Z0-9_.-]/g, "_");
276
+ // Create a unique filename to prevent collisions, especially in retries
277
+ const uniqueFileName = `${index}-${Date.now()}-${safeAttachmentName}`;
278
+ // This is the relative path that will be stored in the JSON report
279
+ const relativeDestPath = path.join(ATTACHMENTS_SUBDIR, testSubfolder, uniqueFileName);
280
+ // This is the absolute path used for the actual file system operation
270
281
  const absoluteDestPath = path.join(this.outputDir, relativeDestPath);
282
+ // Ensure the unique, test-specific attachment directory exists
271
283
  await this._ensureDirExists(path.dirname(absoluteDestPath));
272
284
  await fs.copyFile(attachment.path, absoluteDestPath);
285
+ // Categorize the attachment based on its content type
273
286
  if (attachment.contentType.startsWith("image/")) {
274
287
  (_j = pulseResult.screenshots) === null || _j === void 0 ? void 0 : _j.push(relativeDestPath);
275
288
  }
276
289
  else if (attachment.contentType.startsWith("video/")) {
277
- (_k = pulseResult.videoPath) === null || _k === void 0 ? void 0 : _k.push(relativeDestPath); // MODIFIED: Push to videoPath array
290
+ (_k = pulseResult.videoPath) === null || _k === void 0 ? void 0 : _k.push(relativeDestPath);
278
291
  }
279
292
  else if (attachment.name === "trace") {
280
293
  pulseResult.tracePath = relativeDestPath;
281
294
  }
282
295
  else {
283
- // NEW: Handle all other file types
284
296
  (_l = pulseResult.attachments) === null || _l === void 0 ? void 0 : _l.push({
285
- name: attachment.name,
286
- path: relativeDestPath,
297
+ name: attachment.name, // The original, human-readable name
298
+ path: relativeDestPath, // The safe, relative path for linking
287
299
  contentType: attachment.contentType,
288
300
  });
289
301
  }
@@ -459,29 +471,118 @@ class PlaywrightPulseReporter {
459
471
  console.error("PlaywrightPulseReporter: CRITICAL - finalReport object was not generated. Cannot create summary.");
460
472
  return;
461
473
  }
462
- const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
463
- try {
464
- await this._ensureDirExists(this.outputDir);
465
- await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
466
- if (value instanceof Date)
467
- return value.toISOString();
468
- if (typeof value === "bigint")
469
- return value.toString();
470
- return value;
471
- }, 2));
472
- if (this.printsToStdio()) {
473
- console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
474
+ if (this.resetOnEachRun == true) {
475
+ const finalOutputPath = path.join(this.outputDir, this.baseOutputFile);
476
+ try {
477
+ await this._ensureDirExists(this.outputDir);
478
+ await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
479
+ if (value instanceof Date)
480
+ return value.toISOString();
481
+ if (typeof value === "bigint")
482
+ return value.toString();
483
+ return value;
484
+ }, 2));
485
+ if (this.printsToStdio()) {
486
+ console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
487
+ }
488
+ }
489
+ catch (error) {
490
+ console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
491
+ }
492
+ finally {
493
+ if (this.isSharded) {
494
+ await this._cleanupTemporaryFiles();
495
+ }
474
496
  }
475
497
  }
476
- catch (error) {
477
- console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
478
- }
479
- finally {
480
- if (this.isSharded) {
481
- await this._cleanupTemporaryFiles();
498
+ else {
499
+ console.warn("PlaywrightPulseReporter: resetOnEachRun is set to false. The finalReport will display all the results present in '/pulse-results'.");
500
+ const finalOutputPath = path.join(`${this.outputDir}/pulse-results`, `playwright-pulse-report-${Date.now()}.json`);
501
+ try {
502
+ await this._ensureDirExists(this.outputDir);
503
+ await fs.writeFile(finalOutputPath, JSON.stringify(finalReport, (key, value) => {
504
+ if (value instanceof Date)
505
+ return value.toISOString();
506
+ if (typeof value === "bigint")
507
+ return value.toString();
508
+ return value;
509
+ }, 2));
510
+ if (this.printsToStdio()) {
511
+ console.log(`PlaywrightPulseReporter: JSON report written to ${finalOutputPath}`);
512
+ }
513
+ }
514
+ catch (error) {
515
+ console.error(`Pulse Reporter: Failed to write final JSON report to ${finalOutputPath}. Error: ${error.message}`);
516
+ }
517
+ finally {
518
+ if (this.isSharded) {
519
+ await this._cleanupTemporaryFiles();
520
+ }
482
521
  }
483
522
  }
484
523
  }
485
524
  }
486
525
  exports.PlaywrightPulseReporter = PlaywrightPulseReporter;
526
+ function falseResetOnEachRun() {
527
+ const REPORT_DIR = "./pulse-report"; // Or change this to your reports directory
528
+ const OUTPUT_FILE = "playwright-pulse-report.json";
529
+ function getReportFiles(dir) {
530
+ return fsSync
531
+ .readdirSync(dir)
532
+ .filter((file) => file.startsWith("playwright-pulse-report-") && file.endsWith(".json"));
533
+ }
534
+ function mergeReports(files) {
535
+ var _a;
536
+ let combinedRun = {
537
+ totalTests: 0,
538
+ passed: 0,
539
+ failed: 0,
540
+ skipped: 0,
541
+ duration: 0,
542
+ environment: {},
543
+ };
544
+ let combinedResults = [];
545
+ let latestTimestamp = "";
546
+ let latestGeneratedAt = "";
547
+ for (const file of files) {
548
+ const filePath = path.join(`${REPORT_DIR}/pulse-results`, file);
549
+ const json = JSON.parse(fsSync.readFileSync(filePath, "utf-8"));
550
+ const run = json.run || {};
551
+ combinedRun.totalTests += run.totalTests || 0;
552
+ combinedRun.passed += run.passed || 0;
553
+ combinedRun.failed += run.failed || 0;
554
+ combinedRun.skipped += run.skipped || 0;
555
+ combinedRun.duration += run.duration || 0;
556
+ combinedRun.environment = run.environment || {};
557
+ if (json.results) {
558
+ combinedResults.push(...json.results);
559
+ }
560
+ if (run.timestamp > latestTimestamp)
561
+ latestTimestamp = run.timestamp;
562
+ if (((_a = json.metadata) === null || _a === void 0 ? void 0 : _a.generatedAt) > latestGeneratedAt)
563
+ latestGeneratedAt = json.metadata.generatedAt;
564
+ }
565
+ const finalJson = {
566
+ run: {
567
+ id: `merged-${Date.now()}`,
568
+ timestamp: latestTimestamp,
569
+ ...combinedRun,
570
+ },
571
+ results: combinedResults,
572
+ metadata: {
573
+ generatedAt: latestGeneratedAt,
574
+ },
575
+ };
576
+ return finalJson;
577
+ }
578
+ // Main execution
579
+ const reportFiles = getReportFiles(REPORT_DIR);
580
+ if (reportFiles.length === 0) {
581
+ console.log("No matching JSON report files found.");
582
+ process.exit(1);
583
+ }
584
+ const merged = mergeReports(reportFiles);
585
+ fsSync.writeFileSync(path.join(REPORT_DIR, OUTPUT_FILE), JSON.stringify(merged, null, 2));
586
+ console.log(`✅ Merged report saved as ${OUTPUT_FILE}`);
587
+ }
487
588
  exports.default = PlaywrightPulseReporter;
@@ -72,6 +72,7 @@ export interface PlaywrightPulseReporterOptions {
72
72
  outputFile?: string;
73
73
  outputDir?: string;
74
74
  base64Images?: boolean;
75
+ resetOnEachRun?: boolean;
75
76
  }
76
77
  export interface EnvDetails {
77
78
  host: string;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@arghajit/dummy",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.1.0-beta-19",
4
+ "version": "0.1.0-beta-21",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
+ "homepage": "https://playwright-pulse-report.netlify.app/",
6
7
  "keywords": [
7
8
  "playwright",
8
9
  "reporter",
@@ -15,7 +16,9 @@
15
16
  "report",
16
17
  "email-report",
17
18
  "send-report",
18
- "email"
19
+ "email",
20
+ "playwright-report",
21
+ "pulse"
19
22
  ],
20
23
  "main": "dist/reporter/index.js",
21
24
  "types": "dist/reporter/index.d.ts",
@@ -1409,6 +1409,11 @@ function generateHTML(reportData, trendData = null) {
1409
1409
  </div>
1410
1410
  <div class="test-case-content" style="display: none;">
1411
1411
  <p><strong>Full Path:</strong> ${sanitizeHTML(test.name)}</p>
1412
+ <p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
1413
+ test.workerId
1414
+ )} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
1415
+ test.totalWorkers
1416
+ )}]</p>
1412
1417
  ${
1413
1418
  test.errorMessage
1414
1419
  ? `<div class="test-error-summary">${formatPlaywrightError(
@@ -1441,15 +1446,15 @@ function generateHTML(reportData, trendData = null) {
1441
1446
  ${
1442
1447
  test.stdout && test.stdout.length > 0
1443
1448
  ? `<div class="console-output-section"><h4>Console Output (stdout)</h4><pre class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
1444
- test.stdout.join("\n")
1449
+ test.stdout.map((line) => sanitizeHTML(line)).join("\n")
1445
1450
  )}</pre></div>`
1446
1451
  : ""
1447
1452
  }
1448
1453
  ${
1449
1454
  test.stderr && test.stderr.length > 0
1450
- ? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${test.stderr
1451
- .map((line) => sanitizeHTML(line))
1452
- .join("\n")}</pre></div>`
1455
+ ? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
1456
+ test.stderr.map((line) => sanitizeHTML(line)).join("\n")
1457
+ )}</pre></div>`
1453
1458
  : ""
1454
1459
  }
1455
1460
  ${
@@ -1480,11 +1485,7 @@ function generateHTML(reportData, trendData = null) {
1480
1485
  }
1481
1486
  ${
1482
1487
  test.videoPath && test.videoPath.length > 0
1483
- ? `
1484
- <div class="attachments-section">
1485
- <h4>Videos</h4>
1486
- <div class="attachments-grid">
1487
- ${test.videoPath
1488
+ ? `<div class="attachments-section"><h4>Videos</h4><div class="attachments-grid">${test.videoPath
1488
1489
  .map((videoUrl, index) => {
1489
1490
  const fileExtension = String(videoUrl)
1490
1491
  .split(".")
@@ -1498,8 +1499,7 @@ function generateHTML(reportData, trendData = null) {
1498
1499
  mov: "video/quicktime",
1499
1500
  avi: "video/x-msvideo",
1500
1501
  }[fileExtension] || "video/mp4";
1501
- return `
1502
- <div class="attachment-item video-item">
1502
+ return `<div class="attachment-item video-item">
1503
1503
  <video controls width="100%" height="auto" title="Video ${
1504
1504
  index + 1
1505
1505
  }">
@@ -1517,10 +1517,7 @@ function generateHTML(reportData, trendData = null) {
1517
1517
  </div>
1518
1518
  </div>`;
1519
1519
  })
1520
- .join("")}
1521
- </div>
1522
- </div>
1523
- `
1520
+ .join("")}</div></div>`
1524
1521
  : ""
1525
1522
  }
1526
1523
  ${
@@ -1592,8 +1589,8 @@ function generateHTML(reportData, trendData = null) {
1592
1589
  }
1593
1590
  ${
1594
1591
  test.codeSnippet
1595
- ? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${sanitizeHTML(
1596
- test.codeSnippet
1592
+ ? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${formatPlaywrightError(
1593
+ sanitizeHTML(test.codeSnippet)
1597
1594
  )}</code></pre></div>`
1598
1595
  : ""
1599
1596
  }
@@ -1377,6 +1377,11 @@ function generateHTML(reportData, trendData = null) {
1377
1377
  <p><strong>Full Path:</strong> ${sanitizeHTML(
1378
1378
  test.name
1379
1379
  )}</p>
1380
+ <p><strong>Test run Worker ID:</strong> ${sanitizeHTML(
1381
+ test.workerId
1382
+ )} [<strong>Total No. of Workers:</strong> ${sanitizeHTML(
1383
+ test.totalWorkers
1384
+ )}]</p>
1380
1385
  ${
1381
1386
  test.errorMessage
1382
1387
  ? `<div class="test-error-summary">${formatPlaywrightError(
@@ -1535,6 +1540,14 @@ function generateHTML(reportData, trendData = null) {
1535
1540
  })
1536
1541
  .join("")}</div></div>`;
1537
1542
  })()}
1543
+
1544
+ ${
1545
+ test.codeSnippet
1546
+ ? `<div class="code-section"><h4>Code Snippet</h4><pre><code>${sanitizeHTML(
1547
+ test.codeSnippet
1548
+ )}</code></pre></div>`
1549
+ : ""
1550
+ }
1538
1551
  </div>
1539
1552
  </div>`;
1540
1553
  })
@@ -1,6 +1,6 @@
1
+ #!/usr/bin/env node
1
2
  import * as fs from "fs/promises";
2
3
  import path from "path";
3
- // XLSX is NO LONGER NEEDED here
4
4
 
5
5
  // Use dynamic import for chalk as it's ESM only for prettier console logs
6
6
  let chalk;
@@ -265,11 +265,11 @@ const sendEmail = async (credentials) => {
265
265
  const mailOptions = {
266
266
  from: credentials.username,
267
267
  to: [
268
- process.env.SENDER_EMAIL_1 || "",
269
- process.env.SENDER_EMAIL_2 || "",
270
- process.env.SENDER_EMAIL_3 || "",
271
- process.env.SENDER_EMAIL_4 || "",
272
- process.env.SENDER_EMAIL_5 || "",
268
+ process.env.RECIPIENT_EMAIL_1 || "",
269
+ process.env.RECIPIENT_EMAIL_2 || "",
270
+ process.env.RECIPIENT_EMAIL_3 || "",
271
+ process.env.RECIPIENT_EMAIL_4 || "",
272
+ process.env.RECIPIENT_EMAIL_5 || "",
273
273
  ].filter((email) => email), // Filter out empty strings
274
274
  subject: "Pulse Report " + new Date().toLocaleString(),
275
275
  html: htmlContent,