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