@arghajit/dummy 0.1.0-beta-12 → 0.1.0-beta-14
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/README.md +32 -18
- package/dist/reporter/playwright-pulse-reporter.d.ts +1 -0
- package/dist/reporter/playwright-pulse-reporter.js +35 -0
- package/dist/types/index.d.ts +17 -0
- package/package.json +10 -7
- package/scripts/generate-email-report.mjs +576 -0
- package/scripts/generate-static-report.mjs +603 -991
- package/scripts/{sendReport.js → sendReport.mjs} +138 -71
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import nodemailer from "nodemailer"; // CHANGED
|
|
3
|
+
import path from "path"; // CHANGED (already was, but good to be explicit)
|
|
4
|
+
import archiver from "archiver"; // CHANGED
|
|
5
|
+
import {
|
|
6
|
+
createWriteStream,
|
|
7
|
+
readFileSync as fsReadFileSync, // Renamed to avoid conflict if fs from fs/promises is used
|
|
8
|
+
existsSync as fsExistsSync, // Renamed
|
|
9
|
+
} from "fs"; // CHANGED for specific functions
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { fork } from "child_process"; // This was missing in your sendReport.js but present in generate-email-report.js and needed for runScript
|
|
12
|
+
import "dotenv/config"; // CHANGED for dotenv
|
|
13
|
+
|
|
14
|
+
// Import chalk using top-level await if your Node version supports it (14.8+)
|
|
15
|
+
// or keep the dynamic import if preferred, but ensure chalk is resolved before use.
|
|
16
|
+
let chalk;
|
|
17
|
+
try {
|
|
18
|
+
chalk = (await import("chalk")).default;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.warn("Chalk could not be imported. Using plain console logs.");
|
|
21
|
+
chalk = {
|
|
22
|
+
green: (text) => text,
|
|
23
|
+
red: (text) => text,
|
|
24
|
+
yellow: (text) => text,
|
|
25
|
+
blue: (text) => text,
|
|
26
|
+
bold: (text) => text,
|
|
27
|
+
gray: (text) => text,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
7
30
|
|
|
8
|
-
|
|
31
|
+
const reportDir = "./pulse-report";
|
|
9
32
|
|
|
10
33
|
let fetch;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
process.exit(1);
|
|
18
|
-
});
|
|
34
|
+
// Ensure fetch is imported and available before it's used in fetchCredentials
|
|
35
|
+
// Using a top-level import is generally cleaner:
|
|
36
|
+
// import fetch from 'node-fetch';
|
|
37
|
+
// However, your dynamic import pattern is also fine if `fetch` is awaited properly.
|
|
38
|
+
// For simplicity, I'll assume the dynamic import is handled and awaited before fetchCredentials is called.
|
|
39
|
+
// The existing dynamic import for fetch is okay.
|
|
19
40
|
|
|
20
41
|
let projectName;
|
|
21
42
|
|
|
@@ -26,11 +47,12 @@ function getUUID() {
|
|
|
26
47
|
);
|
|
27
48
|
console.log("Report path:", reportPath);
|
|
28
49
|
|
|
29
|
-
if (!
|
|
50
|
+
if (!fsExistsSync(reportPath)) {
|
|
51
|
+
// CHANGED
|
|
30
52
|
throw new Error("Pulse report file not found.");
|
|
31
53
|
}
|
|
32
54
|
|
|
33
|
-
const content = JSON.parse(
|
|
55
|
+
const content = JSON.parse(fsReadFileSync(reportPath, "utf-8")); // CHANGED
|
|
34
56
|
const idString = content.run.id;
|
|
35
57
|
const parts = idString.split("-");
|
|
36
58
|
const uuid = parts.slice(-5).join("-");
|
|
@@ -49,25 +71,25 @@ const formatStartTime = (isoString) => {
|
|
|
49
71
|
return date.toLocaleString(); // Default locale
|
|
50
72
|
};
|
|
51
73
|
|
|
52
|
-
// Generate test-data from allure report
|
|
53
74
|
const getPulseReportSummary = () => {
|
|
54
75
|
const reportPath = path.join(
|
|
55
76
|
process.cwd(),
|
|
56
77
|
`${reportDir}/playwright-pulse-report.json`
|
|
57
78
|
);
|
|
58
79
|
|
|
59
|
-
if (!
|
|
80
|
+
if (!fsExistsSync(reportPath)) {
|
|
81
|
+
// CHANGED
|
|
60
82
|
throw new Error("Pulse report file not found.");
|
|
61
83
|
}
|
|
62
84
|
|
|
63
|
-
const content = JSON.parse(
|
|
85
|
+
const content = JSON.parse(fsReadFileSync(reportPath, "utf-8")); // CHANGED
|
|
64
86
|
const run = content.run;
|
|
65
87
|
|
|
66
88
|
const total = run.totalTests || 0;
|
|
67
89
|
const passed = run.passed || 0;
|
|
68
90
|
const failed = run.failed || 0;
|
|
69
91
|
const skipped = run.skipped || 0;
|
|
70
|
-
const
|
|
92
|
+
const durationInMs = run.duration || 0; // Keep in ms for formatDuration
|
|
71
93
|
|
|
72
94
|
const readableStartTime = new Date(run.timestamp).toLocaleString();
|
|
73
95
|
|
|
@@ -80,37 +102,35 @@ const getPulseReportSummary = () => {
|
|
|
80
102
|
failedPercentage: total ? ((failed / total) * 100).toFixed(2) : "0.00",
|
|
81
103
|
skippedPercentage: total ? ((skipped / total) * 100).toFixed(2) : "0.00",
|
|
82
104
|
startTime: readableStartTime,
|
|
83
|
-
duration: formatDuration(
|
|
105
|
+
duration: formatDuration(durationInMs), // Pass ms to formatDuration
|
|
84
106
|
};
|
|
85
107
|
};
|
|
86
108
|
|
|
87
|
-
// sleep function for javascript file
|
|
88
109
|
const delay = (time) => new Promise((resolve) => setTimeout(resolve, time));
|
|
89
|
-
|
|
110
|
+
|
|
90
111
|
const zipFolder = async (folderPath, zipPath) => {
|
|
91
112
|
return new Promise((resolve, reject) => {
|
|
92
|
-
const output =
|
|
93
|
-
const
|
|
113
|
+
const output = createWriteStream(zipPath); // CHANGED
|
|
114
|
+
const archiveInstance = archiver("zip", { zlib: { level: 9 } }); // Renamed to avoid conflict
|
|
94
115
|
|
|
95
116
|
output.on("close", () => {
|
|
96
|
-
console.log(`${
|
|
117
|
+
console.log(`${archiveInstance.pointer()} total bytes`);
|
|
97
118
|
console.log("Folder has been zipped successfully.");
|
|
98
|
-
resolve();
|
|
119
|
+
resolve();
|
|
99
120
|
});
|
|
100
121
|
|
|
101
|
-
|
|
102
|
-
reject(err);
|
|
122
|
+
archiveInstance.on("error", (err) => {
|
|
123
|
+
reject(err);
|
|
103
124
|
});
|
|
104
125
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
126
|
+
archiveInstance.pipe(output);
|
|
127
|
+
archiveInstance.directory(folderPath, false);
|
|
128
|
+
archiveInstance.finalize();
|
|
108
129
|
});
|
|
109
130
|
};
|
|
110
131
|
|
|
111
|
-
// Function to convert JSON data to HTML table format
|
|
112
132
|
const generateHtmlTable = (data) => {
|
|
113
|
-
projectName = "Pulse Emailable Report";
|
|
133
|
+
projectName = "Pulse Emailable Report"; // Consider passing projectName as an arg or making it a const
|
|
114
134
|
const stats = data;
|
|
115
135
|
const total = stats.passed + stats.failed + stats.skipped;
|
|
116
136
|
const passedTests = stats.passed;
|
|
@@ -120,7 +140,7 @@ const generateHtmlTable = (data) => {
|
|
|
120
140
|
const skippedTests = stats.skipped;
|
|
121
141
|
const skippedPercentage = stats.skippedPercentage;
|
|
122
142
|
const startTime = stats.startTime;
|
|
123
|
-
const
|
|
143
|
+
const durationString = stats.duration; // Already formatted string
|
|
124
144
|
|
|
125
145
|
return `
|
|
126
146
|
<!DOCTYPE html>
|
|
@@ -161,8 +181,8 @@ const generateHtmlTable = (data) => {
|
|
|
161
181
|
<td>${startTime}</td>
|
|
162
182
|
</tr>
|
|
163
183
|
<tr>
|
|
164
|
-
<td>Test Run Duration
|
|
165
|
-
<td>${
|
|
184
|
+
<td>Test Run Duration</td>
|
|
185
|
+
<td>${durationString}</td>
|
|
166
186
|
</tr>
|
|
167
187
|
<tr>
|
|
168
188
|
<td>Total Tests Count</td>
|
|
@@ -183,32 +203,65 @@ const generateHtmlTable = (data) => {
|
|
|
183
203
|
</tbody>
|
|
184
204
|
</table>
|
|
185
205
|
<p>With regards,</p>
|
|
186
|
-
<p>
|
|
206
|
+
<p>QA / SDET</p>
|
|
187
207
|
</body>
|
|
188
208
|
</html>
|
|
189
209
|
`;
|
|
190
210
|
};
|
|
191
211
|
|
|
192
|
-
|
|
212
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
213
|
+
const __dirname = path.dirname(__filename);
|
|
214
|
+
|
|
215
|
+
// Ensure the name here matches the actual file name of input_file_0.js
|
|
216
|
+
// If input_file_0.js is indeed the script, use that name.
|
|
217
|
+
// Using .mjs extension explicitly tells Node to treat it as ESM.
|
|
218
|
+
const archiveRunScriptPath = path.resolve(
|
|
219
|
+
__dirname,
|
|
220
|
+
"generate-email-report.mjs" // Or input_file_0.mjs if you rename it, or input_file_0.js if you configure package.json
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
async function runScript(scriptPath) {
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
const childProcess = fork(scriptPath, [], {
|
|
226
|
+
// Renamed variable
|
|
227
|
+
stdio: "inherit",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
childProcess.on("error", (err) => {
|
|
231
|
+
console.error(chalk.red(`Failed to start script: ${scriptPath}`), err);
|
|
232
|
+
reject(err);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
childProcess.on("exit", (code) => {
|
|
236
|
+
if (code === 0) {
|
|
237
|
+
resolve();
|
|
238
|
+
} else {
|
|
239
|
+
const errorMessage = `Script ${scriptPath} exited with code ${code}.`;
|
|
240
|
+
console.error(chalk.red(errorMessage));
|
|
241
|
+
reject(new Error(errorMessage));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
193
247
|
const sendEmail = async (credentials) => {
|
|
248
|
+
await runScript(archiveRunScriptPath);
|
|
194
249
|
try {
|
|
195
250
|
console.log("Starting the sendEmail function...");
|
|
196
251
|
|
|
197
|
-
// Configure nodemailer transporter
|
|
198
252
|
const secureTransporter = nodemailer.createTransport({
|
|
199
253
|
host: "smtp.gmail.com",
|
|
200
254
|
port: 465,
|
|
201
|
-
secure: true,
|
|
255
|
+
secure: true,
|
|
202
256
|
auth: {
|
|
203
257
|
user: credentials.username,
|
|
204
|
-
pass: credentials.password,
|
|
258
|
+
pass: credentials.password,
|
|
205
259
|
},
|
|
206
260
|
});
|
|
207
|
-
|
|
261
|
+
|
|
208
262
|
const reportData = getPulseReportSummary();
|
|
209
263
|
const htmlContent = generateHtmlTable(reportData);
|
|
210
264
|
|
|
211
|
-
// Configure mail options
|
|
212
265
|
const mailOptions = {
|
|
213
266
|
from: credentials.username,
|
|
214
267
|
to: [
|
|
@@ -217,18 +270,18 @@ const sendEmail = async (credentials) => {
|
|
|
217
270
|
process.env.SENDER_EMAIL_3 || "",
|
|
218
271
|
process.env.SENDER_EMAIL_4 || "",
|
|
219
272
|
process.env.SENDER_EMAIL_5 || "",
|
|
220
|
-
],
|
|
273
|
+
].filter((email) => email), // Filter out empty strings
|
|
221
274
|
subject: "Pulse Report " + new Date().toLocaleString(),
|
|
222
275
|
html: htmlContent,
|
|
223
276
|
attachments: [
|
|
224
277
|
{
|
|
225
278
|
filename: `report.html`,
|
|
226
|
-
path
|
|
279
|
+
// Make sure this path is correct and the file is generated by archiveRunScriptPath
|
|
280
|
+
path: path.join(reportDir, "pulse-email-summary.html"),
|
|
227
281
|
},
|
|
228
282
|
],
|
|
229
283
|
};
|
|
230
284
|
|
|
231
|
-
// Send email
|
|
232
285
|
const info = await secureTransporter.sendMail(mailOptions);
|
|
233
286
|
console.log("Email sent: ", info.response);
|
|
234
287
|
} catch (error) {
|
|
@@ -237,29 +290,39 @@ const sendEmail = async (credentials) => {
|
|
|
237
290
|
};
|
|
238
291
|
|
|
239
292
|
async function fetchCredentials(retries = 6) {
|
|
240
|
-
|
|
293
|
+
// Ensure fetch is initialized from the dynamic import before calling this
|
|
294
|
+
if (!fetch) {
|
|
295
|
+
try {
|
|
296
|
+
fetch = (await import("node-fetch")).default;
|
|
297
|
+
} catch (err) {
|
|
298
|
+
console.error(
|
|
299
|
+
"Failed to import node-fetch dynamically for fetchCredentials:",
|
|
300
|
+
err
|
|
301
|
+
);
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const timeout = 10000;
|
|
241
307
|
const key = getUUID();
|
|
242
|
-
|
|
308
|
+
|
|
243
309
|
if (!key) {
|
|
244
310
|
console.error(
|
|
245
|
-
"🔴 Critical: API key
|
|
311
|
+
"🔴 Critical: API key (UUID from report) not found or invalid."
|
|
246
312
|
);
|
|
247
|
-
|
|
248
|
-
return null; // Return null instead of throwing
|
|
313
|
+
return null;
|
|
249
314
|
}
|
|
250
315
|
|
|
251
316
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
252
317
|
try {
|
|
253
|
-
console.log(`🟡 Attempt ${attempt} of ${retries}`);
|
|
318
|
+
console.log(`🟡 Attempt ${attempt} of ${retries} to fetch credentials`);
|
|
254
319
|
|
|
255
|
-
// Create a timeout promise
|
|
256
320
|
const timeoutPromise = new Promise((_, reject) => {
|
|
257
321
|
setTimeout(() => {
|
|
258
322
|
reject(new Error(`Request timed out after ${timeout}ms`));
|
|
259
323
|
}, timeout);
|
|
260
324
|
});
|
|
261
325
|
|
|
262
|
-
// Create the fetch promise
|
|
263
326
|
const fetchPromise = fetch(
|
|
264
327
|
"https://test-dashboard-66zd.onrender.com/api/getcredentials",
|
|
265
328
|
{
|
|
@@ -270,11 +333,9 @@ async function fetchCredentials(retries = 6) {
|
|
|
270
333
|
}
|
|
271
334
|
);
|
|
272
335
|
|
|
273
|
-
// Race between fetch and timeout
|
|
274
336
|
const response = await Promise.race([fetchPromise, timeoutPromise]);
|
|
275
337
|
|
|
276
338
|
if (!response.ok) {
|
|
277
|
-
// Handle specific HTTP errors with console messages only
|
|
278
339
|
if (response.status === 401) {
|
|
279
340
|
console.error("🔴 Invalid API key - authentication failed");
|
|
280
341
|
} else if (response.status === 404) {
|
|
@@ -282,14 +343,17 @@ async function fetchCredentials(retries = 6) {
|
|
|
282
343
|
} else {
|
|
283
344
|
console.error(`🔴 Fetch failed with status: ${response.status}`);
|
|
284
345
|
}
|
|
285
|
-
|
|
346
|
+
if (attempt < retries)
|
|
347
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
348
|
+
continue;
|
|
286
349
|
}
|
|
287
350
|
|
|
288
351
|
const data = await response.json();
|
|
289
352
|
|
|
290
|
-
// Validate the response structure
|
|
291
353
|
if (!data.username || !data.password) {
|
|
292
354
|
console.error("🔴 Invalid credentials format received from API");
|
|
355
|
+
if (attempt < retries)
|
|
356
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
293
357
|
continue;
|
|
294
358
|
}
|
|
295
359
|
|
|
@@ -297,34 +361,37 @@ async function fetchCredentials(retries = 6) {
|
|
|
297
361
|
return data;
|
|
298
362
|
} catch (err) {
|
|
299
363
|
console.error(`🔴 Attempt ${attempt} failed: ${err.message}`);
|
|
300
|
-
|
|
301
364
|
if (attempt === retries) {
|
|
302
365
|
console.error(
|
|
303
366
|
`🔴 All ${retries} attempts failed. Last error: ${err.message}`
|
|
304
367
|
);
|
|
305
|
-
console.warn(
|
|
306
|
-
"🟠 Proceeding without credentials - email sending will be skipped"
|
|
307
|
-
);
|
|
308
368
|
return null;
|
|
309
369
|
}
|
|
310
|
-
|
|
311
370
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
312
371
|
}
|
|
313
372
|
}
|
|
373
|
+
return null; // Should be unreachable if loop logic is correct
|
|
314
374
|
}
|
|
315
375
|
|
|
316
|
-
// Main function to zip the folder and send the email
|
|
317
376
|
const main = async () => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
377
|
+
// Ensure fetch is initialized (dynamic import at top or here)
|
|
378
|
+
if (!fetch) {
|
|
379
|
+
try {
|
|
380
|
+
fetch = (await import("node-fetch")).default;
|
|
381
|
+
} catch (err) {
|
|
382
|
+
console.error("Failed to import node-fetch at start of main:", err);
|
|
383
|
+
process.exit(1); // Or handle error appropriately
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
321
387
|
const credentials = await fetchCredentials();
|
|
322
388
|
if (!credentials) {
|
|
323
|
-
console.warn(
|
|
324
|
-
|
|
389
|
+
console.warn(
|
|
390
|
+
"Skipping email sending due to missing or failed credential fetch"
|
|
391
|
+
);
|
|
325
392
|
return;
|
|
326
393
|
}
|
|
327
|
-
await delay(10000);
|
|
394
|
+
// Removed await delay(10000); // If not strictly needed, remove it.
|
|
328
395
|
try {
|
|
329
396
|
await sendEmail(credentials);
|
|
330
397
|
} catch (error) {
|