@arghajit/dummy 0.3.14 → 0.3.15
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/LICENSE +21 -0
- package/README.md +3 -3
- package/package.json +4 -5
- package/scripts/generate-report.mjs +1540 -385
- package/scripts/generate-static-report.mjs +3701 -844
- package/scripts/merge-pulse-report.js +52 -21
- package/scripts/sendReport.mjs +150 -65
|
@@ -14,9 +14,27 @@ for (let i = 0; i < args.length; i++) {
|
|
|
14
14
|
|
|
15
15
|
const OUTPUT_FILE = "playwright-pulse-report.json";
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Securely resolves the report directory.
|
|
19
|
+
* Prevents Path Traversal by ensuring the output directory
|
|
20
|
+
* is contained within the current working directory.
|
|
21
|
+
*/
|
|
17
22
|
async function getReportDir() {
|
|
18
23
|
if (customOutputDir) {
|
|
19
|
-
|
|
24
|
+
// 1. Resolve the absolute path
|
|
25
|
+
const resolvedPath = path.resolve(process.cwd(), customOutputDir);
|
|
26
|
+
|
|
27
|
+
// 2. Security Check: Prevent Path Traversal
|
|
28
|
+
// We ensure the resolved path starts with the project root (process.cwd())
|
|
29
|
+
// This blocks inputs like "../../" that try to escape the project.
|
|
30
|
+
if (!resolvedPath.startsWith(process.cwd())) {
|
|
31
|
+
console.error(
|
|
32
|
+
"⛔ Security Error: Custom output directory must be within the current project root.",
|
|
33
|
+
);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return resolvedPath;
|
|
20
38
|
}
|
|
21
39
|
|
|
22
40
|
try {
|
|
@@ -28,11 +46,16 @@ async function getReportDir() {
|
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
function getReportFiles(dir) {
|
|
49
|
+
// Security Note: 'dir' here is now guaranteed to be safe/sanitized by getReportDir
|
|
50
|
+
if (!fs.existsSync(dir)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
31
54
|
return fs
|
|
32
55
|
.readdirSync(dir)
|
|
33
56
|
.filter(
|
|
34
57
|
(file) =>
|
|
35
|
-
file.startsWith("playwright-pulse-report-") && file.endsWith(".json")
|
|
58
|
+
file.startsWith("playwright-pulse-report-") && file.endsWith(".json"),
|
|
36
59
|
);
|
|
37
60
|
}
|
|
38
61
|
|
|
@@ -51,29 +74,36 @@ function mergeReports(files, reportDir) {
|
|
|
51
74
|
let latestGeneratedAt = "";
|
|
52
75
|
|
|
53
76
|
for (const file of files) {
|
|
77
|
+
// Security Note: 'file' comes from readdirSync (safe), reportDir is sanitized.
|
|
54
78
|
const filePath = path.join(reportDir, file);
|
|
55
|
-
const json = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
56
|
-
|
|
57
|
-
const run = json.run || {};
|
|
58
|
-
combinedRun.totalTests += run.totalTests || 0;
|
|
59
|
-
combinedRun.passed += run.passed || 0;
|
|
60
|
-
combinedRun.failed += run.failed || 0;
|
|
61
|
-
combinedRun.skipped += run.skipped || 0;
|
|
62
|
-
combinedRun.duration += run.duration || 0;
|
|
63
|
-
combinedRun.environment = run.environment;
|
|
64
|
-
|
|
65
|
-
if (json.results) {
|
|
66
|
-
combinedResults.push(...json.results);
|
|
67
|
-
}
|
|
68
79
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
try {
|
|
81
|
+
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
82
|
+
const json = JSON.parse(fileContent);
|
|
83
|
+
|
|
84
|
+
const run = json.run || {};
|
|
85
|
+
combinedRun.totalTests += run.totalTests || 0;
|
|
86
|
+
combinedRun.passed += run.passed || 0;
|
|
87
|
+
combinedRun.failed += run.failed || 0;
|
|
88
|
+
combinedRun.skipped += run.skipped || 0;
|
|
89
|
+
combinedRun.duration += run.duration || 0;
|
|
90
|
+
combinedRun.environment = run.environment;
|
|
91
|
+
|
|
92
|
+
if (json.results) {
|
|
93
|
+
combinedResults.push(...json.results);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (run.timestamp > latestTimestamp) latestTimestamp = run.timestamp;
|
|
97
|
+
if (json.metadata?.generatedAt > latestGeneratedAt)
|
|
98
|
+
latestGeneratedAt = json.metadata.generatedAt;
|
|
99
|
+
} catch (e) {
|
|
100
|
+
console.warn(`Warning: Failed to process ${file}: ${e.message}`);
|
|
101
|
+
}
|
|
72
102
|
}
|
|
73
103
|
|
|
74
104
|
const finalJson = {
|
|
75
105
|
run: {
|
|
76
|
-
id: `merged-${Date.now()}`,
|
|
106
|
+
id: `merged-${Date.now()}-581d5ad8-ce75-4ca5-94a6-ed29c466c815`,
|
|
77
107
|
timestamp: latestTimestamp,
|
|
78
108
|
...combinedRun,
|
|
79
109
|
},
|
|
@@ -106,9 +136,10 @@ function mergeReports(files, reportDir) {
|
|
|
106
136
|
|
|
107
137
|
const merged = mergeReports(reportFiles, REPORT_DIR);
|
|
108
138
|
|
|
139
|
+
// Security Note: REPORT_DIR is sanitized, so writeFileSync is safe.
|
|
109
140
|
fs.writeFileSync(
|
|
110
141
|
path.join(REPORT_DIR, OUTPUT_FILE),
|
|
111
|
-
JSON.stringify(merged, null, 2)
|
|
142
|
+
JSON.stringify(merged, null, 2),
|
|
112
143
|
);
|
|
113
144
|
console.log(`✅ Merged report saved as ${OUTPUT_FILE}`);
|
|
114
|
-
})();
|
|
145
|
+
})();
|
package/scripts/sendReport.mjs
CHANGED
|
@@ -144,71 +144,156 @@ const generateHtmlTable = (data) => {
|
|
|
144
144
|
const startTime = stats.startTime;
|
|
145
145
|
const durationString = stats.duration; // Already formatted string
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
147
|
+
return `
|
|
148
|
+
<!DOCTYPE html>
|
|
149
|
+
<html lang="en">
|
|
150
|
+
<head>
|
|
151
|
+
<meta charset="UTF-8">
|
|
152
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
153
|
+
<title>Test Stats Report</title>
|
|
154
|
+
<style>
|
|
155
|
+
/* ANIMATION KEYFRAMES
|
|
156
|
+
(Supported by Apple Mail, iOS, Outlook Mac, etc.)
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/* 1. Slide the card up and fade in */
|
|
160
|
+
@keyframes slideUpFade {
|
|
161
|
+
0% { opacity: 0; transform: translateY(20px); }
|
|
162
|
+
100% { opacity: 1; transform: translateY(0); }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* 2. Gentle pulse for the logo */
|
|
166
|
+
@keyframes gentlePulse {
|
|
167
|
+
0% { transform: scale(1); }
|
|
168
|
+
50% { transform: scale(1.05); }
|
|
169
|
+
100% { transform: scale(1); }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* 3. Pop in effect for status badges */
|
|
173
|
+
@keyframes popIn {
|
|
174
|
+
0% { opacity: 0; transform: scale(0.5); }
|
|
175
|
+
80% { transform: scale(1.1); }
|
|
176
|
+
100% { opacity: 1; transform: scale(1); }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* CLASSES TO APPLY ANIMATIONS */
|
|
180
|
+
.anim-card {
|
|
181
|
+
animation: slideUpFade 0.8s ease-out forwards;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.anim-logo {
|
|
185
|
+
animation: gentlePulse 3s infinite ease-in-out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Staggered delays for list items so they cascade in */
|
|
189
|
+
.anim-row-1 { animation: slideUpFade 0.5s ease-out 0.2s backwards; }
|
|
190
|
+
.anim-row-2 { animation: slideUpFade 0.5s ease-out 0.3s backwards; }
|
|
191
|
+
.anim-row-3 { animation: slideUpFade 0.5s ease-out 0.4s backwards; }
|
|
192
|
+
.anim-row-4 { animation: slideUpFade 0.5s ease-out 0.5s backwards; }
|
|
193
|
+
|
|
194
|
+
.anim-badge {
|
|
195
|
+
animation: popIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.6s backwards;
|
|
196
|
+
}
|
|
197
|
+
</style>
|
|
198
|
+
</head>
|
|
199
|
+
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
|
|
200
|
+
|
|
201
|
+
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #f3f4f6; padding: 40px 0;">
|
|
202
|
+
<tr>
|
|
203
|
+
<td align="center">
|
|
204
|
+
|
|
205
|
+
<table class="anim-card" role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="max-width: 600px; background-color: #ffffff; border-radius: 12px; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); overflow: hidden;">
|
|
206
|
+
|
|
207
|
+
<tr>
|
|
208
|
+
<td height="6" style="background-color: #4f46e5;"></td>
|
|
209
|
+
</tr>
|
|
210
|
+
|
|
211
|
+
<tr>
|
|
212
|
+
<td style="padding: 32px 32px 20px 32px;">
|
|
213
|
+
<table border="0" cellspacing="0" cellpadding="0" width="100%">
|
|
214
|
+
<tr>
|
|
215
|
+
<td width="55" style="vertical-align: middle; padding-right: 16px;">
|
|
216
|
+
<img class="anim-logo" src="https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/playwright_pulse_icon.png" alt="Report Logo" height="40" style="display: block; border: 0; border-radius: 8px;">
|
|
217
|
+
</td>
|
|
218
|
+
<td style="vertical-align: middle;">
|
|
219
|
+
<h1 style="margin: 0; font-size: 24px; font-weight: 700; color: #111827;">${projectName}</h1>
|
|
220
|
+
<p style="margin: 4px 0 0 0; font-size: 14px; color: #6b7280;">Automated Execution Report</p>
|
|
221
|
+
</td>
|
|
222
|
+
</tr>
|
|
223
|
+
</table>
|
|
224
|
+
</td>
|
|
225
|
+
</tr>
|
|
226
|
+
|
|
227
|
+
<tr>
|
|
228
|
+
<td style="padding: 0 32px 20px 32px;">
|
|
229
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #f9fafb; border-radius: 8px; border: 1px solid #e5e7eb;">
|
|
230
|
+
<tr>
|
|
231
|
+
<td style="padding: 16px; border-right: 1px solid #e5e7eb; width: 50%;">
|
|
232
|
+
<p style="margin: 0 0 4px 0; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #9ca3af; font-weight: 600;">Start Time</p>
|
|
233
|
+
<p style="margin: 0; font-size: 14px; color: #374151; font-weight: 500;">${startTime}</p>
|
|
234
|
+
</td>
|
|
235
|
+
<td style="padding: 16px; width: 50%;">
|
|
236
|
+
<p style="margin: 0 0 4px 0; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #9ca3af; font-weight: 600;">Duration</p>
|
|
237
|
+
<p style="margin: 0; font-size: 14px; color: #374151; font-weight: 500;">${durationString}</p>
|
|
238
|
+
</td>
|
|
239
|
+
</tr>
|
|
240
|
+
</table>
|
|
241
|
+
</td>
|
|
242
|
+
</tr>
|
|
243
|
+
|
|
244
|
+
<tr>
|
|
245
|
+
<td style="padding: 0 32px 32px 32px;">
|
|
246
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
|
247
|
+
|
|
248
|
+
<tr class="anim-row-1">
|
|
249
|
+
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px; color: #4b5563;">Total Tests Executed</td>
|
|
250
|
+
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; text-align: right; font-size: 14px; font-weight: 600; color: #111827;">${total}</td>
|
|
251
|
+
</tr>
|
|
252
|
+
|
|
253
|
+
<tr class="anim-row-2">
|
|
254
|
+
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px; color: #4b5563;">Tests Passed</td>
|
|
255
|
+
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; text-align: right;">
|
|
256
|
+
<span class="anim-badge" style="background-color: #d1fae5; color: #065f46; padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; display: inline-block; white-space: nowrap;">
|
|
257
|
+
${passedTests} (${passedPercentage}%)
|
|
258
|
+
</span>
|
|
259
|
+
</td>
|
|
260
|
+
</tr>
|
|
261
|
+
|
|
262
|
+
<tr class="anim-row-3">
|
|
263
|
+
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px; color: #4b5563;">Tests Failed</td>
|
|
264
|
+
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; text-align: right;">
|
|
265
|
+
<span class="anim-badge" style="background-color: #f3f4f6; color: #991b1b; padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; display: inline-block; white-space: nowrap;">
|
|
266
|
+
${failedTests} (${failedPercentage}%)
|
|
267
|
+
</span>
|
|
268
|
+
</td>
|
|
269
|
+
</tr>
|
|
270
|
+
|
|
271
|
+
<tr class="anim-row-4">
|
|
272
|
+
<td style="padding: 12px 0; font-size: 14px; color: #4b5563;">Tests Skipped</td>
|
|
273
|
+
<td style="padding: 12px 0; text-align: right;">
|
|
274
|
+
<span class="anim-badge" style="background-color: #fef3c7; color: #92400e; padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; display: inline-block; white-space: nowrap;">
|
|
275
|
+
${skippedTests} (${skippedPercentage}%)
|
|
276
|
+
</span>
|
|
277
|
+
</td>
|
|
278
|
+
</tr>
|
|
279
|
+
|
|
280
|
+
</table>
|
|
281
|
+
</td>
|
|
282
|
+
</tr>
|
|
283
|
+
|
|
284
|
+
<tr>
|
|
285
|
+
<td style="background-color: #f9fafb; padding: 20px; text-align: center; border-top: 1px solid #e5e7eb;">
|
|
286
|
+
<p style="margin: 0; font-size: 12px; color: #9ca3af;">Generated by Pulse Report</p>
|
|
287
|
+
</td>
|
|
288
|
+
</tr>
|
|
289
|
+
|
|
290
|
+
</table>
|
|
291
|
+
</td>
|
|
292
|
+
</tr>
|
|
293
|
+
</table>
|
|
294
|
+
</body>
|
|
295
|
+
</html>
|
|
296
|
+
`;
|
|
212
297
|
};
|
|
213
298
|
|
|
214
299
|
const __filename = fileURLToPath(import.meta.url);
|