@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
|
-
|
|
119
|
+
extractCodeSnippet(filePath, targetLine, targetColumn) {
|
|
120
|
+
var _a;
|
|
120
121
|
try {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return undefined;
|
|
122
|
+
const fsSync = require('fs');
|
|
123
|
+
if (!fsSync.existsSync(filePath)) {
|
|
124
|
+
return '';
|
|
125
125
|
}
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
131
|
+
return ((_a = lines[targetLine - 1]) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
142
132
|
}
|
|
143
|
-
catch (
|
|
144
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
246
|
+
const processAllSteps = async (steps) => {
|
|
255
247
|
let processed = [];
|
|
256
|
-
let foundFailedStep = false;
|
|
257
248
|
for (const step of steps) {
|
|
258
|
-
const
|
|
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
|
|
252
|
+
processedStep.steps = await processAllSteps(step.steps);
|
|
266
253
|
}
|
|
267
254
|
}
|
|
268
255
|
return processed;
|
|
269
256
|
};
|
|
270
|
-
let codeSnippet =
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
353
|
+
const resultsMap = new Map();
|
|
370
354
|
for (const result of allResults) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
|
381
|
+
return finalResults;
|
|
378
382
|
}
|
|
379
383
|
onError(error) {
|
|
380
384
|
var _a;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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.
|
|
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
|
|
587
|
-
<option value="passed"
|
|
588
|
-
<option value="failed"
|
|
589
|
-
<option value="skipped"
|
|
590
|
-
<option value="unknown"
|
|
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>
|