@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 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: playwright-report
252
- path: playwright-report/
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, test.id, browserDetails, test);
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: test.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 === 'flaky' ? outcome : undefined, // Only Include if flaky
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 = test.id.replace(/[^a-zA-Z0-9_-]/g, "_");
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
- attempts.sort((a, b) => a.retries - b.retries);
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 && retryAttempts.some(attempt => attempt.status === 'failed' || attempt.status === 'flaky' || firstAttempt.status === 'failed' || firstAttempt.status === 'flaky');
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 === 'flaky' || lastAttempt.status === 'flaky') {
379
- firstAttempt.outcome = 'flaky';
380
- firstAttempt.status = 'flaky';
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"}
@@ -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.33",
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.2.0",
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.3",
74
+ "nodemailer": "^7.0.12",
75
75
  "patch-package": "^8.0.0",
76
- "sharp": "^0.33.5",
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.0",
94
- "minimatch": "^10.1.2",
95
- "@isaacs/brace-expansion": "^5.0.1"
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
- await animate();
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("\\n"),
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("\\n"),
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("\\n"),
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("\\n"),
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>
@@ -1,20 +1,18 @@
1
1
  #!/usr/bin/env node
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
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, // Renamed to avoid conflict if fs from fs/promises is used
8
- existsSync as fsExistsSync, // Renamed
9
- } from "fs"; // CHANGED for specific functions
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"; // This was missing in your sendReport.js but present in generate-email-report.js and needed for runScript
13
- import "dotenv/config"; // CHANGED for dotenv
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")); // CHANGED
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")); // D
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; // Keep in ms for formatDuration
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), // Pass ms to formatDuration
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"; // Consider passing projectName as an arg or making it a const
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; // Already formatted string
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
- /* CLASSES TO APPLY ANIMATIONS */
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" // Or input_file_0.mjs if you rename it, or input_file_0.js if you configure package.json
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
- resolve();
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 timeoutPromise = new Promise((_, reject) => {
433
- setTimeout(() => {
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 fetchPromise = fetch(
393
+ const response = await fetch(
439
394
  "https://get-credentials.netlify.app/api/getcredentials",
440
395
  {
441
396
  method: "GET",
442
- headers: {
443
- "x-api-key": `${key}`,
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; // Should be unreachable if loop logic is correct
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); // Or handle error appropriately
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
- // --- END MODIFICATION ---
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) {