@arghajit/dummy 0.3.18 → 0.3.27

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.
@@ -116,28 +116,22 @@ class PlaywrightPulseReporter {
116
116
  const severityAnnotation = annotations.find((a) => a.type === "pulse_severity");
117
117
  return (severityAnnotation === null || severityAnnotation === void 0 ? void 0 : severityAnnotation.description) || "Medium";
118
118
  }
119
- async extractCodeSnippet(filePath, targetLine, targetColumn) {
119
+ extractCodeSnippet(filePath, targetLine, targetColumn) {
120
+ var _a;
120
121
  try {
121
- const fileContent = await fs.readFile(filePath, "utf-8");
122
- const lines = fileContent.split("\n");
123
- const contextLines = 1;
124
- const startLine = Math.max(0, targetLine - contextLines - 1);
125
- const endLine = Math.min(lines.length, targetLine + contextLines);
126
- const snippetLines = [];
127
- for (let i = startLine; i < endLine; i++) {
128
- const lineNum = i + 1;
129
- const isTargetLine = lineNum === targetLine;
130
- const lineContent = lines[i] || "";
131
- snippetLines.push(`${lineNum.toString().padStart(4)} | ${lineContent}`);
132
- if (isTargetLine) {
133
- const pointer = " ".repeat(4) + " | " + " ".repeat(targetColumn) + "^";
134
- snippetLines.push(pointer);
135
- }
122
+ const fsSync = require('fs');
123
+ if (!fsSync.existsSync(filePath)) {
124
+ return '';
136
125
  }
137
- return snippetLines.join("\n");
126
+ const content = fsSync.readFileSync(filePath, 'utf8');
127
+ const lines = content.split('\n');
128
+ if (targetLine < 1 || targetLine > lines.length) {
129
+ return '';
130
+ }
131
+ return ((_a = lines[targetLine - 1]) === null || _a === void 0 ? void 0 : _a.trim()) || '';
138
132
  }
139
- catch (error) {
140
- return undefined;
133
+ catch (e) {
134
+ return '';
141
135
  }
142
136
  }
143
137
  getBrowserDetails(test) {
@@ -190,7 +184,7 @@ class PlaywrightPulseReporter {
190
184
  }
191
185
  return finalString.trim();
192
186
  }
193
- async processStep(step, testId, browserDetails, testCase, isFailedStep = false) {
187
+ async processStep(step, testId, browserDetails, testCase) {
194
188
  var _a, _b, _c, _d;
195
189
  let stepStatus = "passed";
196
190
  let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
@@ -204,10 +198,10 @@ class PlaywrightPulseReporter {
204
198
  const startTime = new Date(step.startTime);
205
199
  const endTime = new Date(startTime.getTime() + Math.max(0, duration));
206
200
  let codeLocation = "";
207
- let codeSnippet = undefined;
201
+ let codeSnippet = '';
208
202
  if (step.location) {
209
203
  codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
210
- codeSnippet = await this.extractCodeSnippet(step.location.file, step.location.line, step.location.column);
204
+ codeSnippet = this.extractCodeSnippet(step.location.file, step.location.line, step.location.column);
211
205
  }
212
206
  return {
213
207
  id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
@@ -227,7 +221,6 @@ class PlaywrightPulseReporter {
227
221
  ? "before"
228
222
  : "after"
229
223
  : undefined,
230
- isFailedStep: isFailedStep,
231
224
  steps: [],
232
225
  };
233
226
  }
@@ -235,34 +228,35 @@ class PlaywrightPulseReporter {
235
228
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
236
229
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
237
230
  const browserDetails = this.getBrowserDetails(test);
238
- const testStatus = convertStatus(result.status, test);
231
+ // Captured outcome from Playwright
232
+ const outcome = test.outcome();
233
+ // Calculate final status based on the last result (Last-Run-Wins)
234
+ // result.status in onTestEnd is typically the status of the test run (passed if flaky passed)
235
+ // But we double check the last result in test.results just to be sure/consistent
236
+ const lastResult = test.results[test.results.length - 1];
237
+ const finalStatus = convertStatus(lastResult ? lastResult.status : result.status, test);
238
+ // Existing behavior: fail if flaky (implied by user request "existing status field should remain failed")
239
+ // If outcome is flaky, status should be 'failed' to indicate initial failure, but final_status is 'passed'
240
+ let testStatus = finalStatus;
241
+ if (outcome === 'flaky') {
242
+ testStatus = 'flaky';
243
+ }
239
244
  const startTime = new Date(result.startTime);
240
245
  const endTime = new Date(startTime.getTime() + result.duration);
241
- const processAllSteps = async (steps, parentFailed = false) => {
246
+ const processAllSteps = async (steps) => {
242
247
  let processed = [];
243
- let foundFailedStep = false;
244
248
  for (const step of steps) {
245
- const isThisStepFailed = !foundFailedStep && (step.error !== undefined || (testStatus === "failed" && !foundFailedStep));
246
- if (isThisStepFailed) {
247
- foundFailedStep = true;
248
- }
249
- const processedStep = await this.processStep(step, test.id, browserDetails, test, isThisStepFailed);
249
+ const processedStep = await this.processStep(step, test.id, browserDetails, test);
250
250
  processed.push(processedStep);
251
251
  if (step.steps && step.steps.length > 0) {
252
- processedStep.steps = await processAllSteps(step.steps, isThisStepFailed);
252
+ processedStep.steps = await processAllSteps(step.steps);
253
253
  }
254
254
  }
255
255
  return processed;
256
256
  };
257
- let codeSnippet = undefined;
258
- try {
259
- if (((_b = test.location) === null || _b === void 0 ? void 0 : _b.file) && ((_c = test.location) === null || _c === void 0 ? void 0 : _c.line) && ((_d = test.location) === null || _d === void 0 ? void 0 : _d.column)) {
260
- const relativePath = path.relative(this.config.rootDir, test.location.file);
261
- codeSnippet = `Test defined at: ${relativePath}:${test.location.line}:${test.location.column}`;
262
- }
263
- }
264
- catch (e) {
265
- console.warn(`Pulse Reporter: Could not extract code snippet for ${test.title}`, e);
257
+ let codeSnippet = '';
258
+ if (((_b = test.location) === null || _b === void 0 ? void 0 : _b.file) && ((_c = test.location) === null || _c === void 0 ? void 0 : _c.line) && ((_d = test.location) === null || _d === void 0 ? void 0 : _d.column)) {
259
+ codeSnippet = this.extractCodeSnippet(test.location.file, test.location.line, test.location.column);
266
260
  }
267
261
  // 1. Get Spec File Name
268
262
  const specFileName = ((_e = test.location) === null || _e === void 0 ? void 0 : _e.file)
@@ -296,6 +290,8 @@ class PlaywrightPulseReporter {
296
290
  name: test.titlePath().join(" > "),
297
291
  suiteName: (project === null || project === void 0 ? void 0 : project.name) || ((_g = this.config.projects[0]) === null || _g === void 0 ? void 0 : _g.name) || "Default Suite",
298
292
  status: testStatus,
293
+ outcome: outcome === 'flaky' ? outcome : undefined, // Only Include if flaky
294
+ final_status: finalStatus, // New Field
299
295
  duration: result.duration,
300
296
  startTime: startTime,
301
297
  endTime: endTime,
@@ -354,15 +350,35 @@ class PlaywrightPulseReporter {
354
350
  this.results.push(pulseResult);
355
351
  }
356
352
  _getFinalizedResults(allResults) {
357
- const finalResultsMap = new Map();
353
+ const resultsMap = new Map();
358
354
  for (const result of allResults) {
359
- const existing = finalResultsMap.get(result.id);
360
- // Keep the result with the highest retry attempt for each test ID
361
- if (!existing || result.retries >= existing.retries) {
362
- finalResultsMap.set(result.id, result);
355
+ if (!resultsMap.has(result.id)) {
356
+ resultsMap.set(result.id, []);
357
+ }
358
+ resultsMap.get(result.id).push(result);
359
+ }
360
+ const finalResults = [];
361
+ for (const [testId, attempts] of resultsMap.entries()) {
362
+ attempts.sort((a, b) => a.retries - b.retries);
363
+ const firstAttempt = attempts[0];
364
+ const retryAttempts = attempts.slice(1);
365
+ if (retryAttempts.length > 0) {
366
+ firstAttempt.retryHistory = retryAttempts;
367
+ // Calculate final status and outcome from the last attempt if retries exist
368
+ const lastAttempt = attempts[attempts.length - 1];
369
+ firstAttempt.final_status = lastAttempt.status;
370
+ // If the last attempt was flaky, ensure outcome is set on the main result
371
+ if (lastAttempt.outcome === 'flaky') {
372
+ firstAttempt.outcome = 'flaky';
373
+ }
374
+ }
375
+ else {
376
+ // If no retries, ensure final_status is undefined (as requested)
377
+ delete firstAttempt.final_status;
363
378
  }
379
+ finalResults.push(firstAttempt);
364
380
  }
365
- return Array.from(finalResultsMap.values());
381
+ return finalResults;
366
382
  }
367
383
  onError(error) {
368
384
  var _a;
@@ -1,5 +1,5 @@
1
1
  import type { LucideIcon } from "lucide-react";
2
- export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped";
2
+ export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped" | "flaky";
3
3
  export interface TestStep {
4
4
  id: string;
5
5
  title: string;
@@ -14,7 +14,6 @@ export interface TestStep {
14
14
  codeSnippet?: string;
15
15
  isHook?: boolean;
16
16
  hookType?: "before" | "after";
17
- isFailedStep?: boolean;
18
17
  steps?: TestStep[];
19
18
  }
20
19
  export interface TestResult {
@@ -37,6 +36,8 @@ export interface TestResult {
37
36
  suiteName?: string;
38
37
  runId: string;
39
38
  browser: string;
39
+ outcome?: string;
40
+ final_status?: TestStatus;
40
41
  screenshots?: string[];
41
42
  videoPath?: string[];
42
43
  tracePath?: string;
@@ -60,6 +61,7 @@ export interface TestResult {
60
61
  column: number;
61
62
  };
62
63
  }[];
64
+ retryHistory?: TestResult[];
63
65
  }
64
66
  export interface TestRun {
65
67
  id: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arghajit/dummy",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.3.18",
4
+ "version": "0.3.27",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://arghajit47.github.io/playwright-pulse/",
7
7
  "repository": {
@@ -34,12 +34,14 @@
34
34
  ],
35
35
  "license": "MIT",
36
36
  "bin": {
37
+ "logo": "node scripts/terminal-logo.mjs",
37
38
  "generate-pulse-report": "scripts/generate-static-report.mjs",
38
39
  "generate-report": "scripts/generate-report.mjs",
39
40
  "merge-pulse-report": "scripts/merge-pulse-report.js",
40
41
  "send-email": "scripts/sendReport.mjs",
41
42
  "generate-trend": "scripts/generate-trend.mjs",
42
- "generate-email-report": "scripts/generate-email-report.mjs"
43
+ "generate-email-report": "scripts/generate-email-report.mjs",
44
+ "pulse-logo": "scripts/terminal-logo.mjs"
43
45
  },
44
46
  "exports": {
45
47
  ".": {
@@ -3,6 +3,7 @@
3
3
  import * as fs from "fs/promises";
4
4
  import path from "path";
5
5
  import { getOutputDir } from "./config-reader.mjs";
6
+ import { animate } from "./terminal-logo.mjs";
6
7
 
7
8
  // Use dynamic import for chalk as it's ESM only
8
9
  let chalk;
@@ -241,6 +242,17 @@ function generateMinifiedHTML(reportData) {
241
242
  severity
242
243
  )}; font-size: 0.8em; font-weight: 600; padding: 3px 8px; border-radius: 4px; color: #fff; margin-left: 10px; white-space: nowrap;">${severity}</span>`;
243
244
 
245
+ // --- NEW: Retry Count Badge ---
246
+ const retryCountBadge = (test.retryHistory && test.retryHistory.length > 0)
247
+ ? `<span style="background-color: #f59e0b; border: 1px solid #d97706; font-size: 0.8em; font-weight: 700; padding: 4px 10px; border-radius: 50px; color: #fff; margin-left: 10px; white-space: nowrap; display: inline-flex; align-items: center; gap: 4px;">
248
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle;">
249
+ <path d="M1 4v6h6"/>
250
+ <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
251
+ </svg>
252
+ Retry Count: ${test.retryHistory.length}
253
+ </span>`
254
+ : '';
255
+
244
256
  // --- NEW: Tags Logic ---
245
257
  const tagsBadges = (test.tags || [])
246
258
  .map(
@@ -265,6 +277,7 @@ function generateMinifiedHTML(reportData) {
265
277
  test.name
266
278
  )}">${sanitizeHTML(testTitle)}</span>
267
279
 
280
+ ${retryCountBadge}
268
281
  ${severityBadge}
269
282
  ${tagsBadges}
270
283
  </li>
@@ -531,10 +544,10 @@ function generateMinifiedHTML(reportData) {
531
544
  </div>
532
545
  <div class="run-info">
533
546
  <strong>Run Date:</strong> ${formatDate(
534
- runSummary.timestamp
547
+ runSummary.timestamp,
535
548
  )}<br>
536
549
  <strong>Total Duration:</strong> ${formatDuration(
537
- runSummary.duration
550
+ runSummary.duration,
538
551
  )}
539
552
  </div>
540
553
  </header>
@@ -582,11 +595,11 @@ function generateMinifiedHTML(reportData) {
582
595
  <div class="filters-section">
583
596
  <input type="text" id="filter-min-name" placeholder="Search by test name...">
584
597
  <select id="filter-min-status">
585
- <option value="">All Statuses</option>
586
- <option value="passed">Passed</option>
587
- <option value="failed">Failed</option>
588
- <option value="skipped">Skipped</option>
589
- <option value="unknown">Unknown</option>
598
+ <option value="">All Status</option>
599
+ <option value="passed">✅ Passed</option>
600
+ <option value="failed">❌ Failed</option>
601
+ <option value="skipped">⏭️ Skipped</option>
602
+ <option value="unknown">❓ Unknown</option>
590
603
  </select>
591
604
  <select id="filter-min-browser">
592
605
  <option value="">All Browsers</option>
@@ -594,8 +607,8 @@ function generateMinifiedHTML(reportData) {
594
607
  .map(
595
608
  (browser) =>
596
609
  `<option value="${sanitizeHTML(
597
- browser.toLowerCase()
598
- )}">${sanitizeHTML(capitalize(browser))}</option>`
610
+ browser.toLowerCase(),
611
+ )}">${sanitizeHTML(capitalize(browser))}</option>`,
599
612
  )
600
613
  .join("")}
601
614
  </select>
@@ -712,6 +725,8 @@ function generateMinifiedHTML(reportData) {
712
725
  `;
713
726
  }
714
727
  async function main() {
728
+ await animate();
729
+
715
730
  const outputDir = await getOutputDir(customOutputDir);
716
731
  const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE);
717
732
  const minifiedReportHtmlPath = path.resolve(outputDir, MINIFIED_HTML_FILE); // Path for the new minified HTML
@@ -763,8 +778,8 @@ async function main() {
763
778
  await fs.writeFile(minifiedReportHtmlPath, htmlContent, "utf-8");
764
779
  console.log(
765
780
  chalk.green.bold(
766
- `🎉 Minified Pulse summary report generated successfully at: ${minifiedReportHtmlPath}`
767
- )
781
+ `Minified Pulse summary report generated successfully at: ${minifiedReportHtmlPath}`,
782
+ ),
768
783
  );
769
784
  console.log(chalk.gray(`(This HTML file is designed to be lightweight)`));
770
785
  } catch (error) {