@arghajit/dummy 0.3.19 → 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,33 +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
- if (targetLine < 1 || targetLine > lines.length) {
124
- return undefined;
122
+ const fsSync = require('fs');
123
+ if (!fsSync.existsSync(filePath)) {
124
+ return '';
125
125
  }
126
- const contextLines = 2;
127
- const startLine = Math.max(0, targetLine - contextLines - 1);
128
- const endLine = Math.min(lines.length, targetLine + contextLines);
129
- const snippetLines = [];
130
- for (let i = startLine; i < endLine; i++) {
131
- const lineNum = i + 1;
132
- const isTargetLine = lineNum === targetLine;
133
- const lineContent = lines[i] || "";
134
- const prefix = isTargetLine ? ">" : " ";
135
- snippetLines.push(`${prefix} ${lineNum.toString().padStart(4)} | ${lineContent}`);
136
- if (isTargetLine && targetColumn > 0) {
137
- const pointer = " " + " ".repeat(4) + " | " + " ".repeat(Math.max(0, targetColumn)) + "^";
138
- snippetLines.push(pointer);
139
- }
126
+ const content = fsSync.readFileSync(filePath, 'utf8');
127
+ const lines = content.split('\n');
128
+ if (targetLine < 1 || targetLine > lines.length) {
129
+ return '';
140
130
  }
141
- return snippetLines.length > 0 ? snippetLines.join("\n") : undefined;
131
+ return ((_a = lines[targetLine - 1]) === null || _a === void 0 ? void 0 : _a.trim()) || '';
142
132
  }
143
- catch (error) {
144
- console.error(`Failed to extract code snippet from ${filePath}:${targetLine}:${targetColumn}`, error);
145
- return undefined;
133
+ catch (e) {
134
+ return '';
146
135
  }
147
136
  }
148
137
  getBrowserDetails(test) {
@@ -195,7 +184,7 @@ class PlaywrightPulseReporter {
195
184
  }
196
185
  return finalString.trim();
197
186
  }
198
- async processStep(step, testId, browserDetails, testCase, isFailedStep = false) {
187
+ async processStep(step, testId, browserDetails, testCase) {
199
188
  var _a, _b, _c, _d;
200
189
  let stepStatus = "passed";
201
190
  let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
@@ -209,18 +198,10 @@ class PlaywrightPulseReporter {
209
198
  const startTime = new Date(step.startTime);
210
199
  const endTime = new Date(startTime.getTime() + Math.max(0, duration));
211
200
  let codeLocation = "";
212
- let codeSnippet = undefined;
201
+ let codeSnippet = '';
213
202
  if (step.location) {
214
203
  codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
215
- try {
216
- codeSnippet = await this.extractCodeSnippet(step.location.file, step.location.line, step.location.column);
217
- if (!codeSnippet) {
218
- console.warn(`Pulse Reporter: extractCodeSnippet returned undefined for step "${step.title}" at ${step.location.file}:${step.location.line}:${step.location.column}`);
219
- }
220
- }
221
- catch (error) {
222
- console.error(`Pulse Reporter: Failed to extract code snippet for step "${step.title}":`, error);
223
- }
204
+ codeSnippet = this.extractCodeSnippet(step.location.file, step.location.line, step.location.column);
224
205
  }
225
206
  return {
226
207
  id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
@@ -240,7 +221,6 @@ class PlaywrightPulseReporter {
240
221
  ? "before"
241
222
  : "after"
242
223
  : undefined,
243
- isFailedStep: isFailedStep,
244
224
  steps: [],
245
225
  };
246
226
  }
@@ -248,33 +228,35 @@ class PlaywrightPulseReporter {
248
228
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
249
229
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
250
230
  const browserDetails = this.getBrowserDetails(test);
251
- 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
+ }
252
244
  const startTime = new Date(result.startTime);
253
245
  const endTime = new Date(startTime.getTime() + result.duration);
254
- const processAllSteps = async (steps, parentFailed = false) => {
246
+ const processAllSteps = async (steps) => {
255
247
  let processed = [];
256
- let foundFailedStep = false;
257
248
  for (const step of steps) {
258
- const isThisStepFailed = !foundFailedStep && (step.error !== undefined || (testStatus === "failed" && !foundFailedStep));
259
- if (isThisStepFailed) {
260
- foundFailedStep = true;
261
- }
262
- const processedStep = await this.processStep(step, test.id, browserDetails, test, isThisStepFailed);
249
+ const processedStep = await this.processStep(step, test.id, browserDetails, test);
263
250
  processed.push(processedStep);
264
251
  if (step.steps && step.steps.length > 0) {
265
- processedStep.steps = await processAllSteps(step.steps, isThisStepFailed);
252
+ processedStep.steps = await processAllSteps(step.steps);
266
253
  }
267
254
  }
268
255
  return processed;
269
256
  };
270
- let codeSnippet = undefined;
271
- try {
272
- 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)) {
273
- codeSnippet = await this.extractCodeSnippet(test.location.file, test.location.line, test.location.column);
274
- }
275
- }
276
- catch (e) {
277
- 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);
278
260
  }
279
261
  // 1. Get Spec File Name
280
262
  const specFileName = ((_e = test.location) === null || _e === void 0 ? void 0 : _e.file)
@@ -308,6 +290,8 @@ class PlaywrightPulseReporter {
308
290
  name: test.titlePath().join(" > "),
309
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",
310
292
  status: testStatus,
293
+ outcome: outcome === 'flaky' ? outcome : undefined, // Only Include if flaky
294
+ final_status: finalStatus, // New Field
311
295
  duration: result.duration,
312
296
  startTime: startTime,
313
297
  endTime: endTime,
@@ -366,15 +350,35 @@ class PlaywrightPulseReporter {
366
350
  this.results.push(pulseResult);
367
351
  }
368
352
  _getFinalizedResults(allResults) {
369
- const finalResultsMap = new Map();
353
+ const resultsMap = new Map();
370
354
  for (const result of allResults) {
371
- const existing = finalResultsMap.get(result.id);
372
- // Keep the result with the highest retry attempt for each test ID
373
- if (!existing || result.retries >= existing.retries) {
374
- 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;
375
378
  }
379
+ finalResults.push(firstAttempt);
376
380
  }
377
- return Array.from(finalResultsMap.values());
381
+ return finalResults;
378
382
  }
379
383
  onError(error) {
380
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.19",
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": {
@@ -242,6 +242,17 @@ function generateMinifiedHTML(reportData) {
242
242
  severity
243
243
  )}; font-size: 0.8em; font-weight: 600; padding: 3px 8px; border-radius: 4px; color: #fff; margin-left: 10px; white-space: nowrap;">${severity}</span>`;
244
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
+
245
256
  // --- NEW: Tags Logic ---
246
257
  const tagsBadges = (test.tags || [])
247
258
  .map(
@@ -266,6 +277,7 @@ function generateMinifiedHTML(reportData) {
266
277
  test.name
267
278
  )}">${sanitizeHTML(testTitle)}</span>
268
279
 
280
+ ${retryCountBadge}
269
281
  ${severityBadge}
270
282
  ${tagsBadges}
271
283
  </li>
@@ -532,10 +544,10 @@ function generateMinifiedHTML(reportData) {
532
544
  </div>
533
545
  <div class="run-info">
534
546
  <strong>Run Date:</strong> ${formatDate(
535
- runSummary.timestamp
547
+ runSummary.timestamp,
536
548
  )}<br>
537
549
  <strong>Total Duration:</strong> ${formatDuration(
538
- runSummary.duration
550
+ runSummary.duration,
539
551
  )}
540
552
  </div>
541
553
  </header>
@@ -583,11 +595,11 @@ function generateMinifiedHTML(reportData) {
583
595
  <div class="filters-section">
584
596
  <input type="text" id="filter-min-name" placeholder="Search by test name...">
585
597
  <select id="filter-min-status">
586
- <option value="">All Statuses</option>
587
- <option value="passed">Passed</option>
588
- <option value="failed">Failed</option>
589
- <option value="skipped">Skipped</option>
590
- <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>
591
603
  </select>
592
604
  <select id="filter-min-browser">
593
605
  <option value="">All Browsers</option>
@@ -595,8 +607,8 @@ function generateMinifiedHTML(reportData) {
595
607
  .map(
596
608
  (browser) =>
597
609
  `<option value="${sanitizeHTML(
598
- browser.toLowerCase()
599
- )}">${sanitizeHTML(capitalize(browser))}</option>`
610
+ browser.toLowerCase(),
611
+ )}">${sanitizeHTML(capitalize(browser))}</option>`,
600
612
  )
601
613
  .join("")}
602
614
  </select>