@arghajit/dummy 0.3.33 → 0.3.36
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 +4 -2
- package/dist/reporter/playwright-pulse-reporter.js +31 -20
- package/dist/reporter/tsconfig.reporter.tsbuildinfo +1 -0
- package/dist/types/index.d.ts +0 -7
- package/dist/utils/compression-utils.js +12 -8
- package/package.json +14 -10
- package/scripts/generate-email-report.mjs +3 -1
- package/scripts/generate-report.mjs +29 -4
- package/scripts/generate-static-report.mjs +29 -4
- package/scripts/sendReport.mjs +110 -196
package/README.md
CHANGED
|
@@ -245,11 +245,13 @@ jobs:
|
|
|
245
245
|
run: npx playwright install --with-deps
|
|
246
246
|
- name: Run Playwright tests
|
|
247
247
|
run: npm run test
|
|
248
|
+
- name: Generate Pulse Report
|
|
249
|
+
run: npx generate-report
|
|
248
250
|
- uses: actions/upload-artifact@v4
|
|
249
251
|
if: always()
|
|
250
252
|
with:
|
|
251
|
-
name:
|
|
252
|
-
path:
|
|
253
|
+
name: pulse-report
|
|
254
|
+
path: pulse-report/
|
|
253
255
|
retention-days: 30
|
|
254
256
|
```
|
|
255
257
|
|
|
@@ -229,6 +229,7 @@ class PlaywrightPulseReporter {
|
|
|
229
229
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
230
230
|
const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
|
|
231
231
|
const browserDetails = this.getBrowserDetails(test);
|
|
232
|
+
const uniqueTestId = `${(project === null || project === void 0 ? void 0 : project.name) || "default"}-${test.id}`;
|
|
232
233
|
// Captured outcome from Playwright
|
|
233
234
|
const outcome = test.outcome();
|
|
234
235
|
// Calculate final status based on the last result (Last-Run-Wins)
|
|
@@ -247,7 +248,7 @@ class PlaywrightPulseReporter {
|
|
|
247
248
|
const processAllSteps = async (steps) => {
|
|
248
249
|
let processed = [];
|
|
249
250
|
for (const step of steps) {
|
|
250
|
-
const processedStep = await this.processStep(step,
|
|
251
|
+
const processedStep = await this.processStep(step, uniqueTestId, browserDetails, test);
|
|
251
252
|
processed.push(processedStep);
|
|
252
253
|
if (step.steps && step.steps.length > 0) {
|
|
253
254
|
processedStep.steps = await processAllSteps(step.steps);
|
|
@@ -284,14 +285,14 @@ class PlaywrightPulseReporter {
|
|
|
284
285
|
: undefined,
|
|
285
286
|
};
|
|
286
287
|
const pulseResult = {
|
|
287
|
-
id:
|
|
288
|
+
id: uniqueTestId,
|
|
288
289
|
runId: "TBD",
|
|
289
290
|
describe: describeBlockName,
|
|
290
291
|
spec_file: specFileName,
|
|
291
292
|
name: test.titlePath().join(" > "),
|
|
292
293
|
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",
|
|
293
294
|
status: testStatus,
|
|
294
|
-
outcome: outcome ===
|
|
295
|
+
outcome: outcome === "flaky" ? outcome : undefined, // Only Include if flaky
|
|
295
296
|
final_status: finalStatus, // New Field
|
|
296
297
|
duration: result.duration,
|
|
297
298
|
startTime: startTime,
|
|
@@ -318,7 +319,7 @@ class PlaywrightPulseReporter {
|
|
|
318
319
|
if (!attachment.path)
|
|
319
320
|
continue;
|
|
320
321
|
try {
|
|
321
|
-
const testSubfolder =
|
|
322
|
+
const testSubfolder = uniqueTestId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
322
323
|
const safeAttachmentName = path
|
|
323
324
|
.basename(attachment.path)
|
|
324
325
|
.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
@@ -363,21 +364,30 @@ class PlaywrightPulseReporter {
|
|
|
363
364
|
}
|
|
364
365
|
const finalResults = [];
|
|
365
366
|
for (const [testId, attempts] of resultsMap.entries()) {
|
|
366
|
-
|
|
367
|
+
// Sort by retry count (ASC) then timestamp (DESC) to ensure stable resolution
|
|
368
|
+
attempts.sort((a, b) => {
|
|
369
|
+
if (a.retries !== b.retries)
|
|
370
|
+
return a.retries - b.retries;
|
|
371
|
+
return (new Date(b.startTime).getTime() - new Date(a.startTime).getTime());
|
|
372
|
+
});
|
|
367
373
|
const firstAttempt = attempts[0];
|
|
368
374
|
const retryAttempts = attempts.slice(1);
|
|
369
375
|
// Only populate retryHistory if there were actual failures that triggered retries
|
|
370
376
|
// If all attempts passed, we don't need to show retry history
|
|
371
|
-
const hasActualRetries = retryAttempts.length > 0 &&
|
|
377
|
+
const hasActualRetries = retryAttempts.length > 0 &&
|
|
378
|
+
retryAttempts.some((attempt) => attempt.status === "failed" ||
|
|
379
|
+
attempt.status === "flaky" ||
|
|
380
|
+
firstAttempt.status === "failed" ||
|
|
381
|
+
firstAttempt.status === "flaky");
|
|
372
382
|
if (hasActualRetries) {
|
|
373
383
|
firstAttempt.retryHistory = retryAttempts;
|
|
374
384
|
// Calculate final status and outcome from the last attempt if retries exist
|
|
375
385
|
const lastAttempt = attempts[attempts.length - 1];
|
|
376
386
|
firstAttempt.final_status = lastAttempt.status;
|
|
377
387
|
// If the last attempt was flaky, ensure outcome is set on the main result
|
|
378
|
-
if (lastAttempt.outcome ===
|
|
379
|
-
firstAttempt.outcome =
|
|
380
|
-
firstAttempt.status =
|
|
388
|
+
if (lastAttempt.outcome === "flaky" || lastAttempt.status === "flaky") {
|
|
389
|
+
firstAttempt.outcome = "flaky";
|
|
390
|
+
firstAttempt.status = "flaky";
|
|
381
391
|
}
|
|
382
392
|
}
|
|
383
393
|
else {
|
|
@@ -444,10 +454,10 @@ class PlaywrightPulseReporter {
|
|
|
444
454
|
}
|
|
445
455
|
const finalResultsList = this._getFinalizedResults(allShardProcessedResults);
|
|
446
456
|
finalResultsList.forEach((r) => (r.runId = finalRunData.id));
|
|
447
|
-
finalRunData.passed = finalResultsList.filter((r) => r.status === "passed").length;
|
|
448
|
-
finalRunData.failed = finalResultsList.filter((r) => r.status === "failed").length;
|
|
449
|
-
finalRunData.skipped = finalResultsList.filter((r) => r.status === "skipped").length;
|
|
450
|
-
finalRunData.flaky = finalResultsList.filter((r) => r.status === "flaky").length;
|
|
457
|
+
finalRunData.passed = finalResultsList.filter((r) => (r.final_status || r.status) === "passed").length;
|
|
458
|
+
finalRunData.failed = finalResultsList.filter((r) => (r.final_status || r.status) === "failed").length;
|
|
459
|
+
finalRunData.skipped = finalResultsList.filter((r) => (r.final_status || r.status) === "skipped").length;
|
|
460
|
+
finalRunData.flaky = finalResultsList.filter((r) => (r.final_status || r.status) === "flaky").length;
|
|
451
461
|
finalRunData.totalTests = finalResultsList.length;
|
|
452
462
|
const reviveDates = (key, value) => {
|
|
453
463
|
const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;
|
|
@@ -505,10 +515,10 @@ class PlaywrightPulseReporter {
|
|
|
505
515
|
timestamp: new Date(this.runStartTime),
|
|
506
516
|
// Use the length of the de-duplicated array for all counts
|
|
507
517
|
totalTests: finalResults.length,
|
|
508
|
-
passed: finalResults.filter((r) => r.status === "passed").length,
|
|
509
|
-
failed: finalResults.filter((r) => r.status === "failed").length,
|
|
510
|
-
skipped: finalResults.filter((r) => r.status === "skipped").length,
|
|
511
|
-
flaky: finalResults.filter((r) => r.status === "flaky").length,
|
|
518
|
+
passed: finalResults.filter((r) => (r.final_status || r.status) === "passed").length,
|
|
519
|
+
failed: finalResults.filter((r) => (r.final_status || r.status) === "failed").length,
|
|
520
|
+
skipped: finalResults.filter((r) => (r.final_status || r.status) === "skipped").length,
|
|
521
|
+
flaky: finalResults.filter((r) => (r.final_status || r.status) === "flaky").length,
|
|
512
522
|
duration,
|
|
513
523
|
environment: environmentDetails,
|
|
514
524
|
};
|
|
@@ -628,9 +638,10 @@ class PlaywrightPulseReporter {
|
|
|
628
638
|
environment: lastRunEnvironment,
|
|
629
639
|
// Recalculate counts based on the truly final, de-duplicated list
|
|
630
640
|
totalTests: finalMergedResults.length,
|
|
631
|
-
passed: finalMergedResults.filter((r) => r.status === "passed").length,
|
|
632
|
-
failed: finalMergedResults.filter((r) => r.status === "failed").length,
|
|
633
|
-
skipped: finalMergedResults.filter((r) => r.status === "skipped").length,
|
|
641
|
+
passed: finalMergedResults.filter((r) => (r.final_status || r.status) === "passed").length,
|
|
642
|
+
failed: finalMergedResults.filter((r) => (r.final_status || r.status) === "failed").length,
|
|
643
|
+
skipped: finalMergedResults.filter((r) => (r.final_status || r.status) === "skipped").length,
|
|
644
|
+
flaky: finalMergedResults.filter((r) => (r.final_status || r.status) === "flaky").length,
|
|
634
645
|
duration: totalDuration,
|
|
635
646
|
};
|
|
636
647
|
const finalReport = {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/playwright-core/types/protocol.d.ts","../../node_modules/playwright-core/types/structs.d.ts","../../node_modules/playwright-core/types/types.d.ts","../../node_modules/playwright/types/test.d.ts","../../node_modules/playwright/types/testreporter.d.ts","../../node_modules/@playwright/test/reporter.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/prop-types/index.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/lucide-react/dist/lucide-react.d.ts","../../src/types/index.ts","../../src/lib/report-types.ts","../../src/reporter/playwright-pulse-reporter.ts","../../node_modules/@types/caseless/index.d.ts","../../node_modules/@types/d3-array/index.d.ts","../../node_modules/@types/d3-color/index.d.ts","../../node_modules/@types/d3-ease/index.d.ts","../../node_modules/@types/d3-interpolate/index.d.ts","../../node_modules/@types/d3-path/index.d.ts","../../node_modules/@types/d3-time/index.d.ts","../../node_modules/@types/d3-scale/index.d.ts","../../node_modules/@types/d3-shape/index.d.ts","../../node_modules/@types/d3-timer/index.d.ts","../../node_modules/@types/json-schema/index.d.ts","../../node_modules/@types/long/index.d.ts","../../node_modules/@types/node/compatibility/disposable.d.ts","../../node_modules/@types/node/compatibility/indexable.d.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/compatibility/index.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/buffer/index.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/file.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/filereader.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/dom-events.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/@types/react-dom/index.d.ts","../../node_modules/@types/request/node_modules/form-data/index.d.ts","../../node_modules/@types/tough-cookie/index.d.ts","../../node_modules/@types/request/index.d.ts","../../node_modules/@types/shimmer/index.d.ts","../../node_modules/@types/triple-beam/index.d.ts","../../node_modules/@types/yauzl/index.d.ts"],"fileIdsList":[[83,110,153],[110,153],[95,110,153],[99,110,153],[98,110,153],[110,150,153],[110,152,153],[153],[110,153,158,187],[110,153,154,159,165,166,173,184,195],[110,153,154,155,165,173],[105,106,107,110,153],[110,153,156,196],[110,153,157,158,166,174],[110,153,158,184,192],[110,153,159,161,165,173],[110,152,153,160],[110,153,161,162],[110,153,165],[110,153,163,165],[110,152,153,165],[110,153,165,166,167,184,195],[110,153,165,166,167,180,184,187],[110,148,153,200],[110,153,161,165,168,173,184,195],[110,153,165,166,168,169,173,184,192,195],[110,153,168,170,184,192,195],[108,109,110,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],[110,153,165,171],[110,153,172,195,200],[110,153,161,165,173,184],[110,153,174],[110,153,175],[110,152,153,176],[110,150,151,152,153,154,155,156,157,158,159,160,161,162,163,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],[110,153,178],[110,153,179],[110,153,165,180,181],[110,153,180,182,196,198],[110,153,165,184,185,186,187],[110,153,184,186],[110,153,184,185],[110,153,187],[110,153,188],[110,150,153,184],[110,153,165,190,191],[110,153,190,191],[110,153,158,173,184,192],[110,153,193],[110,153,173,194],[110,153,168,179,195],[110,153,158,196],[110,153,184,197],[110,153,172,198],[110,153,199],[110,153,158,165,167,176,184,195,198,200],[110,153,184,201],[88,110,153],[85,86,87,110,153],[93,110,153,166,168,170,173,184,195,202,204,205],[110,153,168,184,202],[110,153,165,184,202],[81,110,153],[79,80,110,153,154,166,184],[82,110,153],[110,120,124,153,195],[110,120,153,184,195],[110,115,153],[110,117,120,153,192,195],[110,153,173,192],[110,153,202],[110,115,153,202],[110,117,120,153,173,195],[110,112,113,116,119,153,165,184,195],[110,120,127,153],[110,112,118,153],[110,120,141,142,153],[110,116,120,153,187,195,202],[110,141,153,202],[110,114,115,153,202],[110,120,153],[110,114,115,116,117,118,119,120,121,122,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,142,143,144,145,146,147,153],[110,120,135,153],[110,120,127,128,153],[110,118,120,128,129,153],[110,119,153],[110,112,115,120,153],[110,120,124,128,129,153],[110,124,153],[110,118,120,123,153,195],[110,112,117,120,127,153],[110,153,184],[110,115,120,141,153,200,202],[90,110,153],[84,90,91,110,153,167,175],[89,110,153]],"fileInfos":[{"version":"e41c290ef7dd7dab3493e6cbe5909e0148edf4a8dad0271be08edec368a0f7b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"27bdc30a0e32783366a5abeda841bc22757c1797de8681bbe81fbc735eeb1c10","impliedFormat":1},{"version":"8fd575e12870e9944c7e1d62e1f5a73fcf23dd8d3a321f2a2c74c20d022283fe","impliedFormat":1},{"version":"e12a46ce14b817d4c9e6b2b478956452330bf00c9801b79de46f7a1815b5bd40","impliedFormat":1},{"version":"4fd3f3422b2d2a3dfd5cdd0f387b3a8ec45f006c6ea896a4cb41264c2100bb2c","affectsGlobalScope":true,"impliedFormat":1},{"version":"69e65d976bf166ce4a9e6f6c18f94d2424bf116e90837ace179610dbccad9b42","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"62bb211266ee48b2d0edf0d8d1b191f0c24fc379a82bd4c1692a082c540bc6b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"936e80ad36a2ee83fc3caf008e7c4c5afe45b3cf3d5c24408f039c1d47bdc1df","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"fef8cfad2e2dc5f5b3d97a6f4f2e92848eb1b88e897bb7318cef0e2820bceaab","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"f1e2a172204962276504466a6393426d2ca9c54894b1ad0a6c9dad867a65f876","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"b5ce7a470bc3628408429040c4e3a53a27755022a32fd05e2cb694e7015386c7","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"bab26767638ab3557de12c900f0b91f710c7dc40ee9793d5a27d32c04f0bf646","affectsGlobalScope":true,"impliedFormat":1},{"version":"436aaf437562f276ec2ddbee2f2cdedac7664c1e4c1d2c36839ddd582eeb3d0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e3c06ea092138bf9fa5e874a1fdbc9d54805d074bee1de31b99a11e2fec239d","affectsGlobalScope":true,"impliedFormat":1},{"version":"87dc0f382502f5bbce5129bdc0aea21e19a3abbc19259e0b43ae038a9fc4e326","affectsGlobalScope":true,"impliedFormat":1},{"version":"b1cb28af0c891c8c96b2d6b7be76bd394fddcfdb4709a20ba05a7c1605eea0f9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2fef54945a13095fdb9b84f705f2b5994597640c46afeb2ce78352fab4cb3279","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac77cb3e8c6d3565793eb90a8373ee8033146315a3dbead3bde8db5eaf5e5ec6","affectsGlobalScope":true,"impliedFormat":1},{"version":"56e4ed5aab5f5920980066a9409bfaf53e6d21d3f8d020c17e4de584d29600ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ece9f17b3866cc077099c73f4983bddbcb1dc7ddb943227f1ec070f529dedd1","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a6282c8827e4b9a95f4bf4f5c205673ada31b982f50572d27103df8ceb8013c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1c9319a09485199c1f7b0498f2988d6d2249793ef67edda49d1e584746be9032","affectsGlobalScope":true,"impliedFormat":1},{"version":"e3a2a0cee0f03ffdde24d89660eba2685bfbdeae955a6c67e8c4c9fd28928eeb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811c71eee4aa0ac5f7adf713323a5c41b0cf6c4e17367a34fbce379e12bbf0a4","affectsGlobalScope":true,"impliedFormat":1},{"version":"51ad4c928303041605b4d7ae32e0c1ee387d43a24cd6f1ebf4a2699e1076d4fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"d4b1d2c51d058fc21ec2629fff7a76249dec2e36e12960ea056e3ef89174080f","affectsGlobalScope":true,"impliedFormat":1},{"version":"61d6a2092f48af66dbfb220e31eea8b10bc02b6932d6e529005fd2d7b3281290","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ef72620b8ed8555b1a571fba0573059b427c72c0f8fe0af12117c01deaa62aa","impliedFormat":1},{"version":"32727845ab5bd8a9ef3e4844c567c09f6d418fcf0f90d381c00652a6f23e7f6e","impliedFormat":1},{"version":"317522008ac4f9a976630cb65fd5b072d7aea6da0a93ec0cfe0c0b0336337ee2","impliedFormat":1},{"version":"e241a236efd22fd53f0cad74d812bece9bc1691bf3890e95705a6e3b73e2f98e","affectsGlobalScope":true,"impliedFormat":1},{"version":"d3cd566b0923109e83f1fe321c59266003ba22668d73195cf53e2eab3a71746f","impliedFormat":1},{"version":"04392f8e190f9e51301f73d17bbb34babd54858c1efc932d2193962f66dabae2","impliedFormat":1},{"version":"36a2e4c9a67439aca5f91bb304611d5ae6e20d420503e96c230cf8fcdc948d94","affectsGlobalScope":true,"impliedFormat":1},{"version":"8a8eb4ebffd85e589a1cc7c178e291626c359543403d58c9cd22b81fab5b1fb9","impliedFormat":1},{"version":"65ff5a0aefd7817a03c1ad04fee85c9cdd3ec415cc3c9efec85d8008d4d5e4ee","impliedFormat":1},{"version":"b89c2ddec6bd955e8721d41e24ca667de06882338d88b183c2cdc1f41f4c5a34","affectsGlobalScope":true,"impliedFormat":1},{"version":"6717dad91e44ad22d68f1fc0db74e5eb5398c2c06a2943bf06d3a168e8b1ba45","impliedFormat":99},{"version":"83aa54434d56363ddc949b3af4dd9eccd2fa9db2ecbae5c4a12b6984f66a630c","signature":"9f468e28df7bf40a346ac2cdd8b64e9f10cf2fbec522b58b92fef46b17c42720"},{"version":"331a971aff7a6da8693ab1ab38961996fecd854f6a639d430c81f8207dc92e96","signature":"14e0d6b6ab7688c7a2b60eb9c844d911175dd49a0e950b19863a5e09b8f9f6bd"},{"version":"c43cfd76af4b5b3dbe5d13d8f3e4ff975093fc952e444f1ad7d2263160c04b25","signature":"8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881"},{"version":"2174e20517788d2a1379fc0aaacd87899a70f9e0197b4295edabfe75c4db03d8","impliedFormat":1},{"version":"e0c868a08451c879984ccf4d4e3c1240b3be15af8988d230214977a3a3dad4ce","impliedFormat":1},{"version":"6fc1a4f64372593767a9b7b774e9b3b92bf04e8785c3f9ea98973aa9f4bbe490","impliedFormat":1},{"version":"ff09b6fbdcf74d8af4e131b8866925c5e18d225540b9b19ce9485ca93e574d84","impliedFormat":1},{"version":"d5895252efa27a50f134a9b580aa61f7def5ab73d0a8071f9b5bf9a317c01c2d","impliedFormat":1},{"version":"2c378d9368abcd2eba8c29b294d40909845f68557bc0b38117e4f04fc56e5f9c","impliedFormat":1},{"version":"56208c500dcb5f42be7e18e8cb578f257a1a89b94b3280c506818fed06391805","impliedFormat":1},{"version":"0c94c2e497e1b9bcfda66aea239d5d36cd980d12a6d9d59e66f4be1fa3da5d5a","impliedFormat":1},{"version":"bb220eaac1677e2ad82ac4e7fd3e609a0c7b6f2d6d9c673a35068c97f9fcd5cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"1f366bde16e0513fa7b64f87f86689c4d36efd85afce7eb24753e9c99b91c319","impliedFormat":1},{"version":"f3d8c757e148ad968f0d98697987db363070abada5f503da3c06aefd9d4248c1","impliedFormat":1},{"version":"0e60e0cbf2283adfd5a15430ae548cd2f662d581b5da6ecd98220203e7067c70","impliedFormat":1},{"version":"70521b6ab0dcba37539e5303104f29b721bfb2940b2776da4cc818c07e1fefc1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab41ef1f2cdafb8df48be20cd969d875602483859dc194e9c97c8a576892c052","affectsGlobalScope":true,"impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"21d819c173c0cf7cc3ce57c3276e77fd9a8a01d35a06ad87158781515c9a438a","impliedFormat":1},{"version":"a79e62f1e20467e11a904399b8b18b18c0c6eea6b50c1168bf215356d5bebfaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"0fd06258805d26c72f5997e07a23155d322d5f05387adb3744a791fe6a0b042d","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e9c23ba78aabc2e0a27033f18737a6df754067731e69dc5f52823957d60a4b6","impliedFormat":1},{"version":"5929864ce17fba74232584d90cb721a89b7ad277220627cc97054ba15a98ea8f","impliedFormat":1},{"version":"24bd580b5743dc56402c440dc7f9a4f5d592ad7a419f25414d37a7bfe11e342b","impliedFormat":1},{"version":"25c8056edf4314820382a5fdb4bb7816999acdcb929c8f75e3f39473b87e85bc","impliedFormat":1},{"version":"c464d66b20788266e5353b48dc4aa6bc0dc4a707276df1e7152ab0c9ae21fad8","impliedFormat":1},{"version":"78d0d27c130d35c60b5e5566c9f1e5be77caf39804636bc1a40133919a949f21","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"1d6e127068ea8e104a912e42fc0a110e2aa5a66a356a917a163e8cf9a65e4a75","impliedFormat":1},{"version":"5ded6427296cdf3b9542de4471d2aa8d3983671d4cac0f4bf9c637208d1ced43","impliedFormat":1},{"version":"6bdc71028db658243775263e93a7db2fd2abfce3ca569c3cca5aee6ed5eb186d","impliedFormat":1},{"version":"cadc8aced301244057c4e7e73fbcae534b0f5b12a37b150d80e5a45aa4bebcbd","impliedFormat":1},{"version":"385aab901643aa54e1c36f5ef3107913b10d1b5bb8cbcd933d4263b80a0d7f20","impliedFormat":1},{"version":"9670d44354bab9d9982eca21945686b5c24a3f893db73c0dae0fd74217a4c219","impliedFormat":1},{"version":"0b8a9268adaf4da35e7fa830c8981cfa22adbbe5b3f6f5ab91f6658899e657a7","impliedFormat":1},{"version":"11396ed8a44c02ab9798b7dca436009f866e8dae3c9c25e8c1fbc396880bf1bb","impliedFormat":1},{"version":"ba7bc87d01492633cb5a0e5da8a4a42a1c86270e7b3d2dea5d156828a84e4882","impliedFormat":1},{"version":"4893a895ea92c85345017a04ed427cbd6a1710453338df26881a6019432febdd","impliedFormat":1},{"version":"c21dc52e277bcfc75fac0436ccb75c204f9e1b3fa5e12729670910639f27343e","impliedFormat":1},{"version":"13f6f39e12b1518c6650bbb220c8985999020fe0f21d818e28f512b7771d00f9","impliedFormat":1},{"version":"9b5369969f6e7175740bf51223112ff209f94ba43ecd3bb09eefff9fd675624a","impliedFormat":1},{"version":"4fe9e626e7164748e8769bbf74b538e09607f07ed17c2f20af8d680ee49fc1da","impliedFormat":1},{"version":"24515859bc0b836719105bb6cc3d68255042a9f02a6022b3187948b204946bd2","impliedFormat":1},{"version":"ea0148f897b45a76544ae179784c95af1bd6721b8610af9ffa467a518a086a43","impliedFormat":1},{"version":"24c6a117721e606c9984335f71711877293a9651e44f59f3d21c1ea0856f9cc9","impliedFormat":1},{"version":"dd3273ead9fbde62a72949c97dbec2247ea08e0c6952e701a483d74ef92d6a17","impliedFormat":1},{"version":"405822be75ad3e4d162e07439bac80c6bcc6dbae1929e179cf467ec0b9ee4e2e","impliedFormat":1},{"version":"0db18c6e78ea846316c012478888f33c11ffadab9efd1cc8bcc12daded7a60b6","impliedFormat":1},{"version":"4d2b0eb911816f66abe4970898f97a2cfc902bcd743cbfa5017fad79f7ef90d8","impliedFormat":1},{"version":"bd0532fd6556073727d28da0edfd1736417a3f9f394877b6d5ef6ad88fba1d1a","impliedFormat":1},{"version":"89167d696a849fce5ca508032aabfe901c0868f833a8625d5a9c6e861ef935d2","impliedFormat":1},{"version":"e53a3c2a9f624d90f24bf4588aacd223e7bec1b9d0d479b68d2f4a9e6011147f","impliedFormat":1},{"version":"24b8685c62562f5d98615c5a0c1d05f297cf5065f15246edfe99e81ec4c0e011","impliedFormat":1},{"version":"93507c745e8f29090efb99399c3f77bec07db17acd75634249dc92f961573387","impliedFormat":1},{"version":"339dc5265ee5ed92e536a93a04c4ebbc2128f45eeec6ed29f379e0085283542c","impliedFormat":1},{"version":"4732aec92b20fb28c5fe9ad99521fb59974289ed1e45aecb282616202184064f","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"bf67d53d168abc1298888693338cb82854bdb2e69ef83f8a0092093c2d562107","impliedFormat":1},{"version":"08faa97886e71757779428dd4c69a545c32c85fd629d1116d42710b32c6378bc","affectsGlobalScope":true,"impliedFormat":1},{"version":"a72ffc815104fb5c075106ebca459b2d55d07862a773768fce89efc621b3964b","impliedFormat":1},{"version":"7394959e5a741b185456e1ef5d64599c36c60a323207450991e7a42e08911419","impliedFormat":1},{"version":"3d77c73be94570813f8cadd1f05ebc3dc5e2e4fdefe4d340ca20cd018724ee36","impliedFormat":1},{"version":"d674383111e06b6741c4ad2db962131b5b0fa4d0294b998566c635e86195a453","affectsGlobalScope":true,"impliedFormat":1},{"version":"f3e58c4c18a031cbb17abec7a4ad0bd5ae9fc70c1f4ba1e7fb921ad87c504aca","impliedFormat":1},{"version":"a3e8bafb2af8e850c644f4be7f5156cf7d23b7bfdc3b786bd4d10ed40329649c","impliedFormat":1},{"version":"35ec8b6760fd7138bbf5809b84551e31028fb2ba7b6dc91d95d098bf212ca8b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"a40826e8476694e90da94aa008283a7de50d1dafd37beada623863f1901cb7fb","impliedFormat":1},{"version":"f77d9188e41291acf14f476e931972460a303e1952538f9546e7b370cb8d0d20","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d04e3640dd9eb67f7f1e5bd3d0bf96c784666f7aefc8ac1537af6f2d38d4c29","impliedFormat":1},{"version":"3c884d9d9ec454bdf0d5a0b8465bf8297d2caa4d853851d92cc417ac6f30b969","impliedFormat":1},{"version":"5a369483ac4cfbdf0331c248deeb36140e6907db5e1daed241546b4a2055f82c","impliedFormat":1},{"version":"e8f5b5cc36615c17d330eaf8eebbc0d6bdd942c25991f96ef122f246f4ff722f","impliedFormat":1},{"version":"f0bd7e6d931657b59605c44112eaf8b980ba7f957a5051ed21cb93d978cf2f45","impliedFormat":1},{"version":"ee1ee365d88c4c6c0c0a5a5701d66ebc27ccd0bcfcfaa482c6e2e7fe7b98edf7","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ada07543808f3b967624645a8e1ccd446f8b01ade47842acf1328aec899fed0","affectsGlobalScope":true,"impliedFormat":1},{"version":"c4a806152acbef81593f96cae6f2b04784d776457d97adbe2694478b243fcf03","impliedFormat":1},{"version":"71adf5dbc59568663d252a46179e71e4d544c053978bfc526d11543a3f716f42","impliedFormat":1},{"version":"c60db41f7bee80fb80c0b12819f5e465c8c8b465578da43e36d04f4a4646f57d","impliedFormat":1},{"version":"93bd413918fa921c8729cef45302b24d8b6c7855d72d5bf82d3972595ae8dcbf","impliedFormat":1},{"version":"4ff41188773cbf465807dd2f7059c7494cbee5115608efc297383832a1150c43","impliedFormat":1},{"version":"dccdf1677e531e33f8ac961a68bc537418c9a414797c1ea7e91307501cdc3f5e","impliedFormat":1},{"version":"e184c4b8918ef56c8c9e68bd79f3f3780e2d0d75bf2b8a41da1509a40c2deb46","affectsGlobalScope":true,"impliedFormat":1},{"version":"d206b4baf4ddcc15d9d69a9a2f4999a72a2c6adeaa8af20fa7a9960816287555","impliedFormat":1},{"version":"93f437e1398a4f06a984f441f7fa7a9f0535c04399619b5c22e0b87bdee182cb","impliedFormat":1},{"version":"afbe24ab0d74694372baa632ecb28bb375be53f3be53f9b07ecd7fc994907de5","impliedFormat":1},{"version":"70731d10d5311bd4cf710ef7f6539b62660f4b0bfdbb3f9fbe1d25fe6366a7fa","affectsGlobalScope":true,"impliedFormat":1},{"version":"6b19db3600a17af69d4f33d08cc7076a7d19fb65bb36e442cac58929ec7c9482","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e043a1bc8fbf2a255bccf9bf27e0f1caf916c3b0518ea34aa72357c0afd42ec","impliedFormat":1},{"version":"137c2894e8f3e9672d401cc0a305dc7b1db7c69511cf6d3970fb53302f9eae09","impliedFormat":1},{"version":"3bc2f1e2c95c04048212c569ed38e338873f6a8593930cf5a7ef24ffb38fc3b6","impliedFormat":1},{"version":"8145e07aad6da5f23f2fcd8c8e4c5c13fb26ee986a79d03b0829b8fce152d8b2","impliedFormat":1},{"version":"f9d9d753d430ed050dc1bf2667a1bab711ccbb1c1507183d794cc195a5b085cc","impliedFormat":1},{"version":"9eece5e586312581ccd106d4853e861aaaa1a39f8e3ea672b8c3847eedd12f6e","impliedFormat":1},{"version":"235bfb54b4869c26f7e98e3d1f68dbfc85acf4cf5c38a4444a006fbf74a8a43d","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"93452d394fdd1dc551ec62f5042366f011a00d342d36d50793b3529bfc9bd633","impliedFormat":1},{"version":"bb715efb4857eb94539eafb420352105a0cff40746837c5140bf6b035dd220ba","affectsGlobalScope":true,"impliedFormat":1},{"version":"1851a3b4db78664f83901bb9cac9e45e03a37bb5933cc5bf37e10bb7e91ab4eb","impliedFormat":1},{"version":"fdedf82878e4c744bc2a1c1e802ae407d63474da51f14a54babe039018e53d8f","affectsGlobalScope":true,"impliedFormat":1},{"version":"27d8987fd22d92efe6560cf0ce11767bf089903ffe26047727debfd1f3bf438b","affectsGlobalScope":true,"impliedFormat":1},{"version":"578d8bb6dcb2a1c03c4c3f8eb71abc9677e1a5c788b7f24848e3138ce17f3400","impliedFormat":1},{"version":"4f029899f9bae07e225c43aef893590541b2b43267383bf5e32e3a884d219ed5","impliedFormat":1},{"version":"ae56f65caf3be91108707bd8dfbccc2a57a91feb5daabf7165a06a945545ed26","impliedFormat":1},{"version":"a136d5de521da20f31631a0a96bf712370779d1c05b7015d7019a9b2a0446ca9","impliedFormat":1},{"version":"5b566927cad2ed2139655d55d690ffa87df378b956e7fe1c96024c4d9f75c4cf","affectsGlobalScope":true,"impliedFormat":1},{"version":"bce947017cb7a2deebcc4f5ba04cead891ce6ad1602a4438ae45ed9aa1f39104","affectsGlobalScope":true,"impliedFormat":1},{"version":"d3dffd70e6375b872f0b4e152de4ae682d762c61a24881ecc5eb9f04c5caf76f","impliedFormat":1},{"version":"e2c72c065a36bc9ab2a00ac6a6f51e71501619a72c0609defd304d46610487a4","impliedFormat":1},{"version":"d91a7d8b5655c42986f1bdfe2105c4408f472831c8f20cf11a8c3345b6b56c8c","impliedFormat":1},{"version":"616075a6ac578cf5a013ee12964188b4412823796ce0b202c6f1d2e4ca8480d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"e8a979b8af001c9fc2e774e7809d233c8ca955a28756f52ee5dee88ccb0611d2","impliedFormat":1},{"version":"cac793cc47c29e26e4ac3601dcb00b4435ebed26203485790e44f2ad8b6ad847","impliedFormat":1},{"version":"17ed71200119e86ccef2d96b73b02ce8854b76ad6bd21b5021d4269bec527b5f","impliedFormat":1},{"version":"e91ad231af87f864b3f07cd0e39b1cf6c133988156f087c1c3ccb0a5491c9115","impliedFormat":1},{"version":"03c258e060b7da220973f84b89615e4e9850e9b5d30b3a8e4840b3e3268ae8eb","impliedFormat":1},{"version":"319c37263037e8d9481a3dc7eadf6afa6a5f5c002189ebe28776ac1a62a38e15","impliedFormat":1},{"version":"837f5c12e3e94ee97aca37aa2a50ede521e5887fb7fa89330f5625b70597e116","impliedFormat":1},{"version":"908217c4f2244ec402b73533ebfcc46d6dcd34fc1c807ff403d7f98702abb3bc","impliedFormat":1},{"version":"74d5a87c3616cd5d8691059d531504403aa857e09cbaecb1c64dfb9ace0db185","impliedFormat":1}],"root":[90,92],"options":{"allowJs":true,"declaration":true,"declarationDir":"./","emitDeclarationOnly":false,"esModuleInterop":true,"jsx":2,"module":1,"outDir":"./","skipLibCheck":true,"strict":true,"target":3},"referencedMap":[[84,1],[93,2],[94,2],[95,2],[96,2],[97,3],[98,2],[100,4],[101,5],[99,2],[102,2],[103,2],[104,2],[150,6],[151,6],[152,7],[110,8],[153,9],[154,10],[155,11],[105,2],[108,12],[106,2],[107,2],[156,13],[157,14],[158,15],[159,16],[160,17],[161,18],[162,18],[164,19],[163,20],[165,21],[166,22],[167,23],[149,24],[109,2],[168,25],[169,26],[170,27],[202,28],[171,29],[172,30],[173,31],[174,32],[175,33],[176,34],[177,35],[178,36],[179,37],[180,38],[181,38],[182,39],[183,2],[184,40],[186,41],[185,42],[187,43],[188,44],[189,45],[190,46],[191,47],[192,48],[193,49],[194,50],[195,51],[196,52],[197,53],[198,54],[199,55],[200,56],[201,57],[87,2],[203,58],[85,2],[88,59],[206,60],[204,61],[207,2],[205,2],[208,2],[209,62],[111,2],[86,2],[89,58],[79,2],[80,63],[81,64],[82,63],[83,65],[77,2],[78,2],[13,2],[14,2],[16,2],[15,2],[2,2],[17,2],[18,2],[19,2],[20,2],[21,2],[22,2],[23,2],[24,2],[3,2],[25,2],[26,2],[4,2],[27,2],[31,2],[28,2],[29,2],[30,2],[32,2],[33,2],[34,2],[5,2],[35,2],[36,2],[37,2],[38,2],[6,2],[42,2],[39,2],[40,2],[41,2],[43,2],[7,2],[44,2],[49,2],[50,2],[45,2],[46,2],[47,2],[48,2],[8,2],[54,2],[51,2],[52,2],[53,2],[55,2],[9,2],[56,2],[57,2],[58,2],[60,2],[59,2],[61,2],[62,2],[10,2],[63,2],[64,2],[65,2],[11,2],[66,2],[67,2],[68,2],[69,2],[70,2],[1,2],[71,2],[72,2],[12,2],[75,2],[74,2],[73,2],[76,2],[127,66],[137,67],[126,66],[147,68],[118,69],[117,70],[146,71],[140,72],[145,73],[120,74],[134,75],[119,76],[143,77],[115,78],[114,71],[144,79],[116,80],[121,81],[122,2],[125,81],[112,2],[148,82],[138,83],[129,84],[130,85],[132,86],[128,87],[131,88],[141,71],[123,89],[124,90],[133,91],[113,92],[136,83],[135,81],[139,2],[142,93],[91,94],[92,95],[90,96]],"version":"5.7.3"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { LucideIcon } from "lucide-react";
|
|
2
1
|
export type TestStatus = "passed" | "failed" | "skipped" | "expected-failure" | "unexpected-success" | "explicitly-skipped" | "flaky";
|
|
3
2
|
export interface TestStep {
|
|
4
3
|
id: string;
|
|
@@ -81,12 +80,6 @@ export interface TrendDataPoint {
|
|
|
81
80
|
skipped: number;
|
|
82
81
|
flaky?: number;
|
|
83
82
|
}
|
|
84
|
-
export interface SummaryMetric {
|
|
85
|
-
label: string;
|
|
86
|
-
value: string | number;
|
|
87
|
-
icon: LucideIcon;
|
|
88
|
-
color?: string;
|
|
89
|
-
}
|
|
90
83
|
export interface PlaywrightPulseReporterOptions {
|
|
91
84
|
outputFile?: string;
|
|
92
85
|
outputDir?: string;
|
|
@@ -73,24 +73,28 @@ async function compressImage(filePath, options = {}) {
|
|
|
73
73
|
.webp({ quality })
|
|
74
74
|
.toBuffer();
|
|
75
75
|
}
|
|
76
|
+
else if (ext === '.gif') {
|
|
77
|
+
// Compress GIF
|
|
78
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
79
|
+
.gif()
|
|
80
|
+
.toBuffer();
|
|
81
|
+
}
|
|
82
|
+
else if (ext === '.tiff' || ext === '.tif') {
|
|
83
|
+
// Compress TIFF
|
|
84
|
+
compressedBuffer = await sharp(imageBuffer)
|
|
85
|
+
.tiff({ quality, compression: 'lzw' })
|
|
86
|
+
.toBuffer();
|
|
87
|
+
}
|
|
76
88
|
else {
|
|
77
89
|
// Unsupported format, skip compression
|
|
78
|
-
console.log(`Compression skipped for unsupported format: ${ext}`);
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
81
92
|
// Only overwrite if compression actually reduced size
|
|
82
93
|
if (compressedBuffer.length < imageBuffer.length) {
|
|
83
94
|
await fs.writeFile(filePath, compressedBuffer);
|
|
84
|
-
const savedBytes = imageBuffer.length - compressedBuffer.length;
|
|
85
|
-
const savedPercent = ((savedBytes / imageBuffer.length) * 100).toFixed(1);
|
|
86
|
-
console.log(`Compressed ${path.basename(filePath)}: ${savedPercent}% smaller`);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
console.log(`Skipped ${path.basename(filePath)}: compression didn't reduce size`);
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
catch (error) {
|
|
93
|
-
console.warn(`Failed to compress image ${filePath}:`, error.message);
|
|
94
98
|
// File remains unchanged
|
|
95
99
|
}
|
|
96
100
|
}
|
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.36",
|
|
5
5
|
"description": "A Playwright reporter and dashboard for visualizing test results.",
|
|
6
6
|
"homepage": "https://arghajit47.github.io/playwright-pulse/",
|
|
7
7
|
"repository": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"email",
|
|
24
24
|
"playwright-report",
|
|
25
25
|
"pulse",
|
|
26
|
-
"ai-failure-analysis"
|
|
26
|
+
"ai-failure-analysis",
|
|
27
|
+
"pulse-report"
|
|
27
28
|
],
|
|
28
29
|
"main": "dist/reporter/index.js",
|
|
29
30
|
"types": "dist/reporter/index.d.ts",
|
|
@@ -67,13 +68,13 @@
|
|
|
67
68
|
"d3": "^7.9.0",
|
|
68
69
|
"date-fns": "^3.6.0",
|
|
69
70
|
"dotenv": "^16.5.0",
|
|
70
|
-
"highcharts": "^12.
|
|
71
|
+
"highcharts": "^12.5.0",
|
|
71
72
|
"jsdom": "^26.1.0",
|
|
72
|
-
"lucide-react": "^0.475.0",
|
|
73
73
|
"node-fetch": "^3.3.2",
|
|
74
|
-
"nodemailer": "^7.0.
|
|
74
|
+
"nodemailer": "^7.0.12",
|
|
75
75
|
"patch-package": "^8.0.0",
|
|
76
|
-
"
|
|
76
|
+
"readable-stream": "^4.7.0",
|
|
77
|
+
"sharp": "^0.34.5",
|
|
77
78
|
"ua-parser-js": "^1.0.41",
|
|
78
79
|
"zod": "^3.24.2"
|
|
79
80
|
},
|
|
@@ -90,8 +91,11 @@
|
|
|
90
91
|
"@playwright/test": ">=1.40.0"
|
|
91
92
|
},
|
|
92
93
|
"overrides": {
|
|
93
|
-
"glob": "^13.0.
|
|
94
|
-
"minimatch": "^10.1
|
|
95
|
-
"
|
|
94
|
+
"glob": "^13.0.6",
|
|
95
|
+
"minimatch": "^10.2.1",
|
|
96
|
+
"ajv": "^8.18.0",
|
|
97
|
+
"isarray": "^2.0.5",
|
|
98
|
+
"safe-buffer": "^5.2.1",
|
|
99
|
+
"string_decoder": "^1.3.0"
|
|
96
100
|
}
|
|
97
|
-
}
|
|
101
|
+
}
|
|
@@ -740,7 +740,9 @@ function generateMinifiedHTML(reportData) {
|
|
|
740
740
|
`;
|
|
741
741
|
}
|
|
742
742
|
async function main() {
|
|
743
|
-
|
|
743
|
+
if (process.env.SKIP_LOGO !== "true") {
|
|
744
|
+
await animate();
|
|
745
|
+
}
|
|
744
746
|
|
|
745
747
|
const outputDir = await getOutputDir(customOutputDir);
|
|
746
748
|
const reportJsonPath = path.resolve(outputDir, DEFAULT_JSON_FILE);
|
|
@@ -8,6 +8,31 @@ import { fileURLToPath } from "url";
|
|
|
8
8
|
import { getOutputDir } from "./config-reader.mjs";
|
|
9
9
|
import { animate } from "./terminal-logo.mjs";
|
|
10
10
|
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Load Highcharts content from node_modules for inlining
|
|
15
|
+
const highchartsPath = path.resolve(
|
|
16
|
+
process.cwd(),
|
|
17
|
+
"node_modules/highcharts/highcharts.js",
|
|
18
|
+
);
|
|
19
|
+
let highchartsContent = "";
|
|
20
|
+
try {
|
|
21
|
+
highchartsContent = readFileSync(highchartsPath, "utf8");
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// If not found in process.cwd(), try relative to the script
|
|
24
|
+
try {
|
|
25
|
+
highchartsContent = readFileSync(
|
|
26
|
+
path.resolve(__dirname, "../node_modules/highcharts/highcharts.js"),
|
|
27
|
+
"utf8",
|
|
28
|
+
);
|
|
29
|
+
} catch (e2) {
|
|
30
|
+
console.warn(
|
|
31
|
+
"Highcharts could not be loaded from node_modules. Falling back to CDN.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
11
36
|
// Use dynamic import for chalk as it's ESM only
|
|
12
37
|
let chalk;
|
|
13
38
|
try {
|
|
@@ -2550,7 +2575,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2550
2575
|
<pre id="${logId}" class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2551
2576
|
testData.stdout
|
|
2552
2577
|
.map((line) => sanitizeHTML(line))
|
|
2553
|
-
.join("
|
|
2578
|
+
.join("\n"),
|
|
2554
2579
|
)}</pre>
|
|
2555
2580
|
</div>
|
|
2556
2581
|
</div>`;
|
|
@@ -2558,7 +2583,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2558
2583
|
${
|
|
2559
2584
|
testData.stderr && testData.stderr.length > 0
|
|
2560
2585
|
? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2561
|
-
testData.stderr.map((line) => sanitizeHTML(line)).join("
|
|
2586
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2562
2587
|
)}</pre></div>`
|
|
2563
2588
|
: ""
|
|
2564
2589
|
}
|
|
@@ -2788,13 +2813,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2788
2813
|
<!-- Preconnect to external domains -->
|
|
2789
2814
|
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
|
|
2790
2815
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
2791
|
-
<link rel="preconnect" href="https://code.highcharts.com">
|
|
2792
2816
|
|
|
2793
2817
|
<!-- Preload critical font -->
|
|
2818
|
+
|
|
2794
2819
|
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
2795
2820
|
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap"></noscript>
|
|
2796
2821
|
|
|
2797
|
-
<script src="https://code.highcharts.com/highcharts.js" defer></script>
|
|
2822
|
+
${highchartsContent ? `<script>${highchartsContent}</script>` : '<script src="https://code.highcharts.com/highcharts.js" defer></script>'}
|
|
2798
2823
|
<title>Pulse Report</title>
|
|
2799
2824
|
<style>
|
|
2800
2825
|
:root {
|
|
@@ -8,6 +8,31 @@ import { fileURLToPath } from "url";
|
|
|
8
8
|
import { getOutputDir } from "./config-reader.mjs";
|
|
9
9
|
import { animate } from "./terminal-logo.mjs";
|
|
10
10
|
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Load Highcharts content from node_modules for inlining
|
|
15
|
+
const highchartsPath = path.resolve(
|
|
16
|
+
process.cwd(),
|
|
17
|
+
"node_modules/highcharts/highcharts.js",
|
|
18
|
+
);
|
|
19
|
+
let highchartsContent = "";
|
|
20
|
+
try {
|
|
21
|
+
highchartsContent = readFileSync(highchartsPath, "utf8");
|
|
22
|
+
} catch (e) {
|
|
23
|
+
// If not found in process.cwd(), try relative to the script
|
|
24
|
+
try {
|
|
25
|
+
highchartsContent = readFileSync(
|
|
26
|
+
path.resolve(__dirname, "../node_modules/highcharts/highcharts.js"),
|
|
27
|
+
"utf8",
|
|
28
|
+
);
|
|
29
|
+
} catch (e2) {
|
|
30
|
+
console.warn(
|
|
31
|
+
"Highcharts could not be loaded from node_modules. Falling back to CDN.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
11
36
|
/**
|
|
12
37
|
* Dynamically imports the 'chalk' library for terminal string styling.
|
|
13
38
|
* This is necessary because chalk is an ESM-only module.
|
|
@@ -2772,7 +2797,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2772
2797
|
<pre id="${logId}" class="console-log stdout-log" style="background-color: #2d2d2d; color: wheat; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2773
2798
|
testData.stdout
|
|
2774
2799
|
.map((line) => sanitizeHTML(line))
|
|
2775
|
-
.join("
|
|
2800
|
+
.join("\n"),
|
|
2776
2801
|
)}</pre>
|
|
2777
2802
|
</div>
|
|
2778
2803
|
</div>`;
|
|
@@ -2780,7 +2805,7 @@ function generateHTML(reportData, trendData = null) {
|
|
|
2780
2805
|
${
|
|
2781
2806
|
testData.stderr && testData.stderr.length > 0
|
|
2782
2807
|
? `<div class="console-output-section"><h4>Console Output (stderr)</h4><pre class="console-log stderr-log" style="background-color: #2d2d2d; color: indianred; padding: 1.25em; border-radius: 0.85em; line-height: 1.2;">${formatPlaywrightError(
|
|
2783
|
-
testData.stderr.map((line) => sanitizeHTML(line)).join("
|
|
2808
|
+
testData.stderr.map((line) => sanitizeHTML(line)).join("\n"),
|
|
2784
2809
|
)}</pre></div>`
|
|
2785
2810
|
: ""
|
|
2786
2811
|
}
|
|
@@ -3010,13 +3035,13 @@ function generateHTML(reportData, trendData = null) {
|
|
|
3010
3035
|
<!-- Preconnect to external domains -->
|
|
3011
3036
|
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
|
|
3012
3037
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
3013
|
-
<link rel="preconnect" href="https://code.highcharts.com">
|
|
3014
3038
|
|
|
3015
3039
|
<!-- Preload critical font -->
|
|
3040
|
+
|
|
3016
3041
|
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
3017
3042
|
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap"></noscript>
|
|
3018
3043
|
|
|
3019
|
-
<script src="https://code.highcharts.com/highcharts.js" defer></script>
|
|
3044
|
+
${highchartsContent ? `<script>${highchartsContent}</script>` : '<script src="https://code.highcharts.com/highcharts.js" defer></script>'}
|
|
3020
3045
|
<title>Pulse Static Report</title>
|
|
3021
3046
|
|
|
3022
3047
|
<style>
|
package/scripts/sendReport.mjs
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import nodemailer from "nodemailer";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import archiver from "archiver";
|
|
2
|
+
import nodemailer from "nodemailer";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import archiver from "archiver";
|
|
5
5
|
import {
|
|
6
6
|
createWriteStream,
|
|
7
|
-
readFileSync as fsReadFileSync,
|
|
8
|
-
existsSync as fsExistsSync,
|
|
9
|
-
} from "fs";
|
|
7
|
+
readFileSync as fsReadFileSync,
|
|
8
|
+
existsSync as fsExistsSync,
|
|
9
|
+
} from "fs";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import { animate } from "./terminal-logo.mjs";
|
|
12
|
-
import { fork } from "child_process";
|
|
13
|
-
import "dotenv/config";
|
|
12
|
+
import { fork } from "child_process";
|
|
13
|
+
import "dotenv/config";
|
|
14
14
|
import { getOutputDir } from "./config-reader.mjs";
|
|
15
15
|
|
|
16
|
-
// Import chalk using top-level await if your Node version supports it (14.8+)
|
|
17
|
-
// or keep the dynamic import if preferred, but ensure chalk is resolved before use.
|
|
18
16
|
let chalk;
|
|
19
17
|
try {
|
|
20
18
|
chalk = (await import("chalk")).default;
|
|
@@ -40,13 +38,6 @@ for (let i = 0; i < args.length; i++) {
|
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
let fetch;
|
|
43
|
-
// Ensure fetch is imported and available before it's used in fetchCredentials
|
|
44
|
-
// Using a top-level import is generally cleaner:
|
|
45
|
-
// import fetch from 'node-fetch';
|
|
46
|
-
// However, your dynamic import pattern is also fine if `fetch` is awaited properly.
|
|
47
|
-
// For simplicity, I'll assume the dynamic import is handled and awaited before fetchCredentials is called.
|
|
48
|
-
// The existing dynamic import for fetch is okay.
|
|
49
|
-
|
|
50
41
|
let projectName;
|
|
51
42
|
|
|
52
43
|
function getUUID(reportDir) {
|
|
@@ -54,11 +45,10 @@ function getUUID(reportDir) {
|
|
|
54
45
|
console.log("Report path:", reportPath);
|
|
55
46
|
|
|
56
47
|
if (!fsExistsSync(reportPath)) {
|
|
57
|
-
// CHANGED
|
|
58
48
|
throw new Error("Pulse report file not found.");
|
|
59
49
|
}
|
|
60
50
|
|
|
61
|
-
const content = JSON.parse(fsReadFileSync(reportPath, "utf-8"));
|
|
51
|
+
const content = JSON.parse(fsReadFileSync(reportPath, "utf-8"));
|
|
62
52
|
const idString = content.run.id;
|
|
63
53
|
const parts = idString.split("-");
|
|
64
54
|
const uuid = parts.slice(-5).join("-");
|
|
@@ -72,27 +62,22 @@ function formatDuration(ms) {
|
|
|
72
62
|
if (ms < 3600000) return `${(ms / 60000).toFixed(1)}min`;
|
|
73
63
|
return `${(ms / 3600000).toFixed(1)}h`;
|
|
74
64
|
}
|
|
75
|
-
const formatStartTime = (isoString) => {
|
|
76
|
-
const date = new Date(isoString);
|
|
77
|
-
return date.toLocaleString(); // Default locale
|
|
78
|
-
};
|
|
79
65
|
|
|
80
66
|
const getPulseReportSummary = (reportDir) => {
|
|
81
67
|
const reportPath = path.join(reportDir, "playwright-pulse-report.json");
|
|
82
68
|
|
|
83
69
|
if (!fsExistsSync(reportPath)) {
|
|
84
|
-
// CHANGED
|
|
85
70
|
throw new Error("Pulse report file not found.");
|
|
86
71
|
}
|
|
87
72
|
|
|
88
|
-
const content = JSON.parse(fsReadFileSync(reportPath, "utf-8"));
|
|
73
|
+
const content = JSON.parse(fsReadFileSync(reportPath, "utf-8"));
|
|
89
74
|
const run = content.run;
|
|
90
75
|
|
|
91
76
|
const total = run.totalTests || 0;
|
|
92
77
|
const passed = run.passed || 0;
|
|
93
78
|
const failed = run.failed || 0;
|
|
94
79
|
const skipped = run.skipped || 0;
|
|
95
|
-
const durationInMs = run.duration || 0;
|
|
80
|
+
const durationInMs = run.duration || 0;
|
|
96
81
|
|
|
97
82
|
const readableStartTime = new Date(run.timestamp).toLocaleString();
|
|
98
83
|
|
|
@@ -105,35 +90,12 @@ const getPulseReportSummary = (reportDir) => {
|
|
|
105
90
|
failedPercentage: total ? ((failed / total) * 100).toFixed(2) : "0.00",
|
|
106
91
|
skippedPercentage: total ? ((skipped / total) * 100).toFixed(2) : "0.00",
|
|
107
92
|
startTime: readableStartTime,
|
|
108
|
-
duration: formatDuration(durationInMs),
|
|
93
|
+
duration: formatDuration(durationInMs),
|
|
109
94
|
};
|
|
110
95
|
};
|
|
111
96
|
|
|
112
|
-
const delay = (time) => new Promise((resolve) => setTimeout(resolve, time));
|
|
113
|
-
|
|
114
|
-
const zipFolder = async (folderPath, zipPath) => {
|
|
115
|
-
return new Promise((resolve, reject) => {
|
|
116
|
-
const output = createWriteStream(zipPath); // CHANGED
|
|
117
|
-
const archiveInstance = archiver("zip", { zlib: { level: 9 } }); // Renamed to avoid conflict
|
|
118
|
-
|
|
119
|
-
output.on("close", () => {
|
|
120
|
-
console.log(`${archiveInstance.pointer()} total bytes`);
|
|
121
|
-
console.log("Folder has been zipped successfully.");
|
|
122
|
-
resolve();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
archiveInstance.on("error", (err) => {
|
|
126
|
-
reject(err);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
archiveInstance.pipe(output);
|
|
130
|
-
archiveInstance.directory(folderPath, false);
|
|
131
|
-
archiveInstance.finalize();
|
|
132
|
-
});
|
|
133
|
-
};
|
|
134
|
-
|
|
135
97
|
const generateHtmlTable = (data) => {
|
|
136
|
-
projectName = "Pulse Emailable Report";
|
|
98
|
+
projectName = "Pulse Emailable Report";
|
|
137
99
|
const stats = data;
|
|
138
100
|
const total = stats.passed + stats.failed + stats.skipped;
|
|
139
101
|
const passedTests = stats.passed;
|
|
@@ -143,9 +105,9 @@ const generateHtmlTable = (data) => {
|
|
|
143
105
|
const skippedTests = stats.skipped;
|
|
144
106
|
const skippedPercentage = stats.skippedPercentage;
|
|
145
107
|
const startTime = stats.startTime;
|
|
146
|
-
const durationString = stats.duration;
|
|
108
|
+
const durationString = stats.duration;
|
|
147
109
|
|
|
148
|
-
return `
|
|
110
|
+
return `
|
|
149
111
|
<!DOCTYPE html>
|
|
150
112
|
<html lang="en">
|
|
151
113
|
<head>
|
|
@@ -153,62 +115,35 @@ return `
|
|
|
153
115
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
154
116
|
<title>Test Stats Report</title>
|
|
155
117
|
<style>
|
|
156
|
-
/* ANIMATION KEYFRAMES
|
|
157
|
-
(Supported by Apple Mail, iOS, Outlook Mac, etc.)
|
|
158
|
-
*/
|
|
159
|
-
|
|
160
|
-
/* 1. Slide the card up and fade in */
|
|
161
118
|
@keyframes slideUpFade {
|
|
162
119
|
0% { opacity: 0; transform: translateY(20px); }
|
|
163
120
|
100% { opacity: 1; transform: translateY(0); }
|
|
164
121
|
}
|
|
165
|
-
|
|
166
|
-
/* 2. Gentle pulse for the logo */
|
|
167
122
|
@keyframes gentlePulse {
|
|
168
123
|
0% { transform: scale(1); }
|
|
169
124
|
50% { transform: scale(1.05); }
|
|
170
125
|
100% { transform: scale(1); }
|
|
171
126
|
}
|
|
172
|
-
|
|
173
|
-
/* 3. Pop in effect for status badges */
|
|
174
127
|
@keyframes popIn {
|
|
175
128
|
0% { opacity: 0; transform: scale(0.5); }
|
|
176
129
|
80% { transform: scale(1.1); }
|
|
177
130
|
100% { opacity: 1; transform: scale(1); }
|
|
178
131
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
.anim-card {
|
|
182
|
-
animation: slideUpFade 0.8s ease-out forwards;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.anim-logo {
|
|
186
|
-
animation: gentlePulse 3s infinite ease-in-out;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/* Staggered delays for list items so they cascade in */
|
|
132
|
+
.anim-card { animation: slideUpFade 0.8s ease-out forwards; }
|
|
133
|
+
.anim-logo { animation: gentlePulse 3s infinite ease-in-out; }
|
|
190
134
|
.anim-row-1 { animation: slideUpFade 0.5s ease-out 0.2s backwards; }
|
|
191
135
|
.anim-row-2 { animation: slideUpFade 0.5s ease-out 0.3s backwards; }
|
|
192
136
|
.anim-row-3 { animation: slideUpFade 0.5s ease-out 0.4s backwards; }
|
|
193
137
|
.anim-row-4 { animation: slideUpFade 0.5s ease-out 0.5s backwards; }
|
|
194
|
-
|
|
195
|
-
.anim-badge {
|
|
196
|
-
animation: popIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.6s backwards;
|
|
197
|
-
}
|
|
138
|
+
.anim-badge { animation: popIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55) 0.6s backwards; }
|
|
198
139
|
</style>
|
|
199
140
|
</head>
|
|
200
141
|
<body style="margin: 0; padding: 0; background-color: #f3f4f6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
|
|
201
|
-
|
|
202
142
|
<table role="presentation" width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #f3f4f6; padding: 40px 0;">
|
|
203
143
|
<tr>
|
|
204
144
|
<td align="center">
|
|
205
|
-
|
|
206
145
|
<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;">
|
|
207
|
-
|
|
208
|
-
<tr>
|
|
209
|
-
<td height="6" style="background-color: #4f46e5;"></td>
|
|
210
|
-
</tr>
|
|
211
|
-
|
|
146
|
+
<tr><td height="6" style="background-color: #4f46e5;"></td></tr>
|
|
212
147
|
<tr>
|
|
213
148
|
<td style="padding: 32px 32px 20px 32px;">
|
|
214
149
|
<table border="0" cellspacing="0" cellpadding="0" width="100%">
|
|
@@ -224,7 +159,6 @@ return `
|
|
|
224
159
|
</table>
|
|
225
160
|
</td>
|
|
226
161
|
</tr>
|
|
227
|
-
|
|
228
162
|
<tr>
|
|
229
163
|
<td style="padding: 0 32px 20px 32px;">
|
|
230
164
|
<table width="100%" border="0" cellspacing="0" cellpadding="0" style="background-color: #f9fafb; border-radius: 8px; border: 1px solid #e5e7eb;">
|
|
@@ -241,16 +175,13 @@ return `
|
|
|
241
175
|
</table>
|
|
242
176
|
</td>
|
|
243
177
|
</tr>
|
|
244
|
-
|
|
245
178
|
<tr>
|
|
246
179
|
<td style="padding: 0 32px 32px 32px;">
|
|
247
180
|
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
|
248
|
-
|
|
249
181
|
<tr class="anim-row-1">
|
|
250
182
|
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px; color: #4b5563;">Total Tests Executed</td>
|
|
251
183
|
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; text-align: right; font-size: 14px; font-weight: 600; color: #111827;">${total}</td>
|
|
252
184
|
</tr>
|
|
253
|
-
|
|
254
185
|
<tr class="anim-row-2">
|
|
255
186
|
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px; color: #4b5563;">Tests Passed</td>
|
|
256
187
|
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; text-align: right;">
|
|
@@ -259,7 +190,6 @@ return `
|
|
|
259
190
|
</span>
|
|
260
191
|
</td>
|
|
261
192
|
</tr>
|
|
262
|
-
|
|
263
193
|
<tr class="anim-row-3">
|
|
264
194
|
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; font-size: 14px; color: #4b5563;">Tests Failed</td>
|
|
265
195
|
<td style="padding: 12px 0; border-bottom: 1px solid #f3f4f6; text-align: right;">
|
|
@@ -268,7 +198,6 @@ return `
|
|
|
268
198
|
</span>
|
|
269
199
|
</td>
|
|
270
200
|
</tr>
|
|
271
|
-
|
|
272
201
|
<tr class="anim-row-4">
|
|
273
202
|
<td style="padding: 12px 0; font-size: 14px; color: #4b5563;">Tests Skipped</td>
|
|
274
203
|
<td style="padding: 12px 0; text-align: right;">
|
|
@@ -277,17 +206,14 @@ return `
|
|
|
277
206
|
</span>
|
|
278
207
|
</td>
|
|
279
208
|
</tr>
|
|
280
|
-
|
|
281
209
|
</table>
|
|
282
210
|
</td>
|
|
283
211
|
</tr>
|
|
284
|
-
|
|
285
212
|
<tr>
|
|
286
213
|
<td style="background-color: #f9fafb; padding: 20px; text-align: center; border-top: 1px solid #e5e7eb;">
|
|
287
214
|
<p style="margin: 0; font-size: 12px; color: #9ca3af;">Generated by Pulse Report</p>
|
|
288
215
|
</td>
|
|
289
216
|
</tr>
|
|
290
|
-
|
|
291
217
|
</table>
|
|
292
218
|
</td>
|
|
293
219
|
</tr>
|
|
@@ -299,45 +225,95 @@ return `
|
|
|
299
225
|
|
|
300
226
|
const __filename = fileURLToPath(import.meta.url);
|
|
301
227
|
const __dirname = path.dirname(__filename);
|
|
302
|
-
|
|
303
|
-
// Ensure the name here matches the actual file name of input_file_0.js
|
|
304
|
-
// If input_file_0.js is indeed the script, use that name.
|
|
305
|
-
// Using .mjs extension explicitly tells Node to treat it as ESM.
|
|
306
228
|
const archiveRunScriptPath = path.resolve(
|
|
307
229
|
__dirname,
|
|
308
|
-
"generate-email-report.mjs"
|
|
230
|
+
"generate-email-report.mjs",
|
|
309
231
|
);
|
|
310
232
|
|
|
311
|
-
async function runScript(scriptPath, args = []) {
|
|
233
|
+
async function runScript(scriptPath, args = [], options = {}) {
|
|
312
234
|
return new Promise((resolve, reject) => {
|
|
313
235
|
const childProcess = fork(scriptPath, args, {
|
|
314
|
-
// Renamed variable
|
|
315
236
|
stdio: "inherit",
|
|
237
|
+
...options,
|
|
238
|
+
env: { ...process.env, ...options.env },
|
|
316
239
|
});
|
|
317
|
-
|
|
318
240
|
childProcess.on("error", (err) => {
|
|
319
241
|
console.error(chalk.red(`Failed to start script: ${scriptPath}`), err);
|
|
320
242
|
reject(err);
|
|
321
243
|
});
|
|
322
|
-
|
|
323
244
|
childProcess.on("exit", (code) => {
|
|
324
|
-
if (code === 0)
|
|
325
|
-
|
|
326
|
-
} else {
|
|
327
|
-
const errorMessage = `Script ${scriptPath} exited with code ${code}.`;
|
|
328
|
-
console.error(chalk.red(errorMessage));
|
|
329
|
-
reject(new Error(errorMessage));
|
|
330
|
-
}
|
|
245
|
+
if (code === 0) resolve();
|
|
246
|
+
else reject(new Error(`Script ${scriptPath} exited with code ${code}.`));
|
|
331
247
|
});
|
|
332
248
|
});
|
|
333
249
|
}
|
|
334
250
|
|
|
335
251
|
const sendEmail = async (credentials, reportDir) => {
|
|
336
252
|
const archiveArgs = customOutputDir ? ["--outputDir", customOutputDir] : [];
|
|
337
|
-
await runScript(archiveRunScriptPath, archiveArgs
|
|
253
|
+
await runScript(archiveRunScriptPath, archiveArgs, {
|
|
254
|
+
env: { SKIP_LOGO: "true" },
|
|
255
|
+
});
|
|
256
|
+
|
|
338
257
|
try {
|
|
339
258
|
console.log("Starting the sendEmail function...");
|
|
259
|
+
const reportData = getPulseReportSummary(reportDir);
|
|
260
|
+
const htmlContent = generateHtmlTable(reportData);
|
|
261
|
+
|
|
262
|
+
const recipients = [
|
|
263
|
+
process.env.RECIPIENT_EMAIL_1 || "",
|
|
264
|
+
process.env.RECIPIENT_EMAIL_2 || "",
|
|
265
|
+
process.env.RECIPIENT_EMAIL_3 || "",
|
|
266
|
+
process.env.RECIPIENT_EMAIL_4 || "",
|
|
267
|
+
process.env.RECIPIENT_EMAIL_5 || "",
|
|
268
|
+
].filter((email) => email);
|
|
269
|
+
|
|
270
|
+
// --- DEFAULT FLOW: BREVO API ---
|
|
271
|
+
if (credentials.apiKey) {
|
|
272
|
+
const SENDER_NAME = "Pulse Email Report";
|
|
273
|
+
const attachmentPath = path.join(reportDir, "pulse-email-summary.html");
|
|
274
|
+
let attachments = [];
|
|
275
|
+
if (fsExistsSync(attachmentPath)) {
|
|
276
|
+
// Brevo requires attachments to be Base64 encoded strings
|
|
277
|
+
const fileContent = fsReadFileSync(attachmentPath).toString("base64");
|
|
278
|
+
attachments.push({
|
|
279
|
+
content: fileContent,
|
|
280
|
+
name: "report.html",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const payload = {
|
|
284
|
+
sender: {
|
|
285
|
+
name: SENDER_NAME,
|
|
286
|
+
email: credentials.username,
|
|
287
|
+
},
|
|
288
|
+
to: recipients.map((email) => ({ email })),
|
|
289
|
+
subject: "Pulse Report " + new Date().toLocaleString(),
|
|
290
|
+
htmlContent: htmlContent,
|
|
291
|
+
attachment: attachments,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const response = await fetch("https://api.brevo.com/v3/smtp/email", {
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers: {
|
|
297
|
+
accept: "application/json",
|
|
298
|
+
"api-key": credentials.apiKey,
|
|
299
|
+
"content-type": "application/json",
|
|
300
|
+
},
|
|
301
|
+
body: JSON.stringify(payload),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const result = await response.json();
|
|
305
|
+
if (response.ok) {
|
|
306
|
+
console.log("Email sent: ", result.messageId);
|
|
307
|
+
} else {
|
|
308
|
+
console.error(
|
|
309
|
+
"Error sending email via Brevo: ",
|
|
310
|
+
result.message || result,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
return; // Exit after default flow
|
|
314
|
+
}
|
|
340
315
|
|
|
316
|
+
// --- CUSTOM FLOW: GMAIL / OUTLOOK ---
|
|
341
317
|
let secureTransporter;
|
|
342
318
|
const mailHost = credentials.host
|
|
343
319
|
? credentials.host.toLowerCase()
|
|
@@ -346,49 +322,32 @@ const sendEmail = async (credentials, reportDir) => {
|
|
|
346
322
|
if (mailHost === "gmail") {
|
|
347
323
|
secureTransporter = nodemailer.createTransport({
|
|
348
324
|
service: "gmail",
|
|
349
|
-
auth: {
|
|
350
|
-
user: credentials.username,
|
|
351
|
-
pass: credentials.password,
|
|
352
|
-
},
|
|
325
|
+
auth: { user: credentials.username, pass: credentials.password },
|
|
353
326
|
});
|
|
354
327
|
} else if (mailHost === "outlook") {
|
|
355
328
|
secureTransporter = nodemailer.createTransport({
|
|
356
329
|
host: "smtp.outlook.com",
|
|
357
330
|
port: 587,
|
|
358
331
|
secure: false,
|
|
359
|
-
auth: {
|
|
360
|
-
user: credentials.username,
|
|
361
|
-
pass: credentials.password,
|
|
362
|
-
},
|
|
332
|
+
auth: { user: credentials.username, pass: credentials.password },
|
|
363
333
|
});
|
|
364
334
|
} else {
|
|
365
|
-
// Should be caught in main, but safety check here
|
|
366
335
|
console.log(
|
|
367
336
|
chalk.red(
|
|
368
|
-
"Pulse report currently do not support provided mail host, kindly use either outlook mail or, gmail"
|
|
369
|
-
)
|
|
337
|
+
"Pulse report currently do not support provided mail host, kindly use either outlook mail or, gmail",
|
|
338
|
+
),
|
|
370
339
|
);
|
|
371
340
|
process.exit(1);
|
|
372
341
|
}
|
|
373
342
|
|
|
374
|
-
const reportData = getPulseReportSummary(reportDir);
|
|
375
|
-
const htmlContent = generateHtmlTable(reportData);
|
|
376
|
-
|
|
377
343
|
const mailOptions = {
|
|
378
344
|
from: credentials.username,
|
|
379
|
-
to:
|
|
380
|
-
process.env.RECIPIENT_EMAIL_1 || "",
|
|
381
|
-
process.env.RECIPIENT_EMAIL_2 || "",
|
|
382
|
-
process.env.RECIPIENT_EMAIL_3 || "",
|
|
383
|
-
process.env.RECIPIENT_EMAIL_4 || "",
|
|
384
|
-
process.env.RECIPIENT_EMAIL_5 || "",
|
|
385
|
-
].filter((email) => email), // Filter out empty strings
|
|
345
|
+
to: recipients,
|
|
386
346
|
subject: "Pulse Report " + new Date().toLocaleString(),
|
|
387
347
|
html: htmlContent,
|
|
388
348
|
attachments: [
|
|
389
349
|
{
|
|
390
350
|
filename: `report.html`,
|
|
391
|
-
// Make sure this path is correct and the file is generated by archiveRunScriptPath
|
|
392
351
|
path: path.join(reportDir, "pulse-email-summary.html"),
|
|
393
352
|
},
|
|
394
353
|
],
|
|
@@ -402,14 +361,13 @@ const sendEmail = async (credentials, reportDir) => {
|
|
|
402
361
|
};
|
|
403
362
|
|
|
404
363
|
async function fetchCredentials(reportDir, retries = 10) {
|
|
405
|
-
// Ensure fetch is initialized from the dynamic import before calling this
|
|
406
364
|
if (!fetch) {
|
|
407
365
|
try {
|
|
408
366
|
fetch = (await import("node-fetch")).default;
|
|
409
367
|
} catch (err) {
|
|
410
368
|
console.error(
|
|
411
369
|
"Failed to import node-fetch dynamically for fetchCredentials:",
|
|
412
|
-
err
|
|
370
|
+
err,
|
|
413
371
|
);
|
|
414
372
|
return null;
|
|
415
373
|
}
|
|
@@ -420,7 +378,7 @@ async function fetchCredentials(reportDir, retries = 10) {
|
|
|
420
378
|
|
|
421
379
|
if (!key) {
|
|
422
380
|
console.error(
|
|
423
|
-
"🔴 Critical: API key (UUID from report) not found or invalid."
|
|
381
|
+
"🔴 Critical: API key (UUID from report) not found or invalid.",
|
|
424
382
|
);
|
|
425
383
|
return null;
|
|
426
384
|
}
|
|
@@ -429,112 +387,72 @@ async function fetchCredentials(reportDir, retries = 10) {
|
|
|
429
387
|
try {
|
|
430
388
|
console.log(`🟡 Attempt ${attempt} of ${retries} to fetch credentials`);
|
|
431
389
|
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
reject(new Error(`Request timed out after ${timeout}ms`));
|
|
435
|
-
}, timeout);
|
|
436
|
-
});
|
|
390
|
+
const controller = new AbortController();
|
|
391
|
+
const id = setTimeout(() => controller.abort(), timeout);
|
|
437
392
|
|
|
438
|
-
const
|
|
393
|
+
const response = await fetch(
|
|
439
394
|
"https://get-credentials.netlify.app/api/getcredentials",
|
|
440
395
|
{
|
|
441
396
|
method: "GET",
|
|
442
|
-
headers: {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
397
|
+
headers: { "x-api-key": `${key}` },
|
|
398
|
+
signal: controller.signal,
|
|
399
|
+
},
|
|
446
400
|
);
|
|
447
|
-
|
|
448
|
-
const response = await Promise.race([fetchPromise, timeoutPromise]);
|
|
401
|
+
clearTimeout(id);
|
|
449
402
|
|
|
450
403
|
if (!response.ok) {
|
|
451
|
-
if (response.status === 401) {
|
|
452
|
-
console.error("🔴 Invalid API key - authentication failed");
|
|
453
|
-
} else if (response.status === 404) {
|
|
454
|
-
console.error("🔴 Endpoint not found - check the API URL");
|
|
455
|
-
} else {
|
|
456
|
-
console.error(`🔴 Fetch failed with status: ${response.status}`);
|
|
457
|
-
}
|
|
458
404
|
if (attempt < retries)
|
|
459
405
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
460
406
|
continue;
|
|
461
407
|
}
|
|
462
408
|
|
|
463
409
|
const data = await response.json();
|
|
464
|
-
|
|
465
|
-
if (!data.username || !data.password) {
|
|
466
|
-
console.error("🔴 Invalid credentials format received from API");
|
|
467
|
-
if (attempt < retries)
|
|
468
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
469
|
-
continue;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
410
|
console.log("🟢 Fetched credentials successfully");
|
|
473
|
-
return data;
|
|
411
|
+
return data; // Returns data which now contains 'apiKey' for Brevo
|
|
474
412
|
} catch (err) {
|
|
475
413
|
console.error(`🔴 Attempt ${attempt} failed: ${err.message}`);
|
|
476
|
-
if (attempt === retries)
|
|
477
|
-
console.error(
|
|
478
|
-
`🔴 All ${retries} attempts failed. Last error: ${err.message}`
|
|
479
|
-
);
|
|
480
|
-
return null;
|
|
481
|
-
}
|
|
414
|
+
if (attempt === retries) return null;
|
|
482
415
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
483
416
|
}
|
|
484
417
|
}
|
|
485
|
-
return null;
|
|
418
|
+
return null;
|
|
486
419
|
}
|
|
487
420
|
|
|
488
421
|
const main = async () => {
|
|
489
422
|
await animate();
|
|
490
|
-
|
|
491
|
-
// Ensure fetch is initialized (dynamic import at top or here)
|
|
492
423
|
if (!fetch) {
|
|
493
424
|
try {
|
|
494
425
|
fetch = (await import("node-fetch")).default;
|
|
495
426
|
} catch (err) {
|
|
496
427
|
console.error("Failed to import node-fetch at start of main:", err);
|
|
497
|
-
process.exit(1);
|
|
428
|
+
process.exit(1);
|
|
498
429
|
}
|
|
499
430
|
}
|
|
500
431
|
|
|
501
432
|
const reportDir = await getOutputDir(customOutputDir);
|
|
502
|
-
|
|
503
433
|
console.log(chalk.blue(`Preparing to send email report...`));
|
|
504
434
|
console.log(chalk.blue(`Report directory set to: ${reportDir}`));
|
|
505
|
-
if (customOutputDir) {
|
|
506
|
-
console.log(chalk.gray(` (from CLI argument)`));
|
|
507
|
-
} else {
|
|
508
|
-
console.log(
|
|
509
|
-
chalk.gray(` (auto-detected from playwright.config or using default)`)
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
435
|
|
|
513
|
-
// --- MODIFIED: Credentials Selection Logic ---
|
|
514
436
|
let credentials;
|
|
515
437
|
|
|
516
|
-
// Check if custom environment variables are provided
|
|
517
438
|
if (
|
|
518
439
|
process.env.PULSE_MAIL_HOST &&
|
|
519
440
|
process.env.PULSE_MAIL_USERNAME &&
|
|
520
441
|
process.env.PULSE_MAIL_PASSWORD
|
|
521
442
|
) {
|
|
522
443
|
const host = process.env.PULSE_MAIL_HOST.toLowerCase();
|
|
523
|
-
|
|
524
|
-
// Validate host immediately
|
|
525
444
|
if (host !== "gmail" && host !== "outlook") {
|
|
526
445
|
console.log(
|
|
527
446
|
chalk.red(
|
|
528
|
-
"Pulse report currently do not support provided mail host, kindly use either outlook mail or, gmail."
|
|
529
|
-
)
|
|
447
|
+
"Pulse report currently do not support provided mail host, kindly use either outlook mail or, gmail.",
|
|
448
|
+
),
|
|
530
449
|
);
|
|
531
450
|
process.exit(1);
|
|
532
451
|
}
|
|
533
|
-
|
|
534
452
|
console.log(
|
|
535
453
|
chalk.blue(
|
|
536
|
-
`Using custom credentials from environment variables for ${host}
|
|
537
|
-
)
|
|
454
|
+
`Using custom credentials from environment variables for ${host}.`,
|
|
455
|
+
),
|
|
538
456
|
);
|
|
539
457
|
credentials = {
|
|
540
458
|
username: process.env.PULSE_MAIL_USERNAME,
|
|
@@ -542,19 +460,15 @@ const main = async () => {
|
|
|
542
460
|
host: host,
|
|
543
461
|
};
|
|
544
462
|
} else {
|
|
545
|
-
// Fallback to existing fetch mechanism
|
|
546
463
|
credentials = await fetchCredentials(reportDir);
|
|
547
464
|
if (!credentials) {
|
|
548
465
|
console.warn(
|
|
549
|
-
"Skipping email sending due to missing or failed credential fetch"
|
|
466
|
+
"Skipping email sending due to missing or failed credential fetch",
|
|
550
467
|
);
|
|
551
468
|
return;
|
|
552
469
|
}
|
|
553
|
-
// Mark fetched credentials as gmail by default for compatibility
|
|
554
|
-
credentials.host = "gmail";
|
|
555
470
|
}
|
|
556
|
-
|
|
557
|
-
// Removed await delay(10000); // If not strictly needed, remove it.
|
|
471
|
+
|
|
558
472
|
try {
|
|
559
473
|
await sendEmail(credentials, reportDir);
|
|
560
474
|
} catch (error) {
|