@checkly/playwright-reporter 0.1.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,56 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ createChecklyReporter: () => createChecklyReporter,
34
+ default: () => index_default
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/extensions/checkly-upload.ts
39
+ var fs5 = __toESM(require("fs"));
40
+ var path4 = __toESM(require("path"));
41
+
42
+ // ../utils/src/ansi.ts
43
+ var ansiRegex = new RegExp(
44
+ [
45
+ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
46
+ "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"
47
+ ].join("|"),
48
+ "g"
49
+ );
50
+
1
51
  // ../utils/src/asset-collector.ts
2
- import * as fs from "fs";
3
- import * as path from "path";
52
+ var fs = __toESM(require("fs"), 1);
53
+ var path = __toESM(require("path"), 1);
4
54
  var AssetCollector = class {
5
55
  constructor(testResultsDir) {
6
56
  this.testResultsDir = testResultsDir;
@@ -234,15 +284,1034 @@ var AssetCollector = class {
234
284
  }
235
285
  };
236
286
 
287
+ // ../utils/src/ci-detector.ts
288
+ function detectCI() {
289
+ if (process.env.GITHUB_ACTIONS === "true") {
290
+ return { environment: "ci", ciProvider: "github-actions", repositoryId: process.env.GITHUB_REPOSITORY };
291
+ }
292
+ if (process.env.GITLAB_CI === "true") {
293
+ return { environment: "ci", ciProvider: "gitlab-ci", repositoryId: process.env.CI_PROJECT_PATH };
294
+ }
295
+ if (process.env.JENKINS_URL) {
296
+ return { environment: "ci", ciProvider: "jenkins", repositoryId: process.env.JOB_NAME };
297
+ }
298
+ if (process.env.CIRCLECI === "true") {
299
+ return { environment: "ci", ciProvider: "circleci", repositoryId: process.env.CIRCLE_PROJECT_REPONAME };
300
+ }
301
+ if (process.env.TRAVIS === "true") {
302
+ return { environment: "ci", ciProvider: "travis-ci", repositoryId: process.env.TRAVIS_REPO_SLUG };
303
+ }
304
+ if (process.env.TF_BUILD === "True") {
305
+ return { environment: "ci", ciProvider: "azure-devops", repositoryId: process.env.BUILD_REPOSITORY_NAME };
306
+ }
307
+ if (process.env.BITBUCKET_PIPELINE_UUID) {
308
+ return { environment: "ci", ciProvider: "bitbucket-pipelines", repositoryId: process.env.BITBUCKET_REPO_SLUG };
309
+ }
310
+ if (process.env.CI === "true" || process.env.CI === "1") {
311
+ return { environment: "ci", ciProvider: "unknown-ci" };
312
+ }
313
+ return { environment: "local", ciProvider: "local" };
314
+ }
315
+
316
+ // ../utils/src/console-adapter.ts
317
+ var import_node_crypto = require("crypto");
318
+ function normalizeType(messageType) {
319
+ switch (messageType.toLowerCase()) {
320
+ case "debug":
321
+ return "debug";
322
+ case "error":
323
+ return "error";
324
+ case "info":
325
+ return "info";
326
+ case "warning":
327
+ case "warn":
328
+ return "warning";
329
+ default:
330
+ return "log";
331
+ }
332
+ }
333
+ function generateId(time, messageType, text, url) {
334
+ return (0, import_node_crypto.createHash)("sha256").update(`${time}-${messageType}-${text}-${url}`).digest("hex").substring(0, 16);
335
+ }
336
+ function toConsoleMessage(event) {
337
+ const url = event.location?.url || "";
338
+ return {
339
+ id: generateId(event.time, event.messageType, event.text, url),
340
+ location: {
341
+ url,
342
+ columnNumber: event.location?.columnNumber || 0,
343
+ lineNumber: event.location?.lineNumber || 0
344
+ },
345
+ text: event.text || "",
346
+ timestamp: event.time,
347
+ type: normalizeType(event.messageType)
348
+ };
349
+ }
350
+
351
+ // ../utils/src/git-info.ts
352
+ function getGitHubRepoInfo() {
353
+ const repository = process.env.GITHUB_REPOSITORY;
354
+ if (!repository) return void 0;
355
+ return {
356
+ repoUrl: `https://github.com/${repository}`,
357
+ commitId: process.env.GITHUB_SHA,
358
+ branchName: process.env.GITHUB_REF_NAME,
359
+ commitOwner: process.env.GITHUB_ACTOR,
360
+ commitMessage: process.env.GITHUB_EVENT_NAME
361
+ };
362
+ }
363
+
364
+ // ../utils/src/machine-id.ts
365
+ var import_node_crypto2 = require("crypto");
366
+ var fs2 = __toESM(require("fs"), 1);
367
+ var os = __toESM(require("os"), 1);
368
+ var path2 = __toESM(require("path"), 1);
369
+
370
+ // ../utils/src/network-adapter.ts
371
+ var import_node_crypto3 = require("crypto");
372
+ function generateId2(url, method, startedAt) {
373
+ return (0, import_node_crypto3.createHash)("sha256").update(`${url}-${method}-${startedAt}`).digest("hex").substring(0, 16);
374
+ }
375
+ function extractDomain(url) {
376
+ try {
377
+ return new URL(url).hostname;
378
+ } catch {
379
+ return "";
380
+ }
381
+ }
382
+ function headersArrayToRecord(headers) {
383
+ const record = {};
384
+ for (const { name, value } of headers) {
385
+ record[name.toLowerCase()] = value;
386
+ }
387
+ return record;
388
+ }
389
+ function isSuccessStatus(status) {
390
+ return status >= 200 && status < 400;
391
+ }
392
+ function determineResourceType(snapshot) {
393
+ if (snapshot._resourceType) {
394
+ return snapshot._resourceType;
395
+ }
396
+ if (snapshot._apiRequest) {
397
+ return "fetch";
398
+ }
399
+ return "other";
400
+ }
401
+ function toNetworkRequest(event) {
402
+ const { snapshot } = event;
403
+ const startedAt = new Date(snapshot.startedDateTime).getTime();
404
+ const time = Math.round(snapshot.time);
405
+ const finishedAt = startedAt + time;
406
+ const statusCode = snapshot.response.status;
407
+ const url = snapshot.request.url;
408
+ const method = snapshot.request.method;
409
+ return {
410
+ id: generateId2(url, method, startedAt),
411
+ url,
412
+ domain: extractDomain(url),
413
+ method,
414
+ resourceType: determineResourceType(snapshot),
415
+ statusCode,
416
+ statusText: snapshot.response.statusText || "",
417
+ start: startedAt,
418
+ startedAt,
419
+ finishedAt,
420
+ time,
421
+ hasFinished: true,
422
+ hasSucceeded: isSuccessStatus(statusCode),
423
+ requestHeaders: headersArrayToRecord(snapshot.request.headers || []),
424
+ responseHeaders: headersArrayToRecord(snapshot.response.headers || []),
425
+ transferBytes: snapshot.response._transferSize,
426
+ resourceBytes: snapshot.response.content?.size
427
+ };
428
+ }
429
+
430
+ // ../utils/src/trace-reader.ts
431
+ var fs3 = __toESM(require("fs"), 1);
432
+
433
+ // ../utils/src/zip-reader.ts
434
+ var import_node_util = require("util");
435
+ var zlib = __toESM(require("zlib"), 1);
436
+ var gunzip2 = (0, import_node_util.promisify)(zlib.gunzip);
437
+ var inflateRaw2 = (0, import_node_util.promisify)(zlib.inflateRaw);
438
+ function parseZipEntries(zipBuffer) {
439
+ const EOCD_SIG = 101010256;
440
+ let eocdOffset = -1;
441
+ for (let i = zipBuffer.length - 22; i >= 0; i--) {
442
+ if (zipBuffer.readUInt32LE(i) === EOCD_SIG) {
443
+ eocdOffset = i;
444
+ break;
445
+ }
446
+ }
447
+ if (eocdOffset === -1) {
448
+ return [];
449
+ }
450
+ const cdOffset = zipBuffer.readUInt32LE(eocdOffset + 16);
451
+ const cdEntries = zipBuffer.readUInt16LE(eocdOffset + 10);
452
+ const entries = [];
453
+ const CD_SIG = 33639248;
454
+ let offset = cdOffset;
455
+ for (let i = 0; i < cdEntries; i++) {
456
+ if (zipBuffer.readUInt32LE(offset) !== CD_SIG) {
457
+ break;
458
+ }
459
+ const compressionMethod = zipBuffer.readUInt16LE(offset + 10);
460
+ const compressedSize = zipBuffer.readUInt32LE(offset + 20);
461
+ const fileNameLength = zipBuffer.readUInt16LE(offset + 28);
462
+ const extraFieldLength = zipBuffer.readUInt16LE(offset + 30);
463
+ const commentLength = zipBuffer.readUInt16LE(offset + 32);
464
+ const localHeaderOffset = zipBuffer.readUInt32LE(offset + 42);
465
+ const fileName = zipBuffer.subarray(offset + 46, offset + 46 + fileNameLength).toString("utf-8");
466
+ entries.push({
467
+ fileName,
468
+ compressionMethod,
469
+ compressedSize,
470
+ localHeaderOffset
471
+ });
472
+ offset += 46 + fileNameLength + extraFieldLength + commentLength;
473
+ }
474
+ return entries;
475
+ }
476
+ async function readZipEntryContent(zipBuffer, entry) {
477
+ const LOCAL_SIG = 67324752;
478
+ if (zipBuffer.readUInt32LE(entry.localHeaderOffset) !== LOCAL_SIG) {
479
+ return null;
480
+ }
481
+ const localFileNameLength = zipBuffer.readUInt16LE(entry.localHeaderOffset + 26);
482
+ const localExtraLength = zipBuffer.readUInt16LE(entry.localHeaderOffset + 28);
483
+ const dataOffset = entry.localHeaderOffset + 30 + localFileNameLength + localExtraLength;
484
+ const compressedData = zipBuffer.subarray(dataOffset, dataOffset + entry.compressedSize);
485
+ let buffer;
486
+ if (entry.compressionMethod === 0) {
487
+ buffer = compressedData;
488
+ } else if (entry.compressionMethod === 8) {
489
+ buffer = await inflateRaw2(compressedData);
490
+ } else {
491
+ return null;
492
+ }
493
+ if (buffer.length >= 2 && buffer[0] === 31 && buffer[1] === 139) {
494
+ const decompressed = await gunzip2(buffer);
495
+ return decompressed.toString("utf-8");
496
+ }
497
+ return buffer.toString("utf-8");
498
+ }
499
+
500
+ // ../utils/src/trace-reader.ts
501
+ var TraceReader = class {
502
+ constructor(tracePath) {
503
+ this.tracePath = tracePath;
504
+ }
505
+ zipBuffer = null;
506
+ traceEntries = [];
507
+ async open() {
508
+ if (!fs3.existsSync(this.tracePath)) {
509
+ return false;
510
+ }
511
+ try {
512
+ this.zipBuffer = fs3.readFileSync(this.tracePath);
513
+ const entries = parseZipEntries(this.zipBuffer);
514
+ this.traceEntries = entries.filter(
515
+ (e) => (/^\d+-trace\.trace$/.test(e.fileName) || /^\d+-trace\.network$/.test(e.fileName)) && !e.fileName.includes("/")
516
+ );
517
+ return this.traceEntries.length > 0;
518
+ } catch {
519
+ return false;
520
+ }
521
+ }
522
+ /**
523
+ * Extracts events matching a text filter.
524
+ *
525
+ * @param textFilter - Substring to match (e.g., '"type":"console"')
526
+ * @param adapter - Optional adapter to transform events
527
+ */
528
+ async extractEvents(textFilter, adapter) {
529
+ if (!this.zipBuffer) {
530
+ throw new Error("TraceReader not opened. Call open() first.");
531
+ }
532
+ const results = [];
533
+ for (const traceEntry of this.traceEntries) {
534
+ const content = await readZipEntryContent(this.zipBuffer, traceEntry);
535
+ if (!content) continue;
536
+ for (const line of content.split("\n")) {
537
+ if (line.indexOf(textFilter) !== -1) {
538
+ try {
539
+ const event = JSON.parse(line);
540
+ results.push(adapter ? adapter(event) : event);
541
+ } catch {
542
+ }
543
+ }
544
+ }
545
+ }
546
+ if (results.length > 0 && typeof results[0].time === "number") {
547
+ return results.sort((a, b) => a.time - b.time);
548
+ }
549
+ return results;
550
+ }
551
+ /**
552
+ * Extracts all events from the trace.
553
+ */
554
+ async extractAllEvents(adapter) {
555
+ if (!this.zipBuffer) {
556
+ throw new Error("TraceReader not opened. Call open() first.");
557
+ }
558
+ const results = [];
559
+ for (const traceEntry of this.traceEntries) {
560
+ const content = await readZipEntryContent(this.zipBuffer, traceEntry);
561
+ if (!content) continue;
562
+ for (const line of content.split("\n")) {
563
+ if (line.trim()) {
564
+ try {
565
+ const event = JSON.parse(line);
566
+ results.push(adapter ? adapter(event) : event);
567
+ } catch {
568
+ }
569
+ }
570
+ }
571
+ }
572
+ if (results.length > 0 && typeof results[0].time === "number") {
573
+ return results.sort((a, b) => a.time - b.time);
574
+ }
575
+ return results;
576
+ }
577
+ listFiles() {
578
+ if (!this.zipBuffer) {
579
+ return [];
580
+ }
581
+ return parseZipEntries(this.zipBuffer).map((e) => e.fileName);
582
+ }
583
+ isOpen() {
584
+ return this.zipBuffer !== null;
585
+ }
586
+ };
587
+
588
+ // ../utils/src/zipper.ts
589
+ var fs4 = __toESM(require("fs"), 1);
590
+ var os2 = __toESM(require("os"), 1);
591
+ var path3 = __toESM(require("path"), 1);
592
+
593
+ // ../../node_modules/.pnpm/fflate@0.8.2/node_modules/fflate/esm/index.mjs
594
+ var import_module = require("module");
595
+ var require2 = (0, import_module.createRequire)("/");
596
+ var Worker;
597
+ try {
598
+ Worker = require2("worker_threads").Worker;
599
+ } catch (e) {
600
+ }
601
+ var u8 = Uint8Array;
602
+ var u16 = Uint16Array;
603
+ var i32 = Int32Array;
604
+ var fleb = new u8([
605
+ 0,
606
+ 0,
607
+ 0,
608
+ 0,
609
+ 0,
610
+ 0,
611
+ 0,
612
+ 0,
613
+ 1,
614
+ 1,
615
+ 1,
616
+ 1,
617
+ 2,
618
+ 2,
619
+ 2,
620
+ 2,
621
+ 3,
622
+ 3,
623
+ 3,
624
+ 3,
625
+ 4,
626
+ 4,
627
+ 4,
628
+ 4,
629
+ 5,
630
+ 5,
631
+ 5,
632
+ 5,
633
+ 0,
634
+ /* unused */
635
+ 0,
636
+ 0,
637
+ /* impossible */
638
+ 0
639
+ ]);
640
+ var fdeb = new u8([
641
+ 0,
642
+ 0,
643
+ 0,
644
+ 0,
645
+ 1,
646
+ 1,
647
+ 2,
648
+ 2,
649
+ 3,
650
+ 3,
651
+ 4,
652
+ 4,
653
+ 5,
654
+ 5,
655
+ 6,
656
+ 6,
657
+ 7,
658
+ 7,
659
+ 8,
660
+ 8,
661
+ 9,
662
+ 9,
663
+ 10,
664
+ 10,
665
+ 11,
666
+ 11,
667
+ 12,
668
+ 12,
669
+ 13,
670
+ 13,
671
+ /* unused */
672
+ 0,
673
+ 0
674
+ ]);
675
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
676
+ var freb = function(eb, start) {
677
+ var b = new u16(31);
678
+ for (var i = 0; i < 31; ++i) {
679
+ b[i] = start += 1 << eb[i - 1];
680
+ }
681
+ var r = new i32(b[30]);
682
+ for (var i = 1; i < 30; ++i) {
683
+ for (var j = b[i]; j < b[i + 1]; ++j) {
684
+ r[j] = j - b[i] << 5 | i;
685
+ }
686
+ }
687
+ return { b, r };
688
+ };
689
+ var _a = freb(fleb, 2);
690
+ var fl = _a.b;
691
+ var revfl = _a.r;
692
+ fl[28] = 258, revfl[258] = 28;
693
+ var _b = freb(fdeb, 0);
694
+ var fd = _b.b;
695
+ var revfd = _b.r;
696
+ var rev = new u16(32768);
697
+ for (i = 0; i < 32768; ++i) {
698
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
699
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
700
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
701
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
702
+ }
703
+ var x;
704
+ var i;
705
+ var hMap = (function(cd, mb, r) {
706
+ var s = cd.length;
707
+ var i = 0;
708
+ var l = new u16(mb);
709
+ for (; i < s; ++i) {
710
+ if (cd[i])
711
+ ++l[cd[i] - 1];
712
+ }
713
+ var le = new u16(mb);
714
+ for (i = 1; i < mb; ++i) {
715
+ le[i] = le[i - 1] + l[i - 1] << 1;
716
+ }
717
+ var co;
718
+ if (r) {
719
+ co = new u16(1 << mb);
720
+ var rvb = 15 - mb;
721
+ for (i = 0; i < s; ++i) {
722
+ if (cd[i]) {
723
+ var sv = i << 4 | cd[i];
724
+ var r_1 = mb - cd[i];
725
+ var v = le[cd[i] - 1]++ << r_1;
726
+ for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
727
+ co[rev[v] >> rvb] = sv;
728
+ }
729
+ }
730
+ }
731
+ } else {
732
+ co = new u16(s);
733
+ for (i = 0; i < s; ++i) {
734
+ if (cd[i]) {
735
+ co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
736
+ }
737
+ }
738
+ }
739
+ return co;
740
+ });
741
+ var flt = new u8(288);
742
+ for (i = 0; i < 144; ++i)
743
+ flt[i] = 8;
744
+ var i;
745
+ for (i = 144; i < 256; ++i)
746
+ flt[i] = 9;
747
+ var i;
748
+ for (i = 256; i < 280; ++i)
749
+ flt[i] = 7;
750
+ var i;
751
+ for (i = 280; i < 288; ++i)
752
+ flt[i] = 8;
753
+ var i;
754
+ var fdt = new u8(32);
755
+ for (i = 0; i < 32; ++i)
756
+ fdt[i] = 5;
757
+ var i;
758
+ var flm = /* @__PURE__ */ hMap(flt, 9, 0);
759
+ var fdm = /* @__PURE__ */ hMap(fdt, 5, 0);
760
+ var shft = function(p) {
761
+ return (p + 7) / 8 | 0;
762
+ };
763
+ var slc = function(v, s, e) {
764
+ if (s == null || s < 0)
765
+ s = 0;
766
+ if (e == null || e > v.length)
767
+ e = v.length;
768
+ return new u8(v.subarray(s, e));
769
+ };
770
+ var ec = [
771
+ "unexpected EOF",
772
+ "invalid block type",
773
+ "invalid length/literal",
774
+ "invalid distance",
775
+ "stream finished",
776
+ "no stream handler",
777
+ ,
778
+ "no callback",
779
+ "invalid UTF-8 data",
780
+ "extra field too long",
781
+ "date not in range 1980-2099",
782
+ "filename too long",
783
+ "stream finishing",
784
+ "invalid zip data"
785
+ // determined by unknown compression method
786
+ ];
787
+ var err = function(ind, msg, nt) {
788
+ var e = new Error(msg || ec[ind]);
789
+ e.code = ind;
790
+ if (Error.captureStackTrace)
791
+ Error.captureStackTrace(e, err);
792
+ if (!nt)
793
+ throw e;
794
+ return e;
795
+ };
796
+ var wbits = function(d, p, v) {
797
+ v <<= p & 7;
798
+ var o = p / 8 | 0;
799
+ d[o] |= v;
800
+ d[o + 1] |= v >> 8;
801
+ };
802
+ var wbits16 = function(d, p, v) {
803
+ v <<= p & 7;
804
+ var o = p / 8 | 0;
805
+ d[o] |= v;
806
+ d[o + 1] |= v >> 8;
807
+ d[o + 2] |= v >> 16;
808
+ };
809
+ var hTree = function(d, mb) {
810
+ var t = [];
811
+ for (var i = 0; i < d.length; ++i) {
812
+ if (d[i])
813
+ t.push({ s: i, f: d[i] });
814
+ }
815
+ var s = t.length;
816
+ var t2 = t.slice();
817
+ if (!s)
818
+ return { t: et, l: 0 };
819
+ if (s == 1) {
820
+ var v = new u8(t[0].s + 1);
821
+ v[t[0].s] = 1;
822
+ return { t: v, l: 1 };
823
+ }
824
+ t.sort(function(a, b) {
825
+ return a.f - b.f;
826
+ });
827
+ t.push({ s: -1, f: 25001 });
828
+ var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
829
+ t[0] = { s: -1, f: l.f + r.f, l, r };
830
+ while (i1 != s - 1) {
831
+ l = t[t[i0].f < t[i2].f ? i0++ : i2++];
832
+ r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
833
+ t[i1++] = { s: -1, f: l.f + r.f, l, r };
834
+ }
835
+ var maxSym = t2[0].s;
836
+ for (var i = 1; i < s; ++i) {
837
+ if (t2[i].s > maxSym)
838
+ maxSym = t2[i].s;
839
+ }
840
+ var tr = new u16(maxSym + 1);
841
+ var mbt = ln(t[i1 - 1], tr, 0);
842
+ if (mbt > mb) {
843
+ var i = 0, dt = 0;
844
+ var lft = mbt - mb, cst = 1 << lft;
845
+ t2.sort(function(a, b) {
846
+ return tr[b.s] - tr[a.s] || a.f - b.f;
847
+ });
848
+ for (; i < s; ++i) {
849
+ var i2_1 = t2[i].s;
850
+ if (tr[i2_1] > mb) {
851
+ dt += cst - (1 << mbt - tr[i2_1]);
852
+ tr[i2_1] = mb;
853
+ } else
854
+ break;
855
+ }
856
+ dt >>= lft;
857
+ while (dt > 0) {
858
+ var i2_2 = t2[i].s;
859
+ if (tr[i2_2] < mb)
860
+ dt -= 1 << mb - tr[i2_2]++ - 1;
861
+ else
862
+ ++i;
863
+ }
864
+ for (; i >= 0 && dt; --i) {
865
+ var i2_3 = t2[i].s;
866
+ if (tr[i2_3] == mb) {
867
+ --tr[i2_3];
868
+ ++dt;
869
+ }
870
+ }
871
+ mbt = mb;
872
+ }
873
+ return { t: new u8(tr), l: mbt };
874
+ };
875
+ var ln = function(n, l, d) {
876
+ return n.s == -1 ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) : l[n.s] = d;
877
+ };
878
+ var lc = function(c) {
879
+ var s = c.length;
880
+ while (s && !c[--s])
881
+ ;
882
+ var cl = new u16(++s);
883
+ var cli = 0, cln = c[0], cls = 1;
884
+ var w = function(v) {
885
+ cl[cli++] = v;
886
+ };
887
+ for (var i = 1; i <= s; ++i) {
888
+ if (c[i] == cln && i != s)
889
+ ++cls;
890
+ else {
891
+ if (!cln && cls > 2) {
892
+ for (; cls > 138; cls -= 138)
893
+ w(32754);
894
+ if (cls > 2) {
895
+ w(cls > 10 ? cls - 11 << 5 | 28690 : cls - 3 << 5 | 12305);
896
+ cls = 0;
897
+ }
898
+ } else if (cls > 3) {
899
+ w(cln), --cls;
900
+ for (; cls > 6; cls -= 6)
901
+ w(8304);
902
+ if (cls > 2)
903
+ w(cls - 3 << 5 | 8208), cls = 0;
904
+ }
905
+ while (cls--)
906
+ w(cln);
907
+ cls = 1;
908
+ cln = c[i];
909
+ }
910
+ }
911
+ return { c: cl.subarray(0, cli), n: s };
912
+ };
913
+ var clen = function(cf, cl) {
914
+ var l = 0;
915
+ for (var i = 0; i < cl.length; ++i)
916
+ l += cf[i] * cl[i];
917
+ return l;
918
+ };
919
+ var wfblk = function(out, pos, dat) {
920
+ var s = dat.length;
921
+ var o = shft(pos + 2);
922
+ out[o] = s & 255;
923
+ out[o + 1] = s >> 8;
924
+ out[o + 2] = out[o] ^ 255;
925
+ out[o + 3] = out[o + 1] ^ 255;
926
+ for (var i = 0; i < s; ++i)
927
+ out[o + i + 4] = dat[i];
928
+ return (o + 4 + s) * 8;
929
+ };
930
+ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
931
+ wbits(out, p++, final);
932
+ ++lf[256];
933
+ var _a2 = hTree(lf, 15), dlt = _a2.t, mlb = _a2.l;
934
+ var _b2 = hTree(df, 15), ddt = _b2.t, mdb = _b2.l;
935
+ var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
936
+ var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
937
+ var lcfreq = new u16(19);
938
+ for (var i = 0; i < lclt.length; ++i)
939
+ ++lcfreq[lclt[i] & 31];
940
+ for (var i = 0; i < lcdt.length; ++i)
941
+ ++lcfreq[lcdt[i] & 31];
942
+ var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
943
+ var nlcc = 19;
944
+ for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
945
+ ;
946
+ var flen = bl + 5 << 3;
947
+ var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
948
+ var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
949
+ if (bs >= 0 && flen <= ftlen && flen <= dtlen)
950
+ return wfblk(out, p, dat.subarray(bs, bs + bl));
951
+ var lm, ll, dm, dl;
952
+ wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
953
+ if (dtlen < ftlen) {
954
+ lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
955
+ var llm = hMap(lct, mlcb, 0);
956
+ wbits(out, p, nlc - 257);
957
+ wbits(out, p + 5, ndc - 1);
958
+ wbits(out, p + 10, nlcc - 4);
959
+ p += 14;
960
+ for (var i = 0; i < nlcc; ++i)
961
+ wbits(out, p + 3 * i, lct[clim[i]]);
962
+ p += 3 * nlcc;
963
+ var lcts = [lclt, lcdt];
964
+ for (var it = 0; it < 2; ++it) {
965
+ var clct = lcts[it];
966
+ for (var i = 0; i < clct.length; ++i) {
967
+ var len = clct[i] & 31;
968
+ wbits(out, p, llm[len]), p += lct[len];
969
+ if (len > 15)
970
+ wbits(out, p, clct[i] >> 5 & 127), p += clct[i] >> 12;
971
+ }
972
+ }
973
+ } else {
974
+ lm = flm, ll = flt, dm = fdm, dl = fdt;
975
+ }
976
+ for (var i = 0; i < li; ++i) {
977
+ var sym = syms[i];
978
+ if (sym > 255) {
979
+ var len = sym >> 18 & 31;
980
+ wbits16(out, p, lm[len + 257]), p += ll[len + 257];
981
+ if (len > 7)
982
+ wbits(out, p, sym >> 23 & 31), p += fleb[len];
983
+ var dst = sym & 31;
984
+ wbits16(out, p, dm[dst]), p += dl[dst];
985
+ if (dst > 3)
986
+ wbits16(out, p, sym >> 5 & 8191), p += fdeb[dst];
987
+ } else {
988
+ wbits16(out, p, lm[sym]), p += ll[sym];
989
+ }
990
+ }
991
+ wbits16(out, p, lm[256]);
992
+ return p + ll[256];
993
+ };
994
+ var deo = /* @__PURE__ */ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
995
+ var et = /* @__PURE__ */ new u8(0);
996
+ var dflt = function(dat, lvl, plvl, pre, post, st) {
997
+ var s = st.z || dat.length;
998
+ var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7e3)) + post);
999
+ var w = o.subarray(pre, o.length - post);
1000
+ var lst = st.l;
1001
+ var pos = (st.r || 0) & 7;
1002
+ if (lvl) {
1003
+ if (pos)
1004
+ w[0] = st.r >> 3;
1005
+ var opt = deo[lvl - 1];
1006
+ var n = opt >> 13, c = opt & 8191;
1007
+ var msk_1 = (1 << plvl) - 1;
1008
+ var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
1009
+ var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
1010
+ var hsh = function(i2) {
1011
+ return (dat[i2] ^ dat[i2 + 1] << bs1_1 ^ dat[i2 + 2] << bs2_1) & msk_1;
1012
+ };
1013
+ var syms = new i32(25e3);
1014
+ var lf = new u16(288), df = new u16(32);
1015
+ var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
1016
+ for (; i + 2 < s; ++i) {
1017
+ var hv = hsh(i);
1018
+ var imod = i & 32767, pimod = head[hv];
1019
+ prev[imod] = pimod;
1020
+ head[hv] = imod;
1021
+ if (wi <= i) {
1022
+ var rem = s - i;
1023
+ if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
1024
+ pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
1025
+ li = lc_1 = eb = 0, bs = i;
1026
+ for (var j = 0; j < 286; ++j)
1027
+ lf[j] = 0;
1028
+ for (var j = 0; j < 30; ++j)
1029
+ df[j] = 0;
1030
+ }
1031
+ var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
1032
+ if (rem > 2 && hv == hsh(i - dif)) {
1033
+ var maxn = Math.min(n, rem) - 1;
1034
+ var maxd = Math.min(32767, i);
1035
+ var ml = Math.min(258, rem);
1036
+ while (dif <= maxd && --ch_1 && imod != pimod) {
1037
+ if (dat[i + l] == dat[i + l - dif]) {
1038
+ var nl = 0;
1039
+ for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
1040
+ ;
1041
+ if (nl > l) {
1042
+ l = nl, d = dif;
1043
+ if (nl > maxn)
1044
+ break;
1045
+ var mmd = Math.min(dif, nl - 2);
1046
+ var md = 0;
1047
+ for (var j = 0; j < mmd; ++j) {
1048
+ var ti = i - dif + j & 32767;
1049
+ var pti = prev[ti];
1050
+ var cd = ti - pti & 32767;
1051
+ if (cd > md)
1052
+ md = cd, pimod = ti;
1053
+ }
1054
+ }
1055
+ }
1056
+ imod = pimod, pimod = prev[imod];
1057
+ dif += imod - pimod & 32767;
1058
+ }
1059
+ }
1060
+ if (d) {
1061
+ syms[li++] = 268435456 | revfl[l] << 18 | revfd[d];
1062
+ var lin = revfl[l] & 31, din = revfd[d] & 31;
1063
+ eb += fleb[lin] + fdeb[din];
1064
+ ++lf[257 + lin];
1065
+ ++df[din];
1066
+ wi = i + l;
1067
+ ++lc_1;
1068
+ } else {
1069
+ syms[li++] = dat[i];
1070
+ ++lf[dat[i]];
1071
+ }
1072
+ }
1073
+ }
1074
+ for (i = Math.max(i, wi); i < s; ++i) {
1075
+ syms[li++] = dat[i];
1076
+ ++lf[dat[i]];
1077
+ }
1078
+ pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
1079
+ if (!lst) {
1080
+ st.r = pos & 7 | w[pos / 8 | 0] << 3;
1081
+ pos -= 7;
1082
+ st.h = head, st.p = prev, st.i = i, st.w = wi;
1083
+ }
1084
+ } else {
1085
+ for (var i = st.w || 0; i < s + lst; i += 65535) {
1086
+ var e = i + 65535;
1087
+ if (e >= s) {
1088
+ w[pos / 8 | 0] = lst;
1089
+ e = s;
1090
+ }
1091
+ pos = wfblk(w, pos + 1, dat.subarray(i, e));
1092
+ }
1093
+ st.i = s;
1094
+ }
1095
+ return slc(o, 0, pre + shft(pos) + post);
1096
+ };
1097
+ var crct = /* @__PURE__ */ (function() {
1098
+ var t = new Int32Array(256);
1099
+ for (var i = 0; i < 256; ++i) {
1100
+ var c = i, k = 9;
1101
+ while (--k)
1102
+ c = (c & 1 && -306674912) ^ c >>> 1;
1103
+ t[i] = c;
1104
+ }
1105
+ return t;
1106
+ })();
1107
+ var crc = function() {
1108
+ var c = -1;
1109
+ return {
1110
+ p: function(d) {
1111
+ var cr = c;
1112
+ for (var i = 0; i < d.length; ++i)
1113
+ cr = crct[cr & 255 ^ d[i]] ^ cr >>> 8;
1114
+ c = cr;
1115
+ },
1116
+ d: function() {
1117
+ return ~c;
1118
+ }
1119
+ };
1120
+ };
1121
+ var dopt = function(dat, opt, pre, post, st) {
1122
+ if (!st) {
1123
+ st = { l: 1 };
1124
+ if (opt.dictionary) {
1125
+ var dict = opt.dictionary.subarray(-32768);
1126
+ var newDat = new u8(dict.length + dat.length);
1127
+ newDat.set(dict);
1128
+ newDat.set(dat, dict.length);
1129
+ dat = newDat;
1130
+ st.w = dict.length;
1131
+ }
1132
+ }
1133
+ return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20 : 12 + opt.mem, pre, post, st);
1134
+ };
1135
+ var mrg = function(a, b) {
1136
+ var o = {};
1137
+ for (var k in a)
1138
+ o[k] = a[k];
1139
+ for (var k in b)
1140
+ o[k] = b[k];
1141
+ return o;
1142
+ };
1143
+ var wbytes = function(d, b, v) {
1144
+ for (; v; ++b)
1145
+ d[b] = v, v >>>= 8;
1146
+ };
1147
+ function deflateSync(data, opts) {
1148
+ return dopt(data, opts || {}, 0, 0);
1149
+ }
1150
+ var fltn = function(d, p, t, o) {
1151
+ for (var k in d) {
1152
+ var val = d[k], n = p + k, op = o;
1153
+ if (Array.isArray(val))
1154
+ op = mrg(o, val[1]), val = val[0];
1155
+ if (val instanceof u8)
1156
+ t[n] = [val, op];
1157
+ else {
1158
+ t[n += "/"] = [new u8(0), op];
1159
+ fltn(val, n, t, o);
1160
+ }
1161
+ }
1162
+ };
1163
+ var te = typeof TextEncoder != "undefined" && /* @__PURE__ */ new TextEncoder();
1164
+ var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder();
1165
+ var tds = 0;
1166
+ try {
1167
+ td.decode(et, { stream: true });
1168
+ tds = 1;
1169
+ } catch (e) {
1170
+ }
1171
+ function strToU8(str, latin1) {
1172
+ if (latin1) {
1173
+ var ar_1 = new u8(str.length);
1174
+ for (var i = 0; i < str.length; ++i)
1175
+ ar_1[i] = str.charCodeAt(i);
1176
+ return ar_1;
1177
+ }
1178
+ if (te)
1179
+ return te.encode(str);
1180
+ var l = str.length;
1181
+ var ar = new u8(str.length + (str.length >> 1));
1182
+ var ai = 0;
1183
+ var w = function(v) {
1184
+ ar[ai++] = v;
1185
+ };
1186
+ for (var i = 0; i < l; ++i) {
1187
+ if (ai + 5 > ar.length) {
1188
+ var n = new u8(ai + 8 + (l - i << 1));
1189
+ n.set(ar);
1190
+ ar = n;
1191
+ }
1192
+ var c = str.charCodeAt(i);
1193
+ if (c < 128 || latin1)
1194
+ w(c);
1195
+ else if (c < 2048)
1196
+ w(192 | c >> 6), w(128 | c & 63);
1197
+ else if (c > 55295 && c < 57344)
1198
+ c = 65536 + (c & 1023 << 10) | str.charCodeAt(++i) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63);
1199
+ else
1200
+ w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
1201
+ }
1202
+ return slc(ar, 0, ai);
1203
+ }
1204
+ var exfl = function(ex) {
1205
+ var le = 0;
1206
+ if (ex) {
1207
+ for (var k in ex) {
1208
+ var l = ex[k].length;
1209
+ if (l > 65535)
1210
+ err(9);
1211
+ le += l + 4;
1212
+ }
1213
+ }
1214
+ return le;
1215
+ };
1216
+ var wzh = function(d, b, f, fn, u, c, ce, co) {
1217
+ var fl2 = fn.length, ex = f.extra, col = co && co.length;
1218
+ var exl = exfl(ex);
1219
+ wbytes(d, b, ce != null ? 33639248 : 67324752), b += 4;
1220
+ if (ce != null)
1221
+ d[b++] = 20, d[b++] = f.os;
1222
+ d[b] = 20, b += 2;
1223
+ d[b++] = f.flag << 1 | (c < 0 && 8), d[b++] = u && 8;
1224
+ d[b++] = f.compression & 255, d[b++] = f.compression >> 8;
1225
+ var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980;
1226
+ if (y < 0 || y > 119)
1227
+ err(10);
1228
+ wbytes(d, b, y << 25 | dt.getMonth() + 1 << 21 | dt.getDate() << 16 | dt.getHours() << 11 | dt.getMinutes() << 5 | dt.getSeconds() >> 1), b += 4;
1229
+ if (c != -1) {
1230
+ wbytes(d, b, f.crc);
1231
+ wbytes(d, b + 4, c < 0 ? -c - 2 : c);
1232
+ wbytes(d, b + 8, f.size);
1233
+ }
1234
+ wbytes(d, b + 12, fl2);
1235
+ wbytes(d, b + 14, exl), b += 16;
1236
+ if (ce != null) {
1237
+ wbytes(d, b, col);
1238
+ wbytes(d, b + 6, f.attrs);
1239
+ wbytes(d, b + 10, ce), b += 14;
1240
+ }
1241
+ d.set(fn, b);
1242
+ b += fl2;
1243
+ if (exl) {
1244
+ for (var k in ex) {
1245
+ var exf = ex[k], l = exf.length;
1246
+ wbytes(d, b, +k);
1247
+ wbytes(d, b + 2, l);
1248
+ d.set(exf, b + 4), b += 4 + l;
1249
+ }
1250
+ }
1251
+ if (col)
1252
+ d.set(co, b), b += col;
1253
+ return b;
1254
+ };
1255
+ var wzf = function(o, b, c, d, e) {
1256
+ wbytes(o, b, 101010256);
1257
+ wbytes(o, b + 8, c);
1258
+ wbytes(o, b + 10, c);
1259
+ wbytes(o, b + 12, d);
1260
+ wbytes(o, b + 16, e);
1261
+ };
1262
+ function zipSync(data, opts) {
1263
+ if (!opts)
1264
+ opts = {};
1265
+ var r = {};
1266
+ var files = [];
1267
+ fltn(data, "", r, opts);
1268
+ var o = 0;
1269
+ var tot = 0;
1270
+ for (var fn in r) {
1271
+ var _a2 = r[fn], file = _a2[0], p = _a2[1];
1272
+ var compression = p.level == 0 ? 0 : 8;
1273
+ var f = strToU8(fn), s = f.length;
1274
+ var com = p.comment, m = com && strToU8(com), ms = m && m.length;
1275
+ var exl = exfl(p.extra);
1276
+ if (s > 65535)
1277
+ err(11);
1278
+ var d = compression ? deflateSync(file, p) : file, l = d.length;
1279
+ var c = crc();
1280
+ c.p(file);
1281
+ files.push(mrg(p, {
1282
+ size: file.length,
1283
+ crc: c.d(),
1284
+ c: d,
1285
+ f,
1286
+ m,
1287
+ u: s != fn.length || m && com.length != ms,
1288
+ o,
1289
+ compression
1290
+ }));
1291
+ o += 30 + s + exl + l;
1292
+ tot += 76 + 2 * (s + exl) + (ms || 0) + l;
1293
+ }
1294
+ var out = new u8(tot + 22), oe = o, cdl = tot - o;
1295
+ for (var i = 0; i < files.length; ++i) {
1296
+ var f = files[i];
1297
+ wzh(out, f.o, f, f.f, f.u, f.c.length);
1298
+ var badd = 30 + f.f.length + exfl(f.extra);
1299
+ out.set(f.c, f.o + badd);
1300
+ wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);
1301
+ }
1302
+ wzf(out, o, files.length, cdl, oe);
1303
+ return out;
1304
+ }
1305
+
237
1306
  // ../utils/src/zipper.ts
238
- import * as fs2 from "fs";
239
- import * as os from "os";
240
- import * as path2 from "path";
241
- import { ZipArchive } from "archiver";
242
1307
  var Zipper = class {
243
1308
  outputPath;
1309
+ compressionLevel;
1310
+ onProgress;
244
1311
  constructor(options) {
245
1312
  this.outputPath = options.outputPath;
1313
+ this.compressionLevel = options.compressionLevel ?? 0;
1314
+ this.onProgress = options.onProgress;
246
1315
  }
247
1316
  /**
248
1317
  * Creates a ZIP archive containing the JSON report and assets
@@ -251,76 +1320,105 @@ var Zipper = class {
251
1320
  * @returns ZIP creation result with metadata
252
1321
  */
253
1322
  async createZip(reportPath, assets) {
1323
+ if (!fs4.existsSync(reportPath)) {
1324
+ throw new Error(`Report file not found: ${reportPath}`);
1325
+ }
1326
+ const transformedReportPath = this.transformJsonReport(reportPath);
1327
+ const files = {};
1328
+ const totalEntries = assets.length + 1;
1329
+ let processedEntries = 0;
1330
+ const reportContent = fs4.readFileSync(transformedReportPath);
1331
+ files["output/playwright-test-report.json"] = new Uint8Array(reportContent);
1332
+ processedEntries++;
1333
+ this.onProgress?.({ processedEntries, totalEntries, currentFile: "output/playwright-test-report.json" });
1334
+ for (const asset of assets) {
1335
+ if (!fs4.existsSync(asset.sourcePath)) {
1336
+ console.warn(`[Checkly Reporter] Skipping missing asset: ${asset.sourcePath}`);
1337
+ continue;
1338
+ }
1339
+ const content = fs4.readFileSync(asset.sourcePath);
1340
+ files[asset.archivePath] = new Uint8Array(content);
1341
+ processedEntries++;
1342
+ this.onProgress?.({ processedEntries, totalEntries, currentFile: asset.archivePath });
1343
+ }
1344
+ const zipData = zipSync(files, { level: this.compressionLevel });
1345
+ fs4.writeFileSync(this.outputPath, zipData);
1346
+ try {
1347
+ fs4.unlinkSync(transformedReportPath);
1348
+ } catch {
1349
+ }
1350
+ const entries = this.extractEntryOffsets(zipData);
1351
+ return {
1352
+ zipPath: this.outputPath,
1353
+ size: zipData.length,
1354
+ entryCount: entries.length,
1355
+ entries
1356
+ };
1357
+ }
1358
+ /**
1359
+ * Extracts byte offsets for each entry from the ZIP data
1360
+ * Parses the central directory to get accurate offset information
1361
+ */
1362
+ extractEntryOffsets(zipData) {
254
1363
  const entries = [];
255
- return new Promise((resolve2, reject) => {
256
- try {
257
- const output = fs2.createWriteStream(this.outputPath);
258
- const archive = new ZipArchive({
259
- zlib: { level: 0 }
260
- });
261
- archive.on("entry", (entryData) => {
262
- const entryName = entryData.name.replace(/\\/g, "/");
263
- const start = entryData._offsets?.contents ?? 0;
264
- const end = entryData._offsets?.contents + (entryData.csize ?? 0) - 1;
265
- entries.push({
266
- name: entryName,
267
- start,
268
- end
269
- });
270
- });
271
- output.on("close", () => {
272
- const zipSize = archive.pointer();
273
- resolve2({
274
- zipPath: this.outputPath,
275
- size: zipSize,
276
- entryCount: entries.length,
277
- entries
278
- });
279
- });
280
- archive.on("error", (err) => {
281
- reject(err);
282
- });
283
- output.on("error", (err) => {
284
- reject(err);
285
- });
286
- archive.pipe(output);
287
- if (!fs2.existsSync(reportPath)) {
288
- reject(new Error(`Report file not found: ${reportPath}`));
289
- return;
290
- }
291
- const transformedReportPath = this.transformJsonReport(reportPath);
292
- archive.file(transformedReportPath, { name: "output/playwright-test-report.json" });
293
- for (const asset of assets) {
294
- if (!fs2.existsSync(asset.sourcePath)) {
295
- console.warn(`[Checkly Reporter] Skipping missing asset: ${asset.sourcePath}`);
296
- continue;
297
- }
298
- archive.file(asset.sourcePath, { name: asset.archivePath });
1364
+ const view = new DataView(zipData.buffer, zipData.byteOffset, zipData.byteLength);
1365
+ let eocdOffset = -1;
1366
+ for (let i = zipData.length - 22; i >= 0; i--) {
1367
+ if (view.getUint32(i, true) === 101010256) {
1368
+ eocdOffset = i;
1369
+ break;
1370
+ }
1371
+ }
1372
+ if (eocdOffset === -1) {
1373
+ console.warn("[Checkly Reporter] Could not find ZIP end of central directory");
1374
+ return entries;
1375
+ }
1376
+ let cdOffset = view.getUint32(eocdOffset + 16, true);
1377
+ const cdSize = view.getUint32(eocdOffset + 12, true);
1378
+ if (cdOffset === 4294967295 && eocdOffset >= 20) {
1379
+ const zip64LocatorOffset = eocdOffset - 20;
1380
+ if (view.getUint32(zip64LocatorOffset, true) === 117853008) {
1381
+ const zip64EocdOffset = Number(view.getBigUint64(zip64LocatorOffset + 8, true));
1382
+ if (view.getUint32(zip64EocdOffset, true) === 101075792) {
1383
+ cdOffset = Number(view.getBigUint64(zip64EocdOffset + 48, true));
299
1384
  }
300
- archive.finalize();
301
- } catch (error) {
302
- reject(error);
303
1385
  }
304
- });
1386
+ }
1387
+ let offset = cdOffset;
1388
+ const cdEnd = cdOffset + cdSize;
1389
+ while (offset < cdEnd && view.getUint32(offset, true) === 33639248) {
1390
+ const compressedSize = view.getUint32(offset + 20, true);
1391
+ const filenameLength = view.getUint16(offset + 28, true);
1392
+ const extraLength = view.getUint16(offset + 30, true);
1393
+ const commentLength = view.getUint16(offset + 32, true);
1394
+ const localHeaderOffset = view.getUint32(offset + 42, true);
1395
+ const filenameBytes = zipData.slice(offset + 46, offset + 46 + filenameLength);
1396
+ const filename = new TextDecoder().decode(filenameBytes);
1397
+ const localExtraLength = view.getUint16(localHeaderOffset + 28, true);
1398
+ const dataStart = localHeaderOffset + 30 + filenameLength + localExtraLength;
1399
+ const dataEnd = dataStart + compressedSize - 1;
1400
+ entries.push({
1401
+ name: filename.replace(/\\/g, "/"),
1402
+ start: dataStart,
1403
+ end: dataEnd >= dataStart ? dataEnd : dataStart
1404
+ });
1405
+ offset += 46 + filenameLength + extraLength + commentLength;
1406
+ }
1407
+ return entries;
305
1408
  }
306
1409
  /**
307
1410
  * Transforms the JSON report to use relative paths for attachments
308
- * This ensures the UI can map attachment paths to ZIP entries
309
- * @param reportPath - Path to the original JSON report
310
- * @returns Path to the transformed JSON report (in temp directory)
311
1411
  */
312
1412
  transformJsonReport(reportPath) {
313
- const reportContent = fs2.readFileSync(reportPath, "utf-8");
1413
+ const reportContent = fs4.readFileSync(reportPath, "utf-8");
314
1414
  const report = JSON.parse(reportContent);
315
1415
  this.transformAttachmentPaths(report);
316
- const tempReportPath = path2.join(os.tmpdir(), `playwright-test-report-${Date.now()}.json`);
317
- fs2.writeFileSync(tempReportPath, JSON.stringify(report, null, 2));
1416
+ const tempReportPath = path3.join(os2.tmpdir(), `playwright-test-report-${Date.now()}.json`);
1417
+ fs4.writeFileSync(tempReportPath, JSON.stringify(report, null, 2));
318
1418
  return tempReportPath;
319
1419
  }
320
1420
  /**
321
1421
  * Recursively transforms attachment paths in the report structure
322
- * Converts absolute paths to relative paths matching ZIP structure
323
- * @param obj - Object to transform (mutated in place)
324
1422
  */
325
1423
  transformAttachmentPaths(obj) {
326
1424
  if (typeof obj !== "object" || obj === null) {
@@ -340,21 +1438,7 @@ var Zipper = class {
340
1438
  Object.values(obj).forEach((value) => this.transformAttachmentPaths(value));
341
1439
  }
342
1440
  /**
343
- * Normalizes attachment paths by extracting the relevant snapshot directory portion.
344
- * Supports Playwright's default and common custom snapshot directory patterns,
345
- * as well as blob merge resource paths.
346
- *
347
- * Priority order (first match wins):
348
- * 1. test-results/ (highest priority, existing behavior)
349
- * 2. blob-reports/resources/ (blob merge extraction paths)
350
- * 3. snapshots directories (Playwright default pattern)
351
- * 4. __screenshots__/ (common custom pattern)
352
- * 5. __snapshots__/ (common custom pattern)
353
- * 6. screenshots/ (simple custom pattern)
354
- * 7. snapshots/ (simple custom pattern)
355
- *
356
- * @param attachmentPath - Absolute or relative path to attachment
357
- * @returns Normalized path starting from the matched directory, or original path if no match
1441
+ * Normalizes attachment paths by extracting the relevant snapshot directory portion
358
1442
  */
359
1443
  normalizeAttachmentPath(attachmentPath) {
360
1444
  const normalizedPath = attachmentPath.replace(/\\/g, "/");
@@ -391,17 +1475,11 @@ var Zipper = class {
391
1475
  }
392
1476
  };
393
1477
 
394
- // src/reporter.ts
395
- import * as fs3 from "fs";
396
- import { readFileSync as readFileSync3 } from "fs";
397
- import * as path3 from "path";
398
- import { dirname, join as join3 } from "path";
399
- import { fileURLToPath } from "url";
400
-
401
- // ../clients/src/checkly-client.ts
402
- import axios from "axios";
1478
+ // src/api/client.ts
1479
+ var import_axios = __toESM(require("axios"));
1480
+ var import_form_data = __toESM(require("form-data"));
403
1481
 
404
- // ../clients/src/errors.ts
1482
+ // src/api/errors.ts
405
1483
  var ApiError = class extends Error {
406
1484
  data;
407
1485
  constructor(data, options) {
@@ -411,44 +1489,20 @@ var ApiError = class extends Error {
411
1489
  }
412
1490
  };
413
1491
  var ValidationError = class extends ApiError {
414
- constructor(data, options) {
415
- super(data, options);
416
- }
417
1492
  };
418
1493
  var UnauthorizedError = class extends ApiError {
419
- constructor(data, options) {
420
- super(data, options);
421
- }
422
1494
  };
423
1495
  var ForbiddenError = class extends ApiError {
424
- constructor(data, options) {
425
- super(data, options);
426
- }
427
1496
  };
428
1497
  var NotFoundError = class extends ApiError {
429
- constructor(data, options) {
430
- super(data, options);
431
- }
432
1498
  };
433
1499
  var RequestTimeoutError = class extends ApiError {
434
- constructor(data, options) {
435
- super(data, options);
436
- }
437
1500
  };
438
1501
  var ConflictError = class extends ApiError {
439
- constructor(data, options) {
440
- super(data, options);
441
- }
442
1502
  };
443
1503
  var ServerError = class extends ApiError {
444
- constructor(data, options) {
445
- super(data, options);
446
- }
447
1504
  };
448
1505
  var MiscellaneousError = class extends ApiError {
449
- constructor(data, options) {
450
- super(data, options);
451
- }
452
1506
  };
453
1507
  var MissingResponseError = class extends Error {
454
1508
  constructor(message, options) {
@@ -456,63 +1510,58 @@ var MissingResponseError = class extends Error {
456
1510
  this.name = "MissingResponseError";
457
1511
  }
458
1512
  };
459
- function parseErrorData(data, options) {
460
- if (!data) {
461
- return void 0;
462
- }
463
- if (typeof data === "object" && data.statusCode && data.error && data.message) {
464
- return {
465
- statusCode: data.statusCode,
466
- error: data.error,
467
- message: data.message,
468
- errorCode: data.errorCode
469
- };
470
- }
471
- if (typeof data === "object" && data.error && !data.message) {
472
- return {
473
- statusCode: options.statusCode,
474
- error: data.error,
475
- message: data.error
476
- };
477
- }
478
- if (typeof data === "object" && data.error && data.message) {
479
- return {
480
- statusCode: options.statusCode,
481
- error: data.error,
482
- message: data.message,
483
- errorCode: data.errorCode
484
- };
485
- }
486
- if (typeof data === "object" && data.message) {
487
- return {
488
- statusCode: options.statusCode,
489
- error: data.message,
490
- message: data.message,
491
- errorCode: data.errorCode
492
- };
1513
+ function parseErrorData(data, statusCode) {
1514
+ if (!data) return void 0;
1515
+ if (typeof data === "object" && data !== null) {
1516
+ const obj = data;
1517
+ if (obj.statusCode && obj.error && obj.message) {
1518
+ return {
1519
+ statusCode: obj.statusCode,
1520
+ error: obj.error,
1521
+ message: obj.message,
1522
+ errorCode: obj.errorCode
1523
+ };
1524
+ }
1525
+ if (obj.error && obj.message) {
1526
+ return {
1527
+ statusCode,
1528
+ error: obj.error,
1529
+ message: obj.message,
1530
+ errorCode: obj.errorCode
1531
+ };
1532
+ }
1533
+ if (obj.error) {
1534
+ return {
1535
+ statusCode,
1536
+ error: obj.error,
1537
+ message: obj.error
1538
+ };
1539
+ }
1540
+ if (obj.message) {
1541
+ return {
1542
+ statusCode,
1543
+ error: obj.message,
1544
+ message: obj.message,
1545
+ errorCode: obj.errorCode
1546
+ };
1547
+ }
493
1548
  }
494
1549
  if (typeof data === "string") {
495
- return {
496
- statusCode: options.statusCode,
497
- error: data,
498
- message: data
499
- };
1550
+ return { statusCode, error: data, message: data };
500
1551
  }
501
1552
  return void 0;
502
1553
  }
503
- function handleErrorResponse(err) {
504
- if (!err.response) {
505
- throw new MissingResponseError(err.message || "Network error");
506
- }
507
- const { status, data } = err.response;
508
- const errorData = parseErrorData(data, { statusCode: status });
509
- if (!errorData) {
510
- throw new MiscellaneousError({
511
- statusCode: status,
512
- error: "Unknown error",
513
- message: err.message || "An error occurred"
514
- });
1554
+ function handleErrorResponse(err2) {
1555
+ const axiosError = err2;
1556
+ if (!axiosError.response) {
1557
+ throw new MissingResponseError(axiosError.message || "Network error");
515
1558
  }
1559
+ const { status, data } = axiosError.response;
1560
+ const errorData = parseErrorData(data, status) ?? {
1561
+ statusCode: status,
1562
+ error: "Unknown error",
1563
+ message: axiosError.message || "An error occurred"
1564
+ };
516
1565
  switch (status) {
517
1566
  case 400:
518
1567
  throw new ValidationError(errorData);
@@ -534,561 +1583,874 @@ function handleErrorResponse(err) {
534
1583
  }
535
1584
  }
536
1585
 
537
- // ../clients/src/checkly-client.ts
538
- function getVersion() {
539
- return "0.1.0";
540
- }
541
- function createRequestInterceptor(apiKey, accountId) {
542
- return (config) => {
1586
+ // src/api/client.ts
1587
+ function createClient(options) {
1588
+ const http = import_axios.default.create({
1589
+ baseURL: options.baseUrl,
1590
+ timeout: 12e4,
1591
+ maxContentLength: Number.POSITIVE_INFINITY,
1592
+ maxBodyLength: Number.POSITIVE_INFINITY
1593
+ });
1594
+ http.interceptors.request.use((config) => {
543
1595
  if (config.headers) {
544
- config.headers.Authorization = `Bearer ${apiKey}`;
545
- config.headers["x-checkly-account"] = accountId;
546
- config.headers["User-Agent"] = `@checkly/playwright-reporter/${getVersion()}`;
1596
+ config.headers.Authorization = `Bearer ${options.apiKey}`;
1597
+ config.headers["x-checkly-account"] = options.accountId;
1598
+ config.headers["x-checkly-reporter-version"] = options.telemetry.reporterVersion;
1599
+ config.headers["x-checkly-playwright-version"] = options.telemetry.playwrightVersion;
1600
+ config.headers["x-checkly-ci-provider"] = options.telemetry.ciProvider;
1601
+ config.headers["x-checkly-ci-environment"] = options.telemetry.ciEnvironment;
1602
+ config.headers["x-checkly-node-version"] = options.telemetry.nodeVersion;
547
1603
  }
548
1604
  return config;
1605
+ });
1606
+ http.interceptors.response.use(
1607
+ (response) => response,
1608
+ (error) => handleErrorResponse(error)
1609
+ );
1610
+ return {
1611
+ testSessions: {
1612
+ async create(request) {
1613
+ const response = await http.post("/next/test-sessions/create", request);
1614
+ return response.data;
1615
+ },
1616
+ async uploadAsset(testSessionId, testResultId, assets) {
1617
+ const form = new import_form_data.default();
1618
+ form.append("assets", assets, { filename: "assets.zip", contentType: "application/zip" });
1619
+ const response = await http.post(
1620
+ `/next/test-sessions/${testSessionId}/results/${testResultId}/assets`,
1621
+ form,
1622
+ { headers: form.getHeaders() }
1623
+ );
1624
+ return response.data;
1625
+ },
1626
+ async updateResult(testSessionId, testResultId, request) {
1627
+ const response = await http.post(
1628
+ `/next/test-sessions/${testSessionId}/results/${testResultId}`,
1629
+ request
1630
+ );
1631
+ return response.data;
1632
+ }
1633
+ }
549
1634
  };
550
1635
  }
551
- function createResponseErrorInterceptor() {
552
- return (error) => {
553
- handleErrorResponse(error);
554
- };
555
- }
556
- var ChecklyClient = class {
557
- apiKey;
558
- baseUrl;
559
- accountId;
560
- api;
561
- constructor(options) {
562
- this.accountId = options.accountId;
563
- this.apiKey = options.apiKey;
564
- this.baseUrl = options.baseUrl;
565
- this.api = axios.create({
566
- baseURL: this.baseUrl,
567
- timeout: 12e4,
568
- // 120 second timeout for large uploads
569
- maxContentLength: Number.POSITIVE_INFINITY,
570
- // Allow large payloads
571
- maxBodyLength: Number.POSITIVE_INFINITY
572
- // Allow large request bodies
573
- });
574
- this.api.interceptors.request.use(createRequestInterceptor(this.apiKey, this.accountId));
575
- this.api.interceptors.response.use((response) => response, createResponseErrorInterceptor());
576
- }
577
- /**
578
- * Gets the underlying axios instance
579
- * Useful for creating resource-specific clients (e.g., TestResults)
580
- */
581
- getAxiosInstance() {
582
- return this.api;
583
- }
584
- };
585
1636
 
586
- // ../clients/src/test-results.ts
587
- import FormData from "form-data";
588
- var TestResults = class {
589
- constructor(api) {
590
- this.api = api;
591
- }
592
- /**
593
- * Creates a new test session in Checkly
594
- *
595
- * @param request Test session creation request
596
- * @returns Test session response with session ID and test result IDs
597
- * @throws {ValidationError} If request data is invalid
598
- * @throws {UnauthorizedError} If authentication fails
599
- * @throws {ServerError} If server error occurs
600
- */
601
- async createTestSession(request) {
602
- const response = await this.api.post("/next/test-sessions/create", request);
603
- return response.data;
604
- }
605
- /**
606
- * Step 1: Upload test result assets to S3
607
- * Streams a ZIP file containing test assets (traces, videos, screenshots)
608
- *
609
- * @param testSessionId ID of the test session
610
- * @param testResultId ID of the test result
611
- * @param assets Buffer or ReadableStream of the ZIP file
612
- * @returns Upload response with assetId, region, key, and url
613
- * @throws {ValidationError} If assets are invalid
614
- * @throws {UnauthorizedError} If authentication fails
615
- * @throws {NotFoundError} If test session or result not found
616
- * @throws {PayloadTooLargeError} If assets exceed 500MB
617
- * @throws {ServerError} If S3 upload fails
618
- */
619
- async uploadTestResultAsset(testSessionId, testResultId, assets) {
620
- const form = new FormData();
621
- form.append("assets", assets, {
622
- filename: "assets.zip",
623
- contentType: "application/zip"
624
- });
625
- const response = await this.api.post(
626
- `/next/test-sessions/${testSessionId}/results/${testResultId}/assets`,
627
- form,
628
- {
629
- headers: {
630
- ...form.getHeaders()
631
- }
632
- }
633
- );
634
- return response.data;
1637
+ // src/extensions/checkly-upload.ts
1638
+ function getPackageVersion() {
1639
+ try {
1640
+ const packageJson = JSON.parse(fs5.readFileSync(path4.join(__dirname, "..", "package.json"), "utf-8"));
1641
+ return packageJson.version;
1642
+ } catch {
1643
+ return "unknown";
635
1644
  }
636
- /**
637
- * Step 2: Update test result with status and optional asset reference
638
- * Uses JSON payload for clean, easy-to-validate updates
639
- *
640
- * @param testSessionId ID of the test session
641
- * @param testResultId ID of the test result to update
642
- * @param request Test result update request (JSON)
643
- * @returns Test result update response
644
- * @throws {ValidationError} If request data is invalid
645
- * @throws {UnauthorizedError} If authentication fails
646
- * @throws {NotFoundError} If test session or result not found
647
- * @throws {ServerError} If server error occurs
648
- */
649
- async updateTestResult(testSessionId, testResultId, request) {
650
- const response = await this.api.post(
651
- `/next/test-sessions/${testSessionId}/results/${testResultId}`,
652
- request
653
- );
654
- return response.data;
655
- }
656
- };
657
-
658
- // src/reporter.ts
659
- var __filename = fileURLToPath(import.meta.url);
660
- var __dirname = dirname(__filename);
661
- var packageJson = JSON.parse(readFileSync3(join3(__dirname, "..", "package.json"), "utf-8"));
662
- var pkgVersion = packageJson.version;
663
- var pluralRules = new Intl.PluralRules("en-US");
664
- var projectForms = {
665
- zero: "Project",
666
- one: "Project",
667
- two: "Projects",
668
- few: "Projects",
669
- many: "Projects",
670
- other: "Projects"
671
- };
1645
+ }
672
1646
  function getApiUrl(environment) {
673
- const environments = {
1647
+ const urls = {
674
1648
  local: "http://127.0.0.1:3000",
675
1649
  development: "https://api-dev.checklyhq.com",
676
1650
  staging: "https://api-test.checklyhq.com",
677
1651
  production: "https://api.checklyhq.com"
678
1652
  };
679
- return environments[environment];
1653
+ return urls[environment];
680
1654
  }
681
- function getEnvironment(options) {
682
- const envFromOptions = options?.environment;
683
- const envFromEnvVar = process.env.CHECKLY_ENV;
684
- const env = envFromOptions || envFromEnvVar || "production";
685
- const validEnvironments = ["local", "development", "staging", "production"];
686
- if (!validEnvironments.includes(env)) {
687
- console.warn(`[Checkly Reporter] Invalid environment "${env}", using "production"`);
1655
+ function getEnvironment() {
1656
+ const env = process.env.CHECKLY_ENV || "production";
1657
+ const valid = ["local", "development", "staging", "production"];
1658
+ if (!valid.includes(env)) {
1659
+ console.warn(`[Checkly] Invalid environment "${env}", using "production"`);
688
1660
  return "production";
689
1661
  }
690
1662
  return env;
691
1663
  }
692
- function convertStepToJSON(step) {
693
- return {
694
- title: step.title,
695
- duration: step.duration,
696
- error: step.error,
697
- steps: step.steps.length > 0 ? step.steps.map(convertStepToJSON) : void 0
698
- };
699
- }
700
1664
  function getDirectoryName() {
701
1665
  const cwd = process.cwd();
702
- let dirName = path3.basename(cwd);
703
- if (!dirName || dirName === "/" || dirName === ".") {
704
- dirName = "playwright-tests";
1666
+ let name = path4.basename(cwd);
1667
+ if (!name || name === "/" || name === ".") name = "playwright-tests";
1668
+ name = name.replace(/[<>:"|?*]/g, "-");
1669
+ if (name.length > 255) name = name.substring(0, 255);
1670
+ return name;
1671
+ }
1672
+ function checklyUpload(options = {}) {
1673
+ const apiKey = process.env.CHECKLY_API_KEY || options.apiKey;
1674
+ const accountId = process.env.CHECKLY_ACCOUNT_ID || options.accountId;
1675
+ const environment = getEnvironment();
1676
+ const baseUrl = getApiUrl(environment);
1677
+ const dryRun = options.dryRun ?? false;
1678
+ const ciInfo = detectCI();
1679
+ const reporterVersion = getPackageVersion();
1680
+ const canUpload = !dryRun && apiKey && accountId;
1681
+ let api;
1682
+ let testSession;
1683
+ let sessionCreationPromise;
1684
+ let startTime;
1685
+ let resolvedOutputDir;
1686
+ let assetCollector;
1687
+ let zipper;
1688
+ const testCounts = { passed: 0, failed: 0, flaky: 0 };
1689
+ const tracePathsMap = /* @__PURE__ */ new Map();
1690
+ const warningsMap = /* @__PURE__ */ new Map();
1691
+ const consoleMessagesMap = /* @__PURE__ */ new Map();
1692
+ const networkRequestsMap = /* @__PURE__ */ new Map();
1693
+ function resolveSessionName(ctx) {
1694
+ const { sessionName } = options;
1695
+ if (typeof sessionName === "function") return sessionName(ctx);
1696
+ if (typeof sessionName === "string") return sessionName;
1697
+ return `Playwright Test Session: ${ctx.directoryName}`;
705
1698
  }
706
- dirName = dirName.replace(/[<>:"|?*]/g, "-");
707
- if (dirName.length > 255) {
708
- dirName = dirName.substring(0, 255);
1699
+ async function createSession(config, suite, log) {
1700
+ if (!api) return;
1701
+ try {
1702
+ const directoryName = getDirectoryName();
1703
+ const sessionName = resolveSessionName({ directoryName, config, suite });
1704
+ const repoInfo = getGitHubRepoInfo();
1705
+ log("\u{1F517} Creating test session", { name: sessionName, environment });
1706
+ testSession = await api.testSessions.create({
1707
+ name: sessionName,
1708
+ environment: process.env.NODE_ENV || "test",
1709
+ repoInfo,
1710
+ startedAt: startTime.getTime(),
1711
+ testResults: [{ name: directoryName }],
1712
+ provider: "PW_REPORTER"
1713
+ });
1714
+ log("\u2705 Session created", { id: testSession.testSessionId });
1715
+ } catch (err2) {
1716
+ console.error("[Checkly] Failed to create test session:", err2);
1717
+ }
709
1718
  }
710
- return dirName;
711
- }
712
- var ChecklyReporter = class {
713
- options;
714
- assetCollector;
715
- zipper;
716
- testResults;
717
- testSession;
718
- startTime;
719
- testCounts = {
720
- passed: 0,
721
- failed: 0,
722
- flaky: 0
723
- };
724
- // Store steps per test result, keyed by "testId:retry"
725
- stepsMap = /* @__PURE__ */ new Map();
726
- // Store warnings per test result, keyed by "testId:retry"
727
- warningsMap = /* @__PURE__ */ new Map();
728
- constructor(options = {}) {
729
- const environment = getEnvironment(options);
730
- const baseUrl = getApiUrl(environment);
731
- const apiKey = process.env.CHECKLY_API_KEY || options.apiKey;
732
- const accountId = process.env.CHECKLY_ACCOUNT_ID || options.accountId;
733
- this.options = {
734
- accountId,
735
- apiKey,
736
- outputPath: options.outputPath ?? "checkly-report.zip",
737
- jsonReportPath: options.jsonReportPath ?? "test-results/playwright-test-report.json",
738
- testResultsDir: options.testResultsDir ?? "test-results",
739
- dryRun: options.dryRun ?? false,
740
- sessionName: options.sessionName
1719
+ function checkTraceAttachment(test, result) {
1720
+ const key = `${test.id}:${result.retry}`;
1721
+ const traceAttachment = result.attachments?.find((a) => a.name === "trace" || a.contentType === "application/zip");
1722
+ if (traceAttachment?.path) {
1723
+ tracePathsMap.set(key, traceAttachment.path);
1724
+ return;
1725
+ }
1726
+ const traceConfig = test.parent?.project()?.use?.trace;
1727
+ const traceMode = typeof traceConfig === "object" ? traceConfig.mode : traceConfig;
1728
+ if (!traceMode || traceMode === "off") return;
1729
+ const warnings = warningsMap.get(key) || [];
1730
+ warnings.push({ type: "trace-missing", message: `No trace found for trace mode "${traceMode}"` });
1731
+ warningsMap.set(key, warnings);
1732
+ }
1733
+ async function extractTraceData(key, tracePath) {
1734
+ const reader = new TraceReader(tracePath);
1735
+ if (!await reader.open()) return;
1736
+ const messages = await reader.extractEvents('"type":"console"', toConsoleMessage);
1737
+ if (messages.length > 0) consoleMessagesMap.set(key, messages);
1738
+ const requests = await reader.extractEvents('"type":"resource-snapshot"', toNetworkRequest);
1739
+ if (requests.length > 0) networkRequestsMap.set(key, requests);
1740
+ }
1741
+ async function extractDataFromTraces(log) {
1742
+ if (tracePathsMap.size === 0) return;
1743
+ log("\u{1F4E6} Extracting trace data", { traces: tracePathsMap.size });
1744
+ const extractions = Array.from(tracePathsMap.entries()).map(
1745
+ ([key, tracePath]) => extractTraceData(key, tracePath).catch((err2) => {
1746
+ console.error(`[Checkly] Failed to extract trace data: ${err2}`);
1747
+ })
1748
+ );
1749
+ await Promise.all(extractions);
1750
+ }
1751
+ function buildChecklyExtensionData(key) {
1752
+ const warnings = warningsMap.get(key);
1753
+ const consoleMessages = consoleMessagesMap.get(key);
1754
+ const network = networkRequestsMap.get(key);
1755
+ if (!warnings?.length && !consoleMessages?.length && !network?.length) {
1756
+ return null;
1757
+ }
1758
+ return {
1759
+ ...warnings?.length ? { warnings } : {},
1760
+ ...consoleMessages?.length ? { console: consoleMessages } : {},
1761
+ ...network?.length ? { network } : {}
741
1762
  };
742
- this.assetCollector = new AssetCollector(this.options.testResultsDir);
743
- this.zipper = new Zipper({
744
- outputPath: this.options.outputPath
745
- });
746
- if (!this.options.dryRun && this.options.apiKey && this.options.accountId) {
747
- const client = new ChecklyClient({
748
- apiKey: this.options.apiKey,
749
- accountId: this.options.accountId,
750
- baseUrl
751
- });
752
- this.testResults = new TestResults(client.getAxiosInstance());
1763
+ }
1764
+ function injectDataIntoReport(report) {
1765
+ function processSuite(suite) {
1766
+ for (const spec of suite.specs) {
1767
+ for (const test of spec.tests) {
1768
+ for (const result of test.results) {
1769
+ const key = `${spec.id}:${result.retry}`;
1770
+ const checklyData = buildChecklyExtensionData(key);
1771
+ if (checklyData) {
1772
+ ;
1773
+ result._checkly = checklyData;
1774
+ }
1775
+ }
1776
+ }
1777
+ }
1778
+ suite.suites?.forEach(processSuite);
753
1779
  }
1780
+ report.suites.forEach(processSuite);
754
1781
  }
755
- /**
756
- * Resolves the session name from options
757
- * Supports string, callback function, or falls back to default
758
- */
759
- resolveSessionName(context) {
760
- const { sessionName } = this.options;
761
- if (typeof sessionName === "function") {
762
- return sessionName(context);
1782
+ function calculateTiming(report) {
1783
+ const stats = report.stats;
1784
+ if (stats?.startTime && stats?.duration !== void 0) {
1785
+ const start = new Date(stats.startTime);
1786
+ const responseTime = Math.round(stats.duration);
1787
+ return {
1788
+ startedAt: start.toISOString(),
1789
+ stoppedAt: new Date(start.getTime() + responseTime).toISOString(),
1790
+ responseTime
1791
+ };
763
1792
  }
764
- if (typeof sessionName === "string") {
765
- return sessionName;
1793
+ const now = Date.now();
1794
+ return {
1795
+ startedAt: startTime?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString(),
1796
+ stoppedAt: new Date(now).toISOString(),
1797
+ responseTime: startTime ? Math.round(now - startTime.getTime()) : 0
1798
+ };
1799
+ }
1800
+ async function uploadAssets(log, zipPath, zipSizeBytes) {
1801
+ if (!api || !testSession || zipSizeBytes === 0) return void 0;
1802
+ const firstResult = testSession.testResults[0];
1803
+ if (!firstResult) return void 0;
1804
+ log("\u{1F4E4} Uploading assets", { size: `${(zipSizeBytes / 1024).toFixed(1)}KB` });
1805
+ try {
1806
+ const assets = fs5.createReadStream(zipPath);
1807
+ const uploadResponse = await api.testSessions.uploadAsset(
1808
+ testSession.testSessionId,
1809
+ firstResult.testResultId,
1810
+ assets
1811
+ );
1812
+ log("\u2705 Assets uploaded", { assetId: uploadResponse.assetId });
1813
+ return uploadResponse.assetId;
1814
+ } catch (err2) {
1815
+ console.error("[Checkly] Asset upload failed:", err2);
1816
+ return void 0;
766
1817
  }
767
- return `Playwright Test Session: ${context.directoryName}`;
768
1818
  }
769
- /**
770
- * Checks if test result has a trace attachment and adds context-aware warning if missing
771
- * The warning type depends on the trace configuration and test result state
772
- */
773
- checkTraceAttachment(test, result) {
774
- const warningsKey = `${test.id}:${result.retry}`;
775
- const hasTrace = result.attachments?.some(
776
- (attachment) => attachment.name === "trace" || attachment.contentType === "application/zip"
777
- );
778
- if (hasTrace) {
779
- return;
1819
+ async function uploadResults(log, report, zipPath, entries) {
1820
+ if (!api || !testSession || testSession.testResults.length === 0) return;
1821
+ try {
1822
+ const { failed, flaky } = testCounts;
1823
+ const overallStatus = failed > 0 ? "FAILED" : "PASSED";
1824
+ const isDegraded = failed === 0 && flaky > 0;
1825
+ const timing = calculateTiming(report);
1826
+ const zipSizeBytes = (await fs5.promises.stat(zipPath)).size;
1827
+ const assetId = await uploadAssets(log, zipPath, zipSizeBytes);
1828
+ const firstResult = testSession.testResults[0];
1829
+ log("\u{1F4DD} Updating test result", { status: overallStatus, isDegraded });
1830
+ await api.testSessions.updateResult(testSession.testSessionId, firstResult.testResultId, {
1831
+ status: overallStatus,
1832
+ assetEntries: assetId ? entries : void 0,
1833
+ isDegraded,
1834
+ ...timing,
1835
+ metadata: { usageData: { s3PostTotalBytes: zipSizeBytes } }
1836
+ });
1837
+ } catch (err2) {
1838
+ console.error("[Checkly] Failed to upload results:", err2);
780
1839
  }
781
- const traceConfig = test.parent?.project()?.use?.trace;
782
- const traceMode = typeof traceConfig === "object" ? traceConfig.mode : traceConfig;
783
- const isRetry = result.retry > 0;
784
- const testPassed = result.status === "passed";
785
- let warningType;
786
- let message;
787
- switch (traceMode) {
788
- case void 0:
1840
+ }
1841
+ return {
1842
+ name: "checkly-upload",
1843
+ onBegin: ({ config, suite, log }) => {
1844
+ startTime = /* @__PURE__ */ new Date();
1845
+ resolvedOutputDir = options.outputDir ?? config.projects[0]?.outputDir ?? "test-results";
1846
+ const zipPath = path4.join(resolvedOutputDir, "checkly-report.zip");
1847
+ assetCollector = new AssetCollector(resolvedOutputDir);
1848
+ zipper = new Zipper({ outputPath: zipPath });
1849
+ if (!canUpload) {
1850
+ if (!dryRun && (!apiKey || !accountId)) {
1851
+ log("\u26A0\uFE0F Skipping upload (missing API credentials)");
1852
+ }
789
1853
  return;
790
- case "off":
791
- warningType = "trace-off";
792
- message = 'Traces are disabled. Set trace: "on" in playwright.config.ts to capture traces.';
793
- break;
794
- case "retain-on-failure":
795
- if (testPassed) {
796
- warningType = "trace-retained-on-failure";
797
- message = 'No trace retained because test passed. Trace mode is "retain-on-failure" which discards traces for passing tests.';
798
- } else {
799
- warningType = "trace-missing";
800
- message = 'Trace should exist but was not found. The test failed with trace: "retain-on-failure".';
1854
+ }
1855
+ api = createClient({
1856
+ apiKey,
1857
+ accountId,
1858
+ baseUrl,
1859
+ telemetry: {
1860
+ reporterVersion,
1861
+ playwrightVersion: config.version,
1862
+ ciProvider: ciInfo.ciProvider,
1863
+ ciEnvironment: ciInfo.environment,
1864
+ nodeVersion: process.version
801
1865
  }
802
- break;
803
- case "on-first-retry":
804
- if (!isRetry) {
805
- warningType = "trace-first-retry-only";
806
- message = 'No trace for initial attempt. Trace mode is "on-first-retry" which only records traces on the first retry.';
807
- } else if (result.retry === 1) {
808
- warningType = "trace-missing";
809
- message = 'Trace should exist but was not found. This is the first retry with trace: "on-first-retry".';
810
- } else {
811
- warningType = "trace-first-retry-only";
812
- message = `No trace for retry #${result.retry}. Trace mode is "on-first-retry" which only records the first retry.`;
1866
+ });
1867
+ sessionCreationPromise = createSession(config, suite, log);
1868
+ },
1869
+ onTestEnd: ({ test, result }) => {
1870
+ checkTraceAttachment(test, result);
1871
+ const outcome = test.outcome();
1872
+ const testIsComplete = result.retry === test.retries || outcome !== "unexpected";
1873
+ if (!testIsComplete) return;
1874
+ if (outcome === "flaky") {
1875
+ testCounts.flaky++;
1876
+ testCounts.passed++;
1877
+ } else if (result.status === "passed") {
1878
+ testCounts.passed++;
1879
+ } else if (result.status === "failed" || result.status === "timedOut") {
1880
+ testCounts.failed++;
1881
+ }
1882
+ },
1883
+ onEnd: async ({ report, log, addSummaryLine }) => {
1884
+ try {
1885
+ if (sessionCreationPromise) {
1886
+ await sessionCreationPromise;
813
1887
  }
814
- break;
815
- case "on-all-retries":
816
- if (!isRetry) {
817
- warningType = "trace-retries-only";
818
- message = 'No trace for initial attempt. Trace mode is "on-all-retries" which only records traces on retries.';
819
- } else {
820
- warningType = "trace-missing";
821
- message = `Trace should exist but was not found. This is retry #${result.retry} with trace: "on-all-retries".`;
1888
+ await extractDataFromTraces(log);
1889
+ injectDataIntoReport(report);
1890
+ fs5.mkdirSync(resolvedOutputDir, { recursive: true });
1891
+ const tempReportPath = path4.join(resolvedOutputDir, `.checkly-report-${Date.now()}.json`);
1892
+ fs5.writeFileSync(tempReportPath, JSON.stringify(report, null, 2));
1893
+ const assets = await assetCollector.collectAssets(report);
1894
+ const zipResult = await zipper.createZip(tempReportPath, assets);
1895
+ log("\u{1F4E6} ZIP created", { path: zipResult.zipPath, size: `${(zipResult.size / 1024).toFixed(1)}KB` });
1896
+ if (api && testSession) {
1897
+ await uploadResults(log, report, zipResult.zipPath, zipResult.entries);
1898
+ if (!dryRun) {
1899
+ try {
1900
+ fs5.unlinkSync(zipResult.zipPath);
1901
+ } catch {
1902
+ }
1903
+ }
1904
+ if (testSession.link) {
1905
+ addSummaryLine(`\u{1F517} Checkly Test Session: ${testSession.link}`);
1906
+ }
822
1907
  }
823
- break;
824
- case "retain-on-first-failure":
825
- if (testPassed) {
826
- warningType = "trace-retained-on-first-failure";
827
- message = 'No trace retained because test passed. Trace mode is "retain-on-first-failure" which discards traces for passing tests.';
828
- } else if (isRetry) {
829
- warningType = "trace-retained-on-first-failure";
830
- message = 'No trace for retries. Trace mode is "retain-on-first-failure" which only records the first run.';
831
- } else {
832
- warningType = "trace-missing";
833
- message = 'Trace should exist but was not found. The test failed on first run with trace: "retain-on-first-failure".';
1908
+ try {
1909
+ fs5.unlinkSync(tempReportPath);
1910
+ } catch {
834
1911
  }
835
- break;
836
- case "on":
837
- warningType = "trace-missing";
838
- message = 'Trace should exist but was not found. Trace mode is "on" which should always record traces.';
839
- break;
840
- default:
841
- warningType = "trace-missing";
842
- message = `No trace found. Trace mode "${traceMode}" may not be generating traces for this result.`;
1912
+ } catch (err2) {
1913
+ console.error("[Checkly] Error in onEnd:", err2);
1914
+ }
843
1915
  }
844
- const warnings = this.warningsMap.get(warningsKey) || [];
845
- warnings.push({ type: warningType, message });
846
- this.warningsMap.set(warningsKey, warnings);
1916
+ };
1917
+ }
1918
+
1919
+ // src/reporter.ts
1920
+ var import_node_fs = require("fs");
1921
+ var import_node_path = require("path");
1922
+ function getPackageVersion2() {
1923
+ try {
1924
+ const packageJson = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(__dirname, "..", "package.json"), "utf-8"));
1925
+ return packageJson.version;
1926
+ } catch {
1927
+ return "unknown";
1928
+ }
1929
+ }
1930
+ var pluralRules = new Intl.PluralRules("en-US");
1931
+ var projectForms = {
1932
+ zero: "Project",
1933
+ one: "Project",
1934
+ two: "Projects",
1935
+ few: "Projects",
1936
+ many: "Projects",
1937
+ other: "Projects"
1938
+ };
1939
+ var BaseReporter = class {
1940
+ config;
1941
+ suite;
1942
+ startTime;
1943
+ options;
1944
+ verbose;
1945
+ globalErrors = [];
1946
+ tests = /* @__PURE__ */ new Map();
1947
+ expectedCount = 0;
1948
+ unexpectedCount = 0;
1949
+ flakyCount = 0;
1950
+ skippedCount = 0;
1951
+ _report = null;
1952
+ extensions = [];
1953
+ summaryLines = [];
1954
+ // Reconstructed projects for merge-reports scenarios
1955
+ reconstructedProjects = /* @__PURE__ */ new Map();
1956
+ constructor(options = {}) {
1957
+ this.options = options;
1958
+ this.verbose = options.verbose ?? process.env.CHECKLY_REPORTER_VERBOSE === "true";
1959
+ }
1960
+ use(extension) {
1961
+ this.extensions.push(extension);
1962
+ return this;
1963
+ }
1964
+ log(message, data) {
1965
+ if (!this.verbose) return;
1966
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].slice(0, 12);
1967
+ const prefix = `[Checkly ${timestamp}]`;
1968
+ if (data && Object.keys(data).length > 0) {
1969
+ console.log(`${prefix} ${message}`, data);
1970
+ } else {
1971
+ console.log(`${prefix} ${message}`);
1972
+ }
1973
+ }
1974
+ createExtensionLogger(extensionName) {
1975
+ return (message, data) => {
1976
+ this.log(`[${extensionName}] ${message}`, data);
1977
+ };
847
1978
  }
848
- /**
849
- * Called once before running tests
850
- * Creates test session in Checkly if credentials provided
851
- */
852
1979
  onBegin(config, suite) {
1980
+ const deprecated = ["outputFile", "testResultsDir", "outputPath"];
1981
+ const opts = this.options;
1982
+ for (const key of deprecated) {
1983
+ if (key in opts && opts[key] !== void 0) {
1984
+ throw new Error(`[Checkly] "${key}" option is no longer supported. Use outputDir instead.`);
1985
+ }
1986
+ }
1987
+ this.config = config;
1988
+ this.suite = suite;
853
1989
  this.startTime = /* @__PURE__ */ new Date();
854
- if (!this.testResults) {
855
- return;
1990
+ this.tests.clear();
1991
+ this.globalErrors = [];
1992
+ this.expectedCount = 0;
1993
+ this.unexpectedCount = 0;
1994
+ this.flakyCount = 0;
1995
+ this.skippedCount = 0;
1996
+ this._report = null;
1997
+ this.reconstructedProjects.clear();
1998
+ this.reconstructProjectsIfNeeded();
1999
+ const testCount = this.countTests(suite);
2000
+ const projectNames = config.projects.length > 0 ? config.projects.map((p) => p.name).join(", ") : Array.from(this.reconstructedProjects.keys()).join(", ") || "default";
2001
+ this.log(`\u{1F3AC} Starting test run`, { tests: testCount, projects: projectNames, workers: config.workers });
2002
+ for (const ext of this.extensions) {
2003
+ ext.onBegin?.({ config, suite, log: this.createExtensionLogger(ext.name) });
856
2004
  }
857
- try {
858
- const directoryName = getDirectoryName();
859
- const sessionName = this.resolveSessionName({ directoryName, config, suite });
860
- const testResults = [{ name: directoryName }];
861
- const repoUrl = process.env.GITHUB_REPOSITORY ? `https://github.com/${process.env.GITHUB_REPOSITORY}` : void 0;
862
- const repoInfo = repoUrl ? {
863
- repoUrl,
864
- commitId: process.env.GITHUB_SHA,
865
- branchName: process.env.GITHUB_REF_NAME,
866
- commitOwner: process.env.GITHUB_ACTOR,
867
- commitMessage: process.env.GITHUB_EVENT_NAME
868
- } : void 0;
869
- this.testResults.createTestSession({
870
- name: sessionName,
871
- environment: process.env.NODE_ENV || "test",
872
- repoInfo,
873
- startedAt: this.startTime.getTime(),
874
- // Required timestamp in milliseconds
875
- testResults,
876
- provider: "PW_REPORTER"
877
- }).then((response) => {
878
- this.testSession = response;
879
- }).catch((error) => {
880
- console.error("[Checkly Reporter] Failed to create test session:", error.message);
881
- });
882
- } catch (error) {
883
- console.error("[Checkly Reporter] Error in onBegin:", error);
2005
+ }
2006
+ onTestBegin(test, result) {
2007
+ for (const ext of this.extensions) {
2008
+ ext.onTestBegin?.({ test, result, log: this.createExtensionLogger(ext.name) });
2009
+ }
2010
+ }
2011
+ onStepBegin(test, result, step) {
2012
+ for (const ext of this.extensions) {
2013
+ ext.onStepBegin?.({ test, result, step, log: this.createExtensionLogger(ext.name) });
2014
+ }
2015
+ }
2016
+ onStepEnd(test, result, step) {
2017
+ for (const ext of this.extensions) {
2018
+ ext.onStepEnd?.({ test, result, step, log: this.createExtensionLogger(ext.name) });
884
2019
  }
885
2020
  }
886
- /**
887
- * Called for each test when it completes
888
- * Captures steps and warnings, tracks test results for final status calculation
889
- */
890
2021
  onTestEnd(test, result) {
891
- try {
892
- this.checkTraceAttachment(test, result);
893
- const stepsKey = `${test.id}:${result.retry}`;
894
- if (result.steps && result.steps.length > 0) {
895
- this.stepsMap.set(stepsKey, result.steps.map(convertStepToJSON));
2022
+ const testId = test.id;
2023
+ let testData = this.tests.get(testId);
2024
+ if (!testData) {
2025
+ testData = { testCase: test, results: [] };
2026
+ this.tests.set(testId, testData);
2027
+ }
2028
+ testData.results.push(result);
2029
+ for (const ext of this.extensions) {
2030
+ ext.onTestEnd?.({ test, result, log: this.createExtensionLogger(ext.name) });
2031
+ }
2032
+ }
2033
+ onStdOut(chunk, test, result) {
2034
+ for (const ext of this.extensions) {
2035
+ ext.onStdOut?.({ chunk, test, result, log: this.createExtensionLogger(ext.name) });
2036
+ }
2037
+ }
2038
+ onStdErr(chunk, test, result) {
2039
+ for (const ext of this.extensions) {
2040
+ ext.onStdErr?.({ chunk, test, result, log: this.createExtensionLogger(ext.name) });
2041
+ }
2042
+ }
2043
+ onError(error) {
2044
+ this.globalErrors.push(error);
2045
+ this.log(`\u{1F4A5} Global error`, { message: error.message?.slice(0, 100) });
2046
+ for (const ext of this.extensions) {
2047
+ ext.onError?.({ error, log: this.createExtensionLogger(ext.name) });
2048
+ }
2049
+ }
2050
+ async onEnd(result) {
2051
+ for (const testData of this.tests.values()) {
2052
+ switch (testData.testCase.outcome()) {
2053
+ case "expected":
2054
+ this.expectedCount++;
2055
+ break;
2056
+ case "unexpected":
2057
+ this.unexpectedCount++;
2058
+ break;
2059
+ case "flaky":
2060
+ this.flakyCount++;
2061
+ break;
2062
+ case "skipped":
2063
+ this.skippedCount++;
2064
+ break;
896
2065
  }
897
- const outcome = test.outcome();
898
- const testIsComplete = result.retry === test.retries || outcome !== "unexpected";
899
- if (!testIsComplete) {
900
- return;
2066
+ }
2067
+ this._report = this.buildReport(result);
2068
+ this.log(`\u{1F3C1} Test run finished`, {
2069
+ status: result.status,
2070
+ duration: `${result.duration}ms`,
2071
+ passed: this.expectedCount,
2072
+ failed: this.unexpectedCount,
2073
+ flaky: this.flakyCount,
2074
+ skipped: this.skippedCount
2075
+ });
2076
+ const outputDir = this.options.outputDir;
2077
+ if (outputDir) {
2078
+ const fs6 = await import("fs");
2079
+ fs6.mkdirSync(outputDir, { recursive: true });
2080
+ const outputPath = `${outputDir}/checkly-report.json`;
2081
+ fs6.writeFileSync(outputPath, JSON.stringify(this._report, null, 2));
2082
+ this.log(`\u{1F4C4} Report saved`, { path: outputPath });
2083
+ }
2084
+ for (const ext of this.extensions) {
2085
+ await ext.onEnd?.({
2086
+ result,
2087
+ report: this._report,
2088
+ log: this.createExtensionLogger(ext.name),
2089
+ addSummaryLine: (line) => this.summaryLines.push(line)
2090
+ });
2091
+ }
2092
+ this.printSummary();
2093
+ }
2094
+ async onExit() {
2095
+ this.log(`\u{1F44B} Reporter exiting`);
2096
+ for (const ext of this.extensions) {
2097
+ await ext.onExit?.({ log: this.createExtensionLogger(ext.name) });
2098
+ }
2099
+ }
2100
+ printsToStdio() {
2101
+ return false;
2102
+ }
2103
+ getReport() {
2104
+ if (this._report) return this._report;
2105
+ return this.buildReport({
2106
+ status: "passed",
2107
+ startTime: this.startTime,
2108
+ duration: Date.now() - this.startTime.getTime()
2109
+ });
2110
+ }
2111
+ buildReport(fullResult) {
2112
+ return {
2113
+ config: this.serializeConfig(),
2114
+ suites: this.serializeSuites(),
2115
+ errors: this.globalErrors.map((e) => this.serializeError(e, true)),
2116
+ stats: {
2117
+ startTime: this.startTime.toISOString(),
2118
+ duration: fullResult.duration,
2119
+ expected: this.expectedCount,
2120
+ unexpected: this.unexpectedCount,
2121
+ flaky: this.flakyCount,
2122
+ skipped: this.skippedCount
901
2123
  }
902
- const isFlaky = outcome === "flaky";
903
- if (isFlaky) {
904
- this.testCounts.flaky++;
905
- this.testCounts.passed++;
906
- } else {
907
- if (result.status === "passed") {
908
- this.testCounts.passed++;
909
- } else if (result.status === "failed" || result.status === "timedOut") {
910
- this.testCounts.failed++;
2124
+ };
2125
+ }
2126
+ reconstructProjectsIfNeeded() {
2127
+ if (this.config.projects.length > 0) {
2128
+ return;
2129
+ }
2130
+ this.log("\u{1F504} Detected merge-reports scenario (empty config.projects), reconstructing projects");
2131
+ for (const projectSuite of this.suite.suites) {
2132
+ const project = projectSuite.project();
2133
+ if (project) {
2134
+ const name = project.name || "default";
2135
+ if (!this.reconstructedProjects.has(name)) {
2136
+ this.reconstructedProjects.set(name, {
2137
+ id: name,
2138
+ name,
2139
+ testDir: project.testDir,
2140
+ outputDir: project.outputDir,
2141
+ timeout: project.timeout,
2142
+ retries: project.retries,
2143
+ repeatEach: project.repeatEach,
2144
+ metadata: project.metadata ?? {},
2145
+ testMatch: Array.isArray(project.testMatch) ? project.testMatch.map(String) : [String(project.testMatch)],
2146
+ testIgnore: Array.isArray(project.testIgnore) ? project.testIgnore.map(String) : [String(project.testIgnore)]
2147
+ });
911
2148
  }
912
2149
  }
913
- } catch (error) {
914
- console.error("[Checkly Reporter] Error in onTestEnd:", error);
915
2150
  }
2151
+ if (this.reconstructedProjects.size === 0) {
2152
+ this.collectProjectsFromTests(this.suite);
2153
+ }
2154
+ this.log("\u{1F504} Reconstructed projects", {
2155
+ count: this.reconstructedProjects.size,
2156
+ names: Array.from(this.reconstructedProjects.keys())
2157
+ });
916
2158
  }
917
- /**
918
- * Called after all tests have completed
919
- * This is where we create the ZIP archive and upload results
920
- */
921
- async onEnd() {
922
- try {
923
- const jsonReportPath = this.options.jsonReportPath;
924
- if (!fs3.existsSync(jsonReportPath)) {
925
- console.error(`[Checkly Reporter] ERROR: JSON report not found at: ${jsonReportPath}`);
926
- console.error("[Checkly Reporter] Make sure to configure the json reporter before the checkly reporter:");
927
- console.error(
928
- " reporter: [\n ['json', { outputFile: 'test-results/playwright-test-report.json' }],\n ['@checkly/playwright-reporter']\n ]"
929
- );
930
- return;
931
- }
932
- const reportContent = fs3.readFileSync(jsonReportPath, "utf-8");
933
- const report = JSON.parse(reportContent);
934
- this.injectDataIntoReport(report);
935
- fs3.writeFileSync(jsonReportPath, JSON.stringify(report, null, 2), "utf-8");
936
- const assets = await this.assetCollector.collectAssets(report);
937
- const result = await this.zipper.createZip(jsonReportPath, assets);
938
- if (this.testResults && this.testSession) {
939
- await this.uploadResults(report, result.zipPath, result.entries);
940
- if (!this.options.dryRun) {
941
- try {
942
- fs3.unlinkSync(result.zipPath);
943
- } catch (cleanupError) {
944
- console.warn(`[Checkly Reporter] Warning: Could not delete ZIP file: ${cleanupError}`);
945
- }
2159
+ collectProjectsFromTests(suite) {
2160
+ for (const test of suite.allTests()) {
2161
+ const project = test.parent.project();
2162
+ const projectName = project?.name || "default";
2163
+ if (!this.reconstructedProjects.has(projectName)) {
2164
+ if (project) {
2165
+ this.reconstructedProjects.set(projectName, {
2166
+ id: projectName,
2167
+ name: projectName,
2168
+ testDir: project.testDir,
2169
+ outputDir: project.outputDir,
2170
+ timeout: project.timeout,
2171
+ retries: project.retries,
2172
+ repeatEach: project.repeatEach,
2173
+ metadata: project.metadata ?? {},
2174
+ testMatch: Array.isArray(project.testMatch) ? project.testMatch.map(String) : [String(project.testMatch)],
2175
+ testIgnore: Array.isArray(project.testIgnore) ? project.testIgnore.map(String) : [String(project.testIgnore)]
2176
+ });
2177
+ } else {
2178
+ this.reconstructedProjects.set(projectName, {
2179
+ id: projectName,
2180
+ name: projectName,
2181
+ testDir: "",
2182
+ outputDir: "",
2183
+ timeout: 0,
2184
+ retries: 0,
2185
+ repeatEach: 1,
2186
+ metadata: {},
2187
+ testMatch: [],
2188
+ testIgnore: []
2189
+ });
946
2190
  }
947
2191
  }
948
- if (this.testResults && this.testSession?.link) {
949
- this.printSummary(report, this.testSession);
950
- }
951
- } catch (error) {
952
- console.error("[Checkly Reporter] ERROR creating report:", error);
953
2192
  }
954
2193
  }
955
- printSummary(report, testSession) {
956
- const rule = pluralRules.select(report.config.projects.length);
957
- console.log("\n======================================================\n");
958
- console.log(`\u{1F99D} Checkly reporter: ${pkgVersion}`);
959
- console.log(`\u{1F3AD} Playwright: ${report.config.version}`);
960
- console.log(`\u{1F4D4} ${projectForms[rule]}: ${report.config.projects.map(({ name }) => name).join(",")}`);
961
- console.log(`\u{1F517} Test session URL: ${testSession.link}`);
962
- console.log("\n======================================================");
2194
+ serializeConfig() {
2195
+ const c = this.config;
2196
+ const projects = c.projects.length > 0 ? c.projects.map((p) => this.serializeProject(p)) : Array.from(this.reconstructedProjects.values());
2197
+ return {
2198
+ rootDir: c.rootDir,
2199
+ configFile: c.configFile ?? void 0,
2200
+ version: c.version,
2201
+ workers: c.workers,
2202
+ fullyParallel: c.fullyParallel,
2203
+ forbidOnly: c.forbidOnly,
2204
+ globalTimeout: c.globalTimeout,
2205
+ maxFailures: c.maxFailures,
2206
+ metadata: c.metadata ?? {},
2207
+ projects,
2208
+ shard: c.shard ?? null,
2209
+ tags: c.tags ?? [],
2210
+ updateSourceMethod: c.updateSourceMethod,
2211
+ preserveOutput: c.preserveOutput,
2212
+ quiet: c.quiet,
2213
+ reportSlowTests: c.reportSlowTests ?? null,
2214
+ webServer: c.webServer ?? null,
2215
+ globalSetup: c.globalSetup ?? null,
2216
+ globalTeardown: c.globalTeardown ?? null,
2217
+ grep: c.grep,
2218
+ grepInvert: c.grepInvert,
2219
+ reporter: c.reporter,
2220
+ updateSnapshots: c.updateSnapshots
2221
+ };
963
2222
  }
964
- /**
965
- * Injects captured steps and warnings into the JSON report
966
- * Traverses the report structure and matches by test ID + retry
967
- */
968
- injectDataIntoReport(report) {
969
- const processSuite = (suite) => {
970
- for (const spec of suite.specs) {
971
- for (const test of spec.tests) {
972
- for (const result of test.results) {
973
- const key = `${spec.id}:${result.retry}`;
974
- const steps = this.stepsMap.get(key);
975
- if (steps) {
976
- result.steps = steps;
977
- }
978
- const warnings = this.warningsMap.get(key);
979
- if (warnings && warnings.length > 0) {
980
- result._checkly = { warnings };
981
- }
982
- }
983
- }
984
- }
985
- if (suite.suites) {
986
- for (const nestedSuite of suite.suites) {
987
- processSuite(nestedSuite);
2223
+ serializeProject(p) {
2224
+ return {
2225
+ id: p.name,
2226
+ name: p.name,
2227
+ testDir: p.testDir,
2228
+ outputDir: p.outputDir,
2229
+ timeout: p.timeout,
2230
+ retries: p.retries,
2231
+ repeatEach: p.repeatEach,
2232
+ metadata: p.metadata ?? {},
2233
+ testMatch: Array.isArray(p.testMatch) ? p.testMatch.map(String) : [String(p.testMatch)],
2234
+ testIgnore: Array.isArray(p.testIgnore) ? p.testIgnore.map(String) : [String(p.testIgnore)]
2235
+ };
2236
+ }
2237
+ serializeSuites() {
2238
+ const fileSuites = /* @__PURE__ */ new Map();
2239
+ for (const projectSuite of this.suite.suites) {
2240
+ for (const fileSuite of projectSuite.suites) {
2241
+ const file = fileSuite.location?.file;
2242
+ if (!file) continue;
2243
+ const fileName = this.getFileName(file);
2244
+ const serialized = this.serializeSuite(fileSuite, fileName);
2245
+ if (!serialized) continue;
2246
+ const existing = fileSuites.get(file);
2247
+ if (existing) {
2248
+ this.mergeSuites(existing, serialized);
2249
+ } else {
2250
+ fileSuites.set(file, serialized);
988
2251
  }
989
2252
  }
990
- };
991
- for (const suite of report.suites) {
992
- processSuite(suite);
993
2253
  }
994
- this.reconstructProjectsFromTests(report);
2254
+ return Array.from(fileSuites.values());
995
2255
  }
996
- /**
997
- * Reconstructs config.projects and test.projectId from test data
998
- * This is necessary for blob merge scenarios where Playwright's JSON reporter
999
- * doesn't populate projects array or projectId fields
1000
- */
1001
- reconstructProjectsFromTests(report) {
1002
- const projectNames = /* @__PURE__ */ new Set();
1003
- const collectProjectNames = (suite) => {
1004
- for (const spec of suite.specs) {
1005
- for (const test of spec.tests) {
1006
- const testAny = test;
1007
- if (testAny.projectName) {
1008
- projectNames.add(testAny.projectName);
1009
- }
1010
- if (testAny.projectName && !testAny.projectId) {
1011
- testAny.projectId = testAny.projectName;
1012
- }
1013
- }
2256
+ serializeSuite(suite, fileName) {
2257
+ const allTests = suite.allTests();
2258
+ if (allTests.length === 0) return null;
2259
+ const childSuites = [];
2260
+ for (const child of suite.suites) {
2261
+ const serialized = this.serializeSuite(child, fileName);
2262
+ if (serialized) childSuites.push(serialized);
2263
+ }
2264
+ const specs = [];
2265
+ for (const test of suite.tests) {
2266
+ const testData = this.tests.get(test.id);
2267
+ if (testData) {
2268
+ specs.push(this.serializeSpec(testData));
1014
2269
  }
1015
- if (suite.suites) {
1016
- for (const nestedSuite of suite.suites) {
1017
- collectProjectNames(nestedSuite);
1018
- }
2270
+ }
2271
+ return {
2272
+ title: suite.title || fileName,
2273
+ file: fileName,
2274
+ line: suite.location?.line ?? 0,
2275
+ column: suite.location?.column ?? 0,
2276
+ specs,
2277
+ suites: childSuites.length ? childSuites : void 0
2278
+ };
2279
+ }
2280
+ mergeSuites(to, from) {
2281
+ for (const fromSuite of from.suites || []) {
2282
+ const toSuite = to.suites?.find(
2283
+ (s) => s.title === fromSuite.title && s.line === fromSuite.line && s.column === fromSuite.column
2284
+ );
2285
+ if (toSuite) {
2286
+ this.mergeSuites(toSuite, fromSuite);
2287
+ } else {
2288
+ to.suites = to.suites ?? [];
2289
+ to.suites.push(fromSuite);
1019
2290
  }
2291
+ }
2292
+ to.specs.push(...from.specs);
2293
+ }
2294
+ serializeSpec(data) {
2295
+ const { testCase } = data;
2296
+ const outcome = testCase.outcome();
2297
+ return {
2298
+ id: testCase.id,
2299
+ title: testCase.title,
2300
+ file: this.getFileName(testCase.location.file),
2301
+ line: testCase.location.line,
2302
+ column: testCase.location.column,
2303
+ tags: this.extractTags(testCase),
2304
+ ok: outcome === "expected" || outcome === "flaky" || outcome === "skipped",
2305
+ tests: [this.serializeTest(data)]
2306
+ };
2307
+ }
2308
+ extractTags(testCase) {
2309
+ const runtimeTags = testCase.tags;
2310
+ if (runtimeTags && runtimeTags.length > 0) {
2311
+ return runtimeTags.map((t) => t.replace(/^@/, ""));
2312
+ }
2313
+ const titleTags = testCase.title.match(/@[\w-]+/g);
2314
+ return titleTags?.map((t) => t.replace(/^@/, "")) ?? [];
2315
+ }
2316
+ serializeTest(data) {
2317
+ const { testCase, results } = data;
2318
+ const projectName = this.getProjectName(testCase);
2319
+ return {
2320
+ projectId: projectName,
2321
+ projectName,
2322
+ timeout: testCase.timeout,
2323
+ expectedStatus: testCase.expectedStatus,
2324
+ annotations: this.serializeAnnotations(testCase.annotations),
2325
+ results: results.map((r) => this.serializeTestResult(r)),
2326
+ status: this.mapOutcome(testCase.outcome())
1020
2327
  };
1021
- for (const suite of report.suites) {
1022
- collectProjectNames(suite);
2328
+ }
2329
+ getProjectName(testCase) {
2330
+ const project = testCase.parent.project();
2331
+ if (project && typeof project.name === "string") {
2332
+ return project.name;
1023
2333
  }
1024
- const configAny = report.config;
1025
- if ((!configAny.projects || configAny.projects.length === 0) && projectNames.size > 0) {
1026
- configAny.projects = Array.from(projectNames).map((name) => ({
1027
- id: name,
1028
- name
1029
- }));
2334
+ let current = testCase.parent;
2335
+ while (current) {
2336
+ const suiteProject = current.project();
2337
+ if (suiteProject && typeof suiteProject.name === "string") {
2338
+ return suiteProject.name;
2339
+ }
2340
+ current = current.parent;
1030
2341
  }
2342
+ return "default";
1031
2343
  }
1032
- /**
1033
- * Uploads test results to Checkly API
1034
- */
1035
- async uploadResults(report, zipPath, entries) {
1036
- if (!this.testResults || !this.testSession) {
1037
- return;
2344
+ serializeTestResult(r) {
2345
+ return {
2346
+ workerIndex: r.workerIndex,
2347
+ parallelIndex: r.parallelIndex ?? 0,
2348
+ status: r.status,
2349
+ duration: r.duration,
2350
+ startTime: r.startTime.toISOString(),
2351
+ retry: r.retry,
2352
+ errors: r.errors.map((e) => this.serializeError(e, true)),
2353
+ error: r.error ? this.serializeError(r.error, false) : void 0,
2354
+ errorLocation: r.error?.location,
2355
+ stdout: r.stdout.map(
2356
+ (s) => typeof s === "string" ? { text: s } : Buffer.isBuffer(s) ? { buffer: s.toString("base64") } : s
2357
+ ),
2358
+ stderr: r.stderr.map(
2359
+ (s) => typeof s === "string" ? { text: s } : Buffer.isBuffer(s) ? { buffer: s.toString("base64") } : s
2360
+ ),
2361
+ attachments: r.attachments.map((a) => ({
2362
+ name: a.name,
2363
+ contentType: a.contentType,
2364
+ path: a.path,
2365
+ body: a.body ? a.body.toString("base64") : void 0
2366
+ })),
2367
+ steps: r.steps.map((s) => this.serializeStep(s)),
2368
+ annotations: this.serializeAnnotations(
2369
+ r.annotations ?? []
2370
+ )
2371
+ };
2372
+ }
2373
+ serializeStep(step) {
2374
+ return {
2375
+ title: step.title,
2376
+ duration: step.duration,
2377
+ error: step.error ? this.serializeError(step.error, false) : void 0,
2378
+ steps: step.steps?.map((s) => this.serializeStep(s))
2379
+ };
2380
+ }
2381
+ serializeError(error, includeContext) {
2382
+ if (error.value !== void 0) {
2383
+ return { message: error.value, value: error.value };
1038
2384
  }
1039
- try {
1040
- const { failed: failedCount, flaky: flakyCount } = this.testCounts;
1041
- const overallStatus = failedCount > 0 ? "FAILED" : "PASSED";
1042
- const isDegraded = failedCount === 0 && flakyCount > 0;
1043
- const endTime = /* @__PURE__ */ new Date();
1044
- const responseTime = this.startTime ? Math.max(0, endTime.getTime() - this.startTime.getTime()) : 0;
1045
- const zipSizeBytes = (await fs3.promises.stat(zipPath)).size;
1046
- if (this.testSession.testResults.length > 0) {
1047
- const firstResult = this.testSession.testResults[0];
1048
- let assetId;
1049
- if (zipSizeBytes > 0) {
1050
- try {
1051
- const assets = fs3.createReadStream(zipPath);
1052
- const uploadResponse = await this.testResults.uploadTestResultAsset(
1053
- this.testSession.testSessionId,
1054
- firstResult.testResultId,
1055
- assets
1056
- );
1057
- assetId = uploadResponse.assetId;
1058
- } catch (error) {
1059
- const errorMessage = error instanceof Error ? error.message : String(error);
1060
- console.error("[Checkly Reporter] Asset upload failed:", errorMessage);
1061
- }
1062
- }
1063
- await this.testResults.updateTestResult(this.testSession.testSessionId, firstResult.testResultId, {
1064
- status: overallStatus,
1065
- assetEntries: assetId ? entries : void 0,
1066
- isDegraded,
1067
- startedAt: this.startTime?.toISOString(),
1068
- stoppedAt: endTime.toISOString(),
1069
- responseTime,
1070
- metadata: {
1071
- usageData: {
1072
- s3PostTotalBytes: zipSizeBytes
1073
- }
1074
- }
1075
- });
2385
+ let msg = error.message ?? "Unknown error";
2386
+ if (includeContext && !/^(\w+Error|Error):/.test(msg)) {
2387
+ msg = "Error: " + msg;
2388
+ }
2389
+ if (includeContext) {
2390
+ if (error.snippet) msg += "\n\n" + error.snippet;
2391
+ if (error.stack) {
2392
+ const frames = error.stack.split("\n").filter((l) => l.trim().startsWith("at "));
2393
+ if (frames.length) msg += "\n" + frames.join("\n");
2394
+ } else if (error.location) {
2395
+ msg += `
2396
+ at ${error.location.file}:${error.location.line}:${error.location.column}`;
1076
2397
  }
1077
- } catch (error) {
1078
- const errorMessage = error instanceof Error ? error.message : String(error);
1079
- console.error("[Checkly Reporter] Failed to upload results:", errorMessage);
1080
2398
  }
2399
+ return {
2400
+ message: msg,
2401
+ location: error.location,
2402
+ stack: error.stack,
2403
+ snippet: error.snippet
2404
+ };
1081
2405
  }
1082
- /**
1083
- * Called when a global error occurs
1084
- */
1085
- onError(error) {
1086
- console.error("[Checkly Reporter] Global error:", error);
2406
+ serializeAnnotations(annotations) {
2407
+ return annotations.map((a) => ({ type: a.type, description: a.description, location: a.location }));
2408
+ }
2409
+ countTests(suite) {
2410
+ let count = suite.tests?.length ?? 0;
2411
+ for (const child of suite.suites ?? []) {
2412
+ count += this.countTests(child);
2413
+ }
2414
+ return count;
2415
+ }
2416
+ getFileName(filePath) {
2417
+ return filePath.split("/").pop() ?? filePath;
2418
+ }
2419
+ mapOutcome(outcome) {
2420
+ if (outcome === "expected" || outcome === "unexpected" || outcome === "flaky" || outcome === "skipped") {
2421
+ return outcome;
2422
+ }
2423
+ return "unexpected";
2424
+ }
2425
+ printSummary() {
2426
+ const pkgVersion = getPackageVersion2();
2427
+ const playwrightVersion = this.config.version;
2428
+ const projectNames = this.config.projects.length > 0 ? this.config.projects.map((p) => p.name).join(", ") : Array.from(this.reconstructedProjects.keys()).join(", ") || "default";
2429
+ const projectCount = this.config.projects.length > 0 ? this.config.projects.length : this.reconstructedProjects.size || 1;
2430
+ const rule = pluralRules.select(projectCount);
2431
+ console.log("\n======================================================\n");
2432
+ console.log(`\u{1F99D} Checkly reporter: ${pkgVersion}`);
2433
+ console.log(`\u{1F3AD} Playwright: ${playwrightVersion}`);
2434
+ console.log(`\u{1F4D4} ${projectForms[rule]}: ${projectNames}`);
2435
+ for (const line of this.summaryLines) {
2436
+ console.log(line);
2437
+ }
2438
+ console.log("\n======================================================");
1087
2439
  }
1088
2440
  };
1089
- export {
1090
- AssetCollector,
1091
- ChecklyReporter,
1092
- Zipper,
1093
- ChecklyReporter as default
2441
+
2442
+ // src/index.ts
2443
+ var ChecklyReporter = class extends BaseReporter {
2444
+ constructor(options = {}) {
2445
+ super(options);
2446
+ this.use(checklyUpload(options));
2447
+ }
1094
2448
  };
2449
+ var index_default = ChecklyReporter;
2450
+ function createChecklyReporter(options = {}) {
2451
+ return ["@checkly/playwright-reporter", options];
2452
+ }
2453
+ // Annotate the CommonJS export names for ESM import in node:
2454
+ 0 && (module.exports = {
2455
+ createChecklyReporter
2456
+ });