@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.
- package/dist/reporter/playwright-pulse-reporter.js +63 -47
- package/dist/types/index.d.ts +4 -2
- package/package.json +4 -2
- package/scripts/generate-email-report.mjs +26 -11
- package/scripts/generate-report.mjs +531 -210
- package/scripts/generate-static-report.mjs +627 -263
- package/scripts/merge-pulse-report.js +3 -0
- package/scripts/sendReport.mjs +3 -0
- package/scripts/terminal-logo.mjs +51 -0
|
@@ -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
|
-
|
|
119
|
+
extractCodeSnippet(filePath, targetLine, targetColumn) {
|
|
120
|
+
var _a;
|
|
120
121
|
try {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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 (
|
|
140
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
246
|
+
const processAllSteps = async (steps) => {
|
|
242
247
|
let processed = [];
|
|
243
|
-
let foundFailedStep = false;
|
|
244
248
|
for (const step of steps) {
|
|
245
|
-
const
|
|
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
|
|
252
|
+
processedStep.steps = await processAllSteps(step.steps);
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
return processed;
|
|
256
256
|
};
|
|
257
|
-
let codeSnippet =
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
353
|
+
const resultsMap = new Map();
|
|
358
354
|
for (const result of allResults) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
|
381
|
+
return finalResults;
|
|
366
382
|
}
|
|
367
383
|
onError(error) {
|
|
368
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": {
|
|
@@ -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
|
|
586
|
-
<option value="passed"
|
|
587
|
-
<option value="failed"
|
|
588
|
-
<option value="skipped"
|
|
589
|
-
<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>
|
|
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
|
-
|
|
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) {
|