@checkly/playwright-reporter 0.1.10 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,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";
@@ -269,6 +282,19 @@ function toConsoleMessage(event) {
269
282
  };
270
283
  }
271
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
+
272
298
  // ../utils/src/network-adapter.ts
273
299
  import { createHash as createHash2 } from "crypto";
274
300
  function generateId2(url, method, startedAt) {
@@ -491,11 +517,729 @@ var TraceReader = class {
491
517
  import * as fs3 from "fs";
492
518
  import * as os from "os";
493
519
  import * as path2 from "path";
494
- 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
495
1235
  var Zipper = class {
496
1236
  outputPath;
1237
+ compressionLevel;
1238
+ onProgress;
497
1239
  constructor(options) {
498
1240
  this.outputPath = options.outputPath;
1241
+ this.compressionLevel = options.compressionLevel ?? 0;
1242
+ this.onProgress = options.onProgress;
499
1243
  }
500
1244
  /**
501
1245
  * Creates a ZIP archive containing the JSON report and assets
@@ -504,63 +1248,94 @@ var Zipper = class {
504
1248
  * @returns ZIP creation result with metadata
505
1249
  */
506
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) {
507
1291
  const entries = [];
508
- return new Promise((resolve2, reject) => {
509
- try {
510
- const output = fs3.createWriteStream(this.outputPath);
511
- const archive = new ZipArchive({
512
- zlib: { level: 0 }
513
- });
514
- archive.on("entry", (entryData) => {
515
- const entryName = entryData.name.replace(/\\/g, "/");
516
- const start = entryData._offsets?.contents ?? 0;
517
- const end = entryData._offsets?.contents + (entryData.csize ?? 0) - 1;
518
- entries.push({
519
- name: entryName,
520
- start,
521
- end
522
- });
523
- });
524
- output.on("close", () => {
525
- const zipSize = archive.pointer();
526
- resolve2({
527
- zipPath: this.outputPath,
528
- size: zipSize,
529
- entryCount: entries.length,
530
- entries
531
- });
532
- });
533
- archive.on("error", (err) => {
534
- reject(err);
535
- });
536
- output.on("error", (err) => {
537
- reject(err);
538
- });
539
- archive.pipe(output);
540
- if (!fs3.existsSync(reportPath)) {
541
- reject(new Error(`Report file not found: ${reportPath}`));
542
- return;
543
- }
544
- const transformedReportPath = this.transformJsonReport(reportPath);
545
- archive.file(transformedReportPath, { name: "output/playwright-test-report.json" });
546
- for (const asset of assets) {
547
- if (!fs3.existsSync(asset.sourcePath)) {
548
- console.warn(`[Checkly Reporter] Skipping missing asset: ${asset.sourcePath}`);
549
- continue;
550
- }
551
- 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));
552
1312
  }
553
- archive.finalize();
554
- } catch (error) {
555
- reject(error);
556
1313
  }
557
- });
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;
558
1336
  }
559
1337
  /**
560
1338
  * Transforms the JSON report to use relative paths for attachments
561
- * This ensures the UI can map attachment paths to ZIP entries
562
- * @param reportPath - Path to the original JSON report
563
- * @returns Path to the transformed JSON report (in temp directory)
564
1339
  */
565
1340
  transformJsonReport(reportPath) {
566
1341
  const reportContent = fs3.readFileSync(reportPath, "utf-8");
@@ -572,8 +1347,6 @@ var Zipper = class {
572
1347
  }
573
1348
  /**
574
1349
  * Recursively transforms attachment paths in the report structure
575
- * Converts absolute paths to relative paths matching ZIP structure
576
- * @param obj - Object to transform (mutated in place)
577
1350
  */
578
1351
  transformAttachmentPaths(obj) {
579
1352
  if (typeof obj !== "object" || obj === null) {
@@ -593,21 +1366,7 @@ var Zipper = class {
593
1366
  Object.values(obj).forEach((value) => this.transformAttachmentPaths(value));
594
1367
  }
595
1368
  /**
596
- * Normalizes attachment paths by extracting the relevant snapshot directory portion.
597
- * Supports Playwright's default and common custom snapshot directory patterns,
598
- * as well as blob merge resource paths.
599
- *
600
- * Priority order (first match wins):
601
- * 1. test-results/ (highest priority, existing behavior)
602
- * 2. blob-reports/resources/ (blob merge extraction paths)
603
- * 3. snapshots directories (Playwright default pattern)
604
- * 4. __screenshots__/ (common custom pattern)
605
- * 5. __snapshots__/ (common custom pattern)
606
- * 6. screenshots/ (simple custom pattern)
607
- * 7. snapshots/ (simple custom pattern)
608
- *
609
- * @param attachmentPath - Absolute or relative path to attachment
610
- * @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
611
1370
  */
612
1371
  normalizeAttachmentPath(attachmentPath) {
613
1372
  const normalizedPath = attachmentPath.replace(/\\/g, "/");
@@ -644,17 +1403,11 @@ var Zipper = class {
644
1403
  }
645
1404
  };
646
1405
 
647
- // src/reporter.ts
648
- import * as fs4 from "fs";
649
- import { readFileSync as readFileSync4 } from "fs";
650
- import * as path3 from "path";
651
- import { dirname, join as join3 } from "path";
652
- import { fileURLToPath } from "url";
653
-
654
- // ../clients/src/checkly-client.ts
1406
+ // src/api/client.ts
655
1407
  import axios from "axios";
1408
+ import FormData from "form-data";
656
1409
 
657
- // ../clients/src/errors.ts
1410
+ // src/api/errors.ts
658
1411
  var ApiError = class extends Error {
659
1412
  data;
660
1413
  constructor(data, options) {
@@ -664,44 +1417,20 @@ var ApiError = class extends Error {
664
1417
  }
665
1418
  };
666
1419
  var ValidationError = class extends ApiError {
667
- constructor(data, options) {
668
- super(data, options);
669
- }
670
1420
  };
671
1421
  var UnauthorizedError = class extends ApiError {
672
- constructor(data, options) {
673
- super(data, options);
674
- }
675
1422
  };
676
1423
  var ForbiddenError = class extends ApiError {
677
- constructor(data, options) {
678
- super(data, options);
679
- }
680
1424
  };
681
1425
  var NotFoundError = class extends ApiError {
682
- constructor(data, options) {
683
- super(data, options);
684
- }
685
1426
  };
686
1427
  var RequestTimeoutError = class extends ApiError {
687
- constructor(data, options) {
688
- super(data, options);
689
- }
690
1428
  };
691
1429
  var ConflictError = class extends ApiError {
692
- constructor(data, options) {
693
- super(data, options);
694
- }
695
1430
  };
696
1431
  var ServerError = class extends ApiError {
697
- constructor(data, options) {
698
- super(data, options);
699
- }
700
1432
  };
701
1433
  var MiscellaneousError = class extends ApiError {
702
- constructor(data, options) {
703
- super(data, options);
704
- }
705
1434
  };
706
1435
  var MissingResponseError = class extends Error {
707
1436
  constructor(message, options) {
@@ -709,63 +1438,58 @@ var MissingResponseError = class extends Error {
709
1438
  this.name = "MissingResponseError";
710
1439
  }
711
1440
  };
712
- function parseErrorData(data, options) {
713
- if (!data) {
714
- return void 0;
715
- }
716
- if (typeof data === "object" && data.statusCode && data.error && data.message) {
717
- return {
718
- statusCode: data.statusCode,
719
- error: data.error,
720
- message: data.message,
721
- errorCode: data.errorCode
722
- };
723
- }
724
- if (typeof data === "object" && data.error && !data.message) {
725
- return {
726
- statusCode: options.statusCode,
727
- error: data.error,
728
- message: data.error
729
- };
730
- }
731
- if (typeof data === "object" && data.error && data.message) {
732
- return {
733
- statusCode: options.statusCode,
734
- error: data.error,
735
- message: data.message,
736
- errorCode: data.errorCode
737
- };
738
- }
739
- if (typeof data === "object" && data.message) {
740
- return {
741
- statusCode: options.statusCode,
742
- error: data.message,
743
- message: data.message,
744
- errorCode: data.errorCode
745
- };
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
+ }
746
1476
  }
747
1477
  if (typeof data === "string") {
748
- return {
749
- statusCode: options.statusCode,
750
- error: data,
751
- message: data
752
- };
1478
+ return { statusCode, error: data, message: data };
753
1479
  }
754
1480
  return void 0;
755
1481
  }
756
- function handleErrorResponse(err) {
757
- if (!err.response) {
758
- throw new MissingResponseError(err.message || "Network error");
759
- }
760
- const { status, data } = err.response;
761
- const errorData = parseErrorData(data, { statusCode: status });
762
- if (!errorData) {
763
- throw new MiscellaneousError({
764
- statusCode: status,
765
- error: "Unknown error",
766
- message: err.message || "An error occurred"
767
- });
768
- }
1482
+ function handleErrorResponse(err2) {
1483
+ const axiosError = err2;
1484
+ if (!axiosError.response) {
1485
+ throw new MissingResponseError(axiosError.message || "Network error");
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
+ };
769
1493
  switch (status) {
770
1494
  case 400:
771
1495
  throw new ValidationError(errorData);
@@ -787,132 +1511,326 @@ function handleErrorResponse(err) {
787
1511
  }
788
1512
  }
789
1513
 
790
- // ../clients/src/checkly-client.ts
791
- function getVersion() {
792
- return "0.1.0";
793
- }
794
- function createRequestInterceptor(apiKey, accountId) {
795
- 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) => {
796
1523
  if (config.headers) {
797
- config.headers.Authorization = `Bearer ${apiKey}`;
798
- config.headers["x-checkly-account"] = accountId;
799
- config.headers["User-Agent"] = `@checkly/playwright-reporter/${getVersion()}`;
1524
+ config.headers.Authorization = `Bearer ${options.apiKey}`;
1525
+ config.headers["x-checkly-account"] = options.accountId;
800
1526
  }
801
1527
  return config;
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
+ }
802
1557
  };
803
1558
  }
804
- function createResponseErrorInterceptor() {
805
- return (error) => {
806
- 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"
807
1567
  };
1568
+ return urls[environment];
808
1569
  }
809
- var ChecklyClient = class {
810
- apiKey;
811
- baseUrl;
812
- accountId;
813
- api;
814
- constructor(options) {
815
- this.accountId = options.accountId;
816
- this.apiKey = options.apiKey;
817
- this.baseUrl = options.baseUrl;
818
- this.api = axios.create({
819
- baseURL: this.baseUrl,
820
- timeout: 12e4,
821
- // 120 second timeout for large uploads
822
- maxContentLength: Number.POSITIVE_INFINITY,
823
- // Allow large payloads
824
- maxBodyLength: Number.POSITIVE_INFINITY
825
- // Allow large request bodies
826
- });
827
- this.api.interceptors.request.use(createRequestInterceptor(this.apiKey, this.accountId));
828
- this.api.interceptors.response.use((response) => response, createResponseErrorInterceptor());
829
- }
830
- /**
831
- * Gets the underlying axios instance
832
- * Useful for creating resource-specific clients (e.g., TestResults)
833
- */
834
- getAxiosInstance() {
835
- return this.api;
836
- }
837
- };
838
-
839
- // ../clients/src/test-results.ts
840
- import FormData from "form-data";
841
- var TestResults = class {
842
- constructor(api) {
843
- this.api = api;
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";
844
1576
  }
845
- /**
846
- * Creates a new test session in Checkly
847
- *
848
- * @param request Test session creation request
849
- * @returns Test session response with session ID and test result IDs
850
- * @throws {ValidationError} If request data is invalid
851
- * @throws {UnauthorizedError} If authentication fails
852
- * @throws {ServerError} If server error occurs
853
- */
854
- async createTestSession(request) {
855
- const response = await this.api.post("/next/test-sessions/create", request);
856
- return response.data;
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");
1595
+ }
1596
+ if (options.outputPath) {
1597
+ console.warn("[Checkly] Warning: outputPath is deprecated, ZIP is now written to {outputDir}/checkly-report.zip");
1598
+ }
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 });
1612
+ }
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
+ };
857
1663
  }
858
- /**
859
- * Step 1: Upload test result assets to S3
860
- * Streams a ZIP file containing test assets (traces, videos, screenshots)
861
- *
862
- * @param testSessionId ID of the test session
863
- * @param testResultId ID of the test result
864
- * @param assets Buffer or ReadableStream of the ZIP file
865
- * @returns Upload response with assetId, region, key, and url
866
- * @throws {ValidationError} If assets are invalid
867
- * @throws {UnauthorizedError} If authentication fails
868
- * @throws {NotFoundError} If test session or result not found
869
- * @throws {PayloadTooLargeError} If assets exceed 500MB
870
- * @throws {ServerError} If S3 upload fails
871
- */
872
- async uploadTestResultAsset(testSessionId, testResultId, assets) {
873
- const form = new FormData();
874
- form.append("assets", assets, {
875
- filename: "assets.zip",
876
- contentType: "application/zip"
877
- });
878
- const response = await this.api.post(
879
- `/next/test-sessions/${testSessionId}/results/${testResultId}/assets`,
880
- form,
881
- {
882
- headers: {
883
- ...form.getHeaders()
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
+ }
884
1676
  }
885
1677
  }
886
- );
887
- return response.data;
1678
+ suite.suites?.forEach(processSuite);
1679
+ }
1680
+ report.suites.forEach(processSuite);
888
1681
  }
889
- /**
890
- * Step 2: Update test result with status and optional asset reference
891
- * Uses JSON payload for clean, easy-to-validate updates
892
- *
893
- * @param testSessionId ID of the test session
894
- * @param testResultId ID of the test result to update
895
- * @param request Test result update request (JSON)
896
- * @returns Test result update response
897
- * @throws {ValidationError} If request data is invalid
898
- * @throws {UnauthorizedError} If authentication fails
899
- * @throws {NotFoundError} If test session or result not found
900
- * @throws {ServerError} If server error occurs
901
- */
902
- async updateTestResult(testSessionId, testResultId, request) {
903
- const response = await this.api.post(
904
- `/next/test-sessions/${testSessionId}/results/${testResultId}`,
905
- request
906
- );
907
- 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
+ };
908
1699
  }
909
- };
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
+ }
910
1819
 
911
1820
  // src/reporter.ts
1821
+ import { readFileSync as readFileSync3 } from "fs";
1822
+ import { dirname, join as join4 } from "path";
1823
+ import { fileURLToPath } from "url";
912
1824
  var __filename = fileURLToPath(import.meta.url);
913
1825
  var __dirname = dirname(__filename);
914
- var packageJson = JSON.parse(readFileSync4(join3(__dirname, "..", "package.json"), "utf-8"));
915
- 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
+ }
916
1834
  var pluralRules = new Intl.PluralRules("en-US");
917
1835
  var projectForms = {
918
1836
  zero: "Project",
@@ -922,580 +1840,572 @@ var projectForms = {
922
1840
  many: "Projects",
923
1841
  other: "Projects"
924
1842
  };
925
- function getApiUrl(environment) {
926
- const environments = {
927
- local: "http://127.0.0.1:3000",
928
- development: "https://api-dev.checklyhq.com",
929
- staging: "https://api-test.checklyhq.com",
930
- production: "https://api.checklyhq.com"
931
- };
932
- return environments[environment];
933
- }
934
- function getEnvironment(options) {
935
- const envFromOptions = options?.environment;
936
- const envFromEnvVar = process.env.CHECKLY_ENV;
937
- const env = envFromOptions || envFromEnvVar || "production";
938
- const validEnvironments = ["local", "development", "staging", "production"];
939
- if (!validEnvironments.includes(env)) {
940
- console.warn(`[Checkly Reporter] Invalid environment "${env}", using "production"`);
941
- return "production";
942
- }
943
- return env;
944
- }
945
- function convertStepToJSON(step) {
946
- return {
947
- title: step.title,
948
- duration: step.duration,
949
- error: step.error,
950
- steps: step.steps.length > 0 ? step.steps.map(convertStepToJSON) : void 0
951
- };
952
- }
953
- function getDirectoryName() {
954
- const cwd = process.cwd();
955
- let dirName = path3.basename(cwd);
956
- if (!dirName || dirName === "/" || dirName === ".") {
957
- dirName = "playwright-tests";
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
+ // Reconstructed projects for merge-reports scenarios
1859
+ reconstructedProjects = /* @__PURE__ */ new Map();
1860
+ constructor(options = {}) {
1861
+ this.options = options;
1862
+ this.verbose = options.verbose ?? process.env.CHECKLY_REPORTER_VERBOSE === "true";
958
1863
  }
959
- dirName = dirName.replace(/[<>:"|?*]/g, "-");
960
- if (dirName.length > 255) {
961
- dirName = dirName.substring(0, 255);
1864
+ use(extension) {
1865
+ this.extensions.push(extension);
1866
+ return this;
962
1867
  }
963
- return dirName;
964
- }
965
- var ChecklyReporter = class {
966
- options;
967
- assetCollector;
968
- zipper;
969
- testResults;
970
- testSession;
971
- startTime;
972
- testCounts = {
973
- passed: 0,
974
- failed: 0,
975
- flaky: 0
976
- };
977
- // Store steps per test result, keyed by "testId:retry"
978
- stepsMap = /* @__PURE__ */ new Map();
979
- // Store warnings per test result, keyed by "testId:retry"
980
- warningsMap = /* @__PURE__ */ new Map();
981
- // Store trace file paths per test result, keyed by "testId:retry"
982
- tracePathsMap = /* @__PURE__ */ new Map();
983
- // Store console messages per test result, keyed by "testId:retry"
984
- consoleMessagesMap = /* @__PURE__ */ new Map();
985
- // Store network requests per test result, keyed by "testId:retry"
986
- networkRequestsMap = /* @__PURE__ */ new Map();
987
- /**
988
- * Log a message if verbose mode is enabled
989
- */
990
1868
  log(message, data) {
991
- if (!this.options.verbose) return;
992
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
993
- if (data) {
994
- console.log(`[Checkly Reporter DEBUG ${timestamp}] ${message}`, JSON.stringify(data, null, 2));
1869
+ if (!this.verbose) return;
1870
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].slice(0, 12);
1871
+ const prefix = `[Checkly ${timestamp}]`;
1872
+ if (data && Object.keys(data).length > 0) {
1873
+ console.log(`${prefix} ${message}`, data);
995
1874
  } else {
996
- console.log(`[Checkly Reporter DEBUG ${timestamp}] ${message}`);
1875
+ console.log(`${prefix} ${message}`);
997
1876
  }
998
1877
  }
999
- constructor(options = {}) {
1000
- const environment = getEnvironment(options);
1001
- const baseUrl = getApiUrl(environment);
1002
- const apiKey = process.env.CHECKLY_API_KEY || options.apiKey;
1003
- const accountId = process.env.CHECKLY_ACCOUNT_ID || options.accountId;
1004
- const verbose = options.verbose ?? process.env.CHECKLY_REPORTER_VERBOSE === "true";
1005
- this.options = {
1006
- accountId,
1007
- apiKey,
1008
- outputPath: options.outputPath ?? "checkly-report.zip",
1009
- jsonReportPath: options.jsonReportPath ?? "test-results/playwright-test-report.json",
1010
- testResultsDir: options.testResultsDir ?? "test-results",
1011
- dryRun: options.dryRun ?? false,
1012
- sessionName: options.sessionName,
1013
- verbose
1878
+ createExtensionLogger(extensionName) {
1879
+ return (message, data) => {
1880
+ this.log(`[${extensionName}] ${message}`, data);
1014
1881
  };
1015
- this.assetCollector = new AssetCollector(this.options.testResultsDir);
1016
- this.zipper = new Zipper({
1017
- outputPath: this.options.outputPath
1882
+ }
1883
+ onBegin(config, suite) {
1884
+ this.config = config;
1885
+ this.suite = suite;
1886
+ this.startTime = /* @__PURE__ */ new Date();
1887
+ this.tests.clear();
1888
+ this.globalErrors = [];
1889
+ this.expectedCount = 0;
1890
+ this.unexpectedCount = 0;
1891
+ this.flakyCount = 0;
1892
+ this.skippedCount = 0;
1893
+ this._report = null;
1894
+ this.reconstructedProjects.clear();
1895
+ this.reconstructProjectsIfNeeded();
1896
+ const testCount = this.countTests(suite);
1897
+ const projectNames = config.projects.length > 0 ? config.projects.map((p) => p.name).join(", ") : Array.from(this.reconstructedProjects.keys()).join(", ") || "default";
1898
+ this.log(`\u{1F3AC} Starting test run`, { tests: testCount, projects: projectNames, workers: config.workers });
1899
+ for (const ext of this.extensions) {
1900
+ ext.onBegin?.({ config, suite, log: this.createExtensionLogger(ext.name) });
1901
+ }
1902
+ }
1903
+ onTestBegin(test, result) {
1904
+ for (const ext of this.extensions) {
1905
+ ext.onTestBegin?.({ test, result, log: this.createExtensionLogger(ext.name) });
1906
+ }
1907
+ }
1908
+ onStepBegin(test, result, step) {
1909
+ for (const ext of this.extensions) {
1910
+ ext.onStepBegin?.({ test, result, step, log: this.createExtensionLogger(ext.name) });
1911
+ }
1912
+ }
1913
+ onStepEnd(test, result, step) {
1914
+ for (const ext of this.extensions) {
1915
+ ext.onStepEnd?.({ test, result, step, log: this.createExtensionLogger(ext.name) });
1916
+ }
1917
+ }
1918
+ onTestEnd(test, result) {
1919
+ const testId = test.id;
1920
+ let testData = this.tests.get(testId);
1921
+ if (!testData) {
1922
+ testData = { testCase: test, results: [] };
1923
+ this.tests.set(testId, testData);
1924
+ }
1925
+ testData.results.push(result);
1926
+ for (const ext of this.extensions) {
1927
+ ext.onTestEnd?.({ test, result, log: this.createExtensionLogger(ext.name) });
1928
+ }
1929
+ }
1930
+ onStdOut(chunk, test, result) {
1931
+ for (const ext of this.extensions) {
1932
+ ext.onStdOut?.({ chunk, test, result, log: this.createExtensionLogger(ext.name) });
1933
+ }
1934
+ }
1935
+ onStdErr(chunk, test, result) {
1936
+ for (const ext of this.extensions) {
1937
+ ext.onStdErr?.({ chunk, test, result, log: this.createExtensionLogger(ext.name) });
1938
+ }
1939
+ }
1940
+ onError(error) {
1941
+ this.globalErrors.push(error);
1942
+ this.log(`\u{1F4A5} Global error`, { message: error.message?.slice(0, 100) });
1943
+ for (const ext of this.extensions) {
1944
+ ext.onError?.({ error, log: this.createExtensionLogger(ext.name) });
1945
+ }
1946
+ }
1947
+ async onEnd(result) {
1948
+ for (const testData of this.tests.values()) {
1949
+ switch (testData.testCase.outcome()) {
1950
+ case "expected":
1951
+ this.expectedCount++;
1952
+ break;
1953
+ case "unexpected":
1954
+ this.unexpectedCount++;
1955
+ break;
1956
+ case "flaky":
1957
+ this.flakyCount++;
1958
+ break;
1959
+ case "skipped":
1960
+ this.skippedCount++;
1961
+ break;
1962
+ }
1963
+ }
1964
+ this._report = this.buildReport(result);
1965
+ this.log(`\u{1F3C1} Test run finished`, {
1966
+ status: result.status,
1967
+ duration: `${result.duration}ms`,
1968
+ passed: this.expectedCount,
1969
+ failed: this.unexpectedCount,
1970
+ flaky: this.flakyCount,
1971
+ skipped: this.skippedCount
1018
1972
  });
1019
- if (!this.options.dryRun && this.options.apiKey && this.options.accountId) {
1020
- const client = new ChecklyClient({
1021
- apiKey: this.options.apiKey,
1022
- accountId: this.options.accountId,
1023
- baseUrl
1024
- });
1025
- this.testResults = new TestResults(client.getAxiosInstance());
1026
- }
1027
- if (verbose) {
1028
- console.log(`[Checkly Reporter DEBUG] Initialized with options:`, {
1029
- environment,
1030
- baseUrl,
1031
- hasApiKey: !!apiKey,
1032
- hasAccountId: !!accountId,
1033
- outputPath: this.options.outputPath,
1034
- jsonReportPath: this.options.jsonReportPath,
1035
- testResultsDir: this.options.testResultsDir,
1036
- dryRun: this.options.dryRun,
1037
- verbose: this.options.verbose,
1038
- hasTestResults: !!this.testResults
1973
+ const outputDir = this.options.outputDir;
1974
+ const outputFile = this.options.outputFile ?? process.env.PLAYWRIGHT_JSON_OUTPUT_NAME;
1975
+ if (outputFile) {
1976
+ console.warn("[Checkly] Warning: outputFile is deprecated, use outputDir instead");
1977
+ }
1978
+ const outputPath = outputDir ? `${outputDir}/checkly-report.json` : outputFile;
1979
+ if (outputPath) {
1980
+ const fs5 = await import("fs");
1981
+ const path4 = await import("path");
1982
+ const dir = path4.dirname(outputPath);
1983
+ if (dir && dir !== ".") fs5.mkdirSync(dir, { recursive: true });
1984
+ fs5.writeFileSync(outputPath, JSON.stringify(this._report, null, 2));
1985
+ this.log(`\u{1F4C4} Report saved`, { path: outputPath });
1986
+ }
1987
+ for (const ext of this.extensions) {
1988
+ await ext.onEnd?.({
1989
+ result,
1990
+ report: this._report,
1991
+ log: this.createExtensionLogger(ext.name),
1992
+ addSummaryLine: (line) => this.summaryLines.push(line)
1039
1993
  });
1040
1994
  }
1995
+ this.printSummary();
1041
1996
  }
1042
- /**
1043
- * Resolves the session name from options
1044
- * Supports string, callback function, or falls back to default
1045
- */
1046
- resolveSessionName(context) {
1047
- const { sessionName } = this.options;
1048
- if (typeof sessionName === "function") {
1049
- return sessionName(context);
1997
+ async onExit() {
1998
+ this.log(`\u{1F44B} Reporter exiting`);
1999
+ for (const ext of this.extensions) {
2000
+ await ext.onExit?.({ log: this.createExtensionLogger(ext.name) });
1050
2001
  }
1051
- if (typeof sessionName === "string") {
1052
- return sessionName;
1053
- }
1054
- return `Playwright Test Session: ${context.directoryName}`;
2002
+ }
2003
+ printsToStdio() {
2004
+ return false;
2005
+ }
2006
+ getReport() {
2007
+ if (this._report) return this._report;
2008
+ return this.buildReport({
2009
+ status: "passed",
2010
+ startTime: this.startTime,
2011
+ duration: Date.now() - this.startTime.getTime()
2012
+ });
2013
+ }
2014
+ buildReport(fullResult) {
2015
+ return {
2016
+ config: this.serializeConfig(),
2017
+ suites: this.serializeSuites(),
2018
+ errors: this.globalErrors.map((e) => this.serializeError(e, true)),
2019
+ stats: {
2020
+ startTime: this.startTime.toISOString(),
2021
+ duration: fullResult.duration,
2022
+ expected: this.expectedCount,
2023
+ unexpected: this.unexpectedCount,
2024
+ flaky: this.flakyCount,
2025
+ skipped: this.skippedCount
2026
+ }
2027
+ };
1055
2028
  }
1056
2029
  /**
1057
- * Checks if test result has a trace attachment and adds context-aware warning if missing
1058
- * Also captures trace file path for later console message extraction
1059
- * The warning type depends on the trace configuration and test result state
2030
+ * Reconstructs projects from the suite structure when config.projects is empty.
2031
+ * This handles the merge-reports scenario where Playwright doesn't populate
2032
+ * config.projects but still provides project information via suite.project().
1060
2033
  */
1061
- checkTraceAttachment(test, result) {
1062
- const key = `${test.id}:${result.retry}`;
1063
- const traceAttachment = result.attachments?.find(
1064
- (attachment) => attachment.name === "trace" || attachment.contentType === "application/zip"
1065
- );
1066
- if (traceAttachment?.path) {
1067
- this.tracePathsMap.set(key, traceAttachment.path);
2034
+ reconstructProjectsIfNeeded() {
2035
+ if (this.config.projects.length > 0) {
1068
2036
  return;
1069
2037
  }
1070
- const traceConfig = test.parent?.project()?.use?.trace;
1071
- const traceMode = typeof traceConfig === "object" ? traceConfig.mode : traceConfig;
1072
- const isRetry = result.retry > 0;
1073
- const testPassed = result.status === "passed";
1074
- let warningType;
1075
- let message;
1076
- switch (traceMode) {
1077
- case void 0:
1078
- return;
1079
- case "off":
1080
- warningType = "trace-off";
1081
- message = 'Traces are disabled. Set trace: "on" in playwright.config.ts to capture traces.';
1082
- break;
1083
- case "retain-on-failure":
1084
- if (testPassed) {
1085
- warningType = "trace-retained-on-failure";
1086
- message = 'No trace retained because test passed. Trace mode is "retain-on-failure" which discards traces for passing tests.';
1087
- } else {
1088
- warningType = "trace-missing";
1089
- message = 'Trace should exist but was not found. The test failed with trace: "retain-on-failure".';
1090
- }
1091
- break;
1092
- case "on-first-retry":
1093
- if (!isRetry) {
1094
- warningType = "trace-first-retry-only";
1095
- message = 'No trace for initial attempt. Trace mode is "on-first-retry" which only records traces on the first retry.';
1096
- } else if (result.retry === 1) {
1097
- warningType = "trace-missing";
1098
- message = 'Trace should exist but was not found. This is the first retry with trace: "on-first-retry".';
1099
- } else {
1100
- warningType = "trace-first-retry-only";
1101
- message = `No trace for retry #${result.retry}. Trace mode is "on-first-retry" which only records the first retry.`;
1102
- }
1103
- break;
1104
- case "on-all-retries":
1105
- if (!isRetry) {
1106
- warningType = "trace-retries-only";
1107
- message = 'No trace for initial attempt. Trace mode is "on-all-retries" which only records traces on retries.';
1108
- } else {
1109
- warningType = "trace-missing";
1110
- message = `Trace should exist but was not found. This is retry #${result.retry} with trace: "on-all-retries".`;
1111
- }
1112
- break;
1113
- case "retain-on-first-failure":
1114
- if (testPassed) {
1115
- warningType = "trace-retained-on-first-failure";
1116
- message = 'No trace retained because test passed. Trace mode is "retain-on-first-failure" which discards traces for passing tests.';
1117
- } else if (isRetry) {
1118
- warningType = "trace-retained-on-first-failure";
1119
- message = 'No trace for retries. Trace mode is "retain-on-first-failure" which only records the first run.';
1120
- } else {
1121
- warningType = "trace-missing";
1122
- message = 'Trace should exist but was not found. The test failed on first run with trace: "retain-on-first-failure".';
2038
+ this.log("\u{1F504} Detected merge-reports scenario (empty config.projects), reconstructing projects");
2039
+ for (const projectSuite of this.suite.suites) {
2040
+ const project = projectSuite.project();
2041
+ if (project) {
2042
+ const name = project.name || "default";
2043
+ if (!this.reconstructedProjects.has(name)) {
2044
+ this.reconstructedProjects.set(name, {
2045
+ id: name,
2046
+ name,
2047
+ testDir: project.testDir,
2048
+ outputDir: project.outputDir,
2049
+ timeout: project.timeout,
2050
+ retries: project.retries,
2051
+ repeatEach: project.repeatEach,
2052
+ metadata: project.metadata ?? {},
2053
+ testMatch: Array.isArray(project.testMatch) ? project.testMatch.map(String) : [String(project.testMatch)],
2054
+ testIgnore: Array.isArray(project.testIgnore) ? project.testIgnore.map(String) : [String(project.testIgnore)]
2055
+ });
1123
2056
  }
1124
- break;
1125
- case "on":
1126
- warningType = "trace-missing";
1127
- message = 'Trace should exist but was not found. Trace mode is "on" which should always record traces.';
1128
- break;
1129
- default:
1130
- warningType = "trace-missing";
1131
- message = `No trace found. Trace mode "${traceMode}" may not be generating traces for this result.`;
2057
+ }
1132
2058
  }
1133
- const warnings = this.warningsMap.get(key) || [];
1134
- warnings.push({ type: warningType, message });
1135
- this.warningsMap.set(key, warnings);
2059
+ if (this.reconstructedProjects.size === 0) {
2060
+ this.collectProjectsFromTests(this.suite);
2061
+ }
2062
+ this.log("\u{1F504} Reconstructed projects", {
2063
+ count: this.reconstructedProjects.size,
2064
+ names: Array.from(this.reconstructedProjects.keys())
2065
+ });
1136
2066
  }
1137
2067
  /**
1138
- * Called once before running tests
1139
- * Creates test session in Checkly if credentials provided
2068
+ * Recursively collects project information from test cases in the suite.
2069
+ * Fallback for when suite.project() doesn't provide project info.
1140
2070
  */
1141
- onBegin(config, suite) {
1142
- this.startTime = /* @__PURE__ */ new Date();
1143
- if (!this.testResults) {
1144
- return;
1145
- }
1146
- try {
1147
- const directoryName = getDirectoryName();
1148
- const sessionName = this.resolveSessionName({ directoryName, config, suite });
1149
- const testResults = [{ name: directoryName }];
1150
- const repoUrl = process.env.GITHUB_REPOSITORY ? `https://github.com/${process.env.GITHUB_REPOSITORY}` : void 0;
1151
- const repoInfo = repoUrl ? {
1152
- repoUrl,
1153
- commitId: process.env.GITHUB_SHA,
1154
- branchName: process.env.GITHUB_REF_NAME,
1155
- commitOwner: process.env.GITHUB_ACTOR,
1156
- commitMessage: process.env.GITHUB_EVENT_NAME
1157
- } : void 0;
1158
- this.testResults.createTestSession({
1159
- name: sessionName,
1160
- environment: process.env.NODE_ENV || "test",
1161
- repoInfo,
1162
- startedAt: this.startTime.getTime(),
1163
- // Required timestamp in milliseconds
1164
- testResults,
1165
- provider: "PW_REPORTER"
1166
- }).then((response) => {
1167
- this.testSession = response;
1168
- }).catch((error) => {
1169
- console.error("[Checkly Reporter] Failed to create test session:", error.message);
1170
- });
1171
- } catch (error) {
1172
- console.error("[Checkly Reporter] Error in onBegin:", error);
2071
+ collectProjectsFromTests(suite) {
2072
+ for (const test of suite.allTests()) {
2073
+ const project = test.parent.project();
2074
+ const projectName = project?.name || "default";
2075
+ if (!this.reconstructedProjects.has(projectName)) {
2076
+ if (project) {
2077
+ this.reconstructedProjects.set(projectName, {
2078
+ id: projectName,
2079
+ name: projectName,
2080
+ testDir: project.testDir,
2081
+ outputDir: project.outputDir,
2082
+ timeout: project.timeout,
2083
+ retries: project.retries,
2084
+ repeatEach: project.repeatEach,
2085
+ metadata: project.metadata ?? {},
2086
+ testMatch: Array.isArray(project.testMatch) ? project.testMatch.map(String) : [String(project.testMatch)],
2087
+ testIgnore: Array.isArray(project.testIgnore) ? project.testIgnore.map(String) : [String(project.testIgnore)]
2088
+ });
2089
+ } else {
2090
+ this.reconstructedProjects.set(projectName, {
2091
+ id: projectName,
2092
+ name: projectName,
2093
+ testDir: "",
2094
+ outputDir: "",
2095
+ timeout: 0,
2096
+ retries: 0,
2097
+ repeatEach: 1,
2098
+ metadata: {},
2099
+ testMatch: [],
2100
+ testIgnore: []
2101
+ });
2102
+ }
2103
+ }
1173
2104
  }
1174
2105
  }
2106
+ serializeConfig() {
2107
+ const c = this.config;
2108
+ const projects = c.projects.length > 0 ? c.projects.map((p) => this.serializeProject(p)) : Array.from(this.reconstructedProjects.values());
2109
+ return {
2110
+ rootDir: c.rootDir,
2111
+ configFile: c.configFile ?? void 0,
2112
+ version: c.version,
2113
+ workers: c.workers,
2114
+ fullyParallel: c.fullyParallel,
2115
+ forbidOnly: c.forbidOnly,
2116
+ globalTimeout: c.globalTimeout,
2117
+ maxFailures: c.maxFailures,
2118
+ metadata: c.metadata ?? {},
2119
+ projects,
2120
+ shard: c.shard ?? null,
2121
+ tags: c.tags ?? [],
2122
+ updateSourceMethod: c.updateSourceMethod,
2123
+ preserveOutput: c.preserveOutput,
2124
+ quiet: c.quiet,
2125
+ reportSlowTests: c.reportSlowTests ?? null,
2126
+ webServer: c.webServer ?? null,
2127
+ globalSetup: c.globalSetup ?? null,
2128
+ globalTeardown: c.globalTeardown ?? null,
2129
+ grep: c.grep,
2130
+ grepInvert: c.grepInvert,
2131
+ reporter: c.reporter,
2132
+ updateSnapshots: c.updateSnapshots
2133
+ };
2134
+ }
2135
+ serializeProject(p) {
2136
+ return {
2137
+ id: p.name,
2138
+ name: p.name,
2139
+ testDir: p.testDir,
2140
+ outputDir: p.outputDir,
2141
+ timeout: p.timeout,
2142
+ retries: p.retries,
2143
+ repeatEach: p.repeatEach,
2144
+ metadata: p.metadata ?? {},
2145
+ testMatch: Array.isArray(p.testMatch) ? p.testMatch.map(String) : [String(p.testMatch)],
2146
+ testIgnore: Array.isArray(p.testIgnore) ? p.testIgnore.map(String) : [String(p.testIgnore)]
2147
+ };
2148
+ }
1175
2149
  /**
1176
- * Called for each test when it completes
1177
- * Captures steps and warnings, tracks test results for final status calculation
2150
+ * Serialize suites following the same order as native Playwright JSON reporter.
2151
+ * Iterates through suite tree structure (discovery order) rather than test completion order.
1178
2152
  */
1179
- onTestEnd(test, result) {
1180
- try {
1181
- this.checkTraceAttachment(test, result);
1182
- const stepsKey = `${test.id}:${result.retry}`;
1183
- if (result.steps && result.steps.length > 0) {
1184
- this.stepsMap.set(stepsKey, result.steps.map(convertStepToJSON));
1185
- }
1186
- const outcome = test.outcome();
1187
- const testIsComplete = result.retry === test.retries || outcome !== "unexpected";
1188
- if (!testIsComplete) {
1189
- return;
1190
- }
1191
- const isFlaky = outcome === "flaky";
1192
- if (isFlaky) {
1193
- this.testCounts.flaky++;
1194
- this.testCounts.passed++;
1195
- } else {
1196
- if (result.status === "passed") {
1197
- this.testCounts.passed++;
1198
- } else if (result.status === "failed" || result.status === "timedOut") {
1199
- this.testCounts.failed++;
2153
+ serializeSuites() {
2154
+ const fileSuites = /* @__PURE__ */ new Map();
2155
+ for (const projectSuite of this.suite.suites) {
2156
+ for (const fileSuite of projectSuite.suites) {
2157
+ const file = fileSuite.location?.file;
2158
+ if (!file) continue;
2159
+ const fileName = this.getFileName(file);
2160
+ const serialized = this.serializeSuite(fileSuite, fileName);
2161
+ if (!serialized) continue;
2162
+ const existing = fileSuites.get(file);
2163
+ if (existing) {
2164
+ this.mergeSuites(existing, serialized);
2165
+ } else {
2166
+ fileSuites.set(file, serialized);
1200
2167
  }
1201
2168
  }
1202
- } catch (error) {
1203
- console.error("[Checkly Reporter] Error in onTestEnd:", error);
1204
2169
  }
2170
+ return Array.from(fileSuites.values());
1205
2171
  }
1206
2172
  /**
1207
- * Called after all tests have completed
1208
- * This is where we create the ZIP archive and upload results
2173
+ * Recursively serialize a suite and its children.
2174
+ * Returns null if the suite has no tests.
1209
2175
  */
1210
- async onEnd() {
1211
- this.log("onEnd started", {
1212
- testCounts: this.testCounts,
1213
- stepsMapSize: this.stepsMap.size,
1214
- warningsMapSize: this.warningsMap.size,
1215
- hasTestSession: !!this.testSession,
1216
- testSessionId: this.testSession?.testSessionId
1217
- });
1218
- try {
1219
- const jsonReportPath = this.options.jsonReportPath;
1220
- if (!fs4.existsSync(jsonReportPath)) {
1221
- console.error(`[Checkly Reporter] ERROR: JSON report not found at: ${jsonReportPath}`);
1222
- console.error("[Checkly Reporter] Make sure to configure the json reporter before the checkly reporter:");
1223
- console.error(
1224
- " reporter: [\n ['json', { outputFile: 'test-results/playwright-test-report.json' }],\n ['@checkly/playwright-reporter']\n ]"
1225
- );
1226
- return;
1227
- }
1228
- this.log("Reading JSON report", { path: jsonReportPath });
1229
- const reportContent = fs4.readFileSync(jsonReportPath, "utf-8");
1230
- const report = JSON.parse(reportContent);
1231
- this.log("JSON report parsed", {
1232
- configVersion: report.config?.version,
1233
- projectsCount: report.config?.projects?.length ?? 0,
1234
- suitesCount: report.suites?.length ?? 0,
1235
- rootDir: report.config?.rootDir
1236
- });
1237
- await this.extractDataFromTraces();
1238
- this.injectDataIntoReport(report);
1239
- this.log("Data injected into report", {
1240
- projectsCountAfterReconstruction: report.config?.projects?.length ?? 0
1241
- });
1242
- fs4.writeFileSync(jsonReportPath, JSON.stringify(report, null, 2), "utf-8");
1243
- this.log("Enriched report written to disk");
1244
- this.log("Collecting assets", { testResultsDir: this.options.testResultsDir });
1245
- const assets = await this.assetCollector.collectAssets(report);
1246
- this.log("Assets collected", {
1247
- count: assets.length,
1248
- assets: assets.map((a) => ({ source: a.sourcePath, archive: a.archivePath, type: a.type }))
1249
- });
1250
- this.log("Creating ZIP archive", { outputPath: this.options.outputPath });
1251
- const result = await this.zipper.createZip(jsonReportPath, assets);
1252
- this.log("ZIP created", {
1253
- zipPath: result.zipPath,
1254
- zipSize: result.size,
1255
- entriesCount: result.entries.length,
1256
- entries: result.entries.map((e) => ({ name: e.name, start: e.start, end: e.end }))
1257
- });
1258
- if (this.testResults && this.testSession) {
1259
- this.log("Uploading results", { testSessionId: this.testSession.testSessionId });
1260
- await this.uploadResults(report, result.zipPath, result.entries);
1261
- if (!this.options.dryRun) {
1262
- try {
1263
- fs4.unlinkSync(result.zipPath);
1264
- } catch (cleanupError) {
1265
- console.warn(`[Checkly Reporter] Warning: Could not delete ZIP file: ${cleanupError}`);
1266
- }
1267
- }
1268
- }
1269
- if (this.testResults && this.testSession?.link) {
1270
- this.printSummary(report, this.testSession);
2176
+ serializeSuite(suite, fileName) {
2177
+ const allTests = suite.allTests();
2178
+ if (allTests.length === 0) return null;
2179
+ const childSuites = [];
2180
+ for (const child of suite.suites) {
2181
+ const serialized = this.serializeSuite(child, fileName);
2182
+ if (serialized) childSuites.push(serialized);
2183
+ }
2184
+ const specs = [];
2185
+ for (const test of suite.tests) {
2186
+ const testData = this.tests.get(test.id);
2187
+ if (testData) {
2188
+ specs.push(this.serializeSpec(testData));
1271
2189
  }
1272
- } catch (error) {
1273
- console.error("[Checkly Reporter] ERROR creating report:", error);
1274
2190
  }
1275
- }
1276
- printSummary(report, testSession) {
1277
- const rule = pluralRules.select(report.config.projects.length);
1278
- console.log("\n======================================================\n");
1279
- console.log(`\u{1F99D} Checkly reporter: ${pkgVersion}`);
1280
- console.log(`\u{1F3AD} Playwright: ${report.config.version}`);
1281
- console.log(`\u{1F4D4} ${projectForms[rule]}: ${report.config.projects.map(({ name }) => name).join(",")}`);
1282
- console.log(`\u{1F517} Test session URL: ${testSession.link}`);
1283
- console.log("\n======================================================");
2191
+ return {
2192
+ title: suite.title || fileName,
2193
+ file: fileName,
2194
+ line: suite.location?.line ?? 0,
2195
+ column: suite.location?.column ?? 0,
2196
+ specs,
2197
+ suites: childSuites.length ? childSuites : void 0
2198
+ };
1284
2199
  }
1285
2200
  /**
1286
- * Extracts console messages and network requests from all captured traces
1287
- * Called before injecting data into the report
2201
+ * Merge tests from 'from' suite into 'to' suite (for multi-project scenarios).
2202
+ * Matches native Playwright JSON reporter behavior - specs are NOT merged,
2203
+ * each project gets its own spec even for the same test.
1288
2204
  */
1289
- async extractDataFromTraces() {
1290
- const extractionPromises = [];
1291
- for (const [key, tracePath] of this.tracePathsMap.entries()) {
1292
- extractionPromises.push(
1293
- (async () => {
1294
- const reader = new TraceReader(tracePath);
1295
- if (!await reader.open()) return;
1296
- const messages = await reader.extractEvents('"type":"console"', toConsoleMessage);
1297
- if (messages.length > 0) {
1298
- this.consoleMessagesMap.set(key, messages);
1299
- }
1300
- const networkRequests = await reader.extractEvents('"type":"resource-snapshot"', toNetworkRequest);
1301
- if (networkRequests.length > 0) {
1302
- this.networkRequestsMap.set(key, networkRequests);
1303
- }
1304
- })().catch((error) => {
1305
- console.error(`[Checkly Reporter] Failed to extract data from trace: ${error}`);
1306
- })
2205
+ mergeSuites(to, from) {
2206
+ for (const fromSuite of from.suites || []) {
2207
+ const toSuite = to.suites?.find(
2208
+ (s) => s.title === fromSuite.title && s.line === fromSuite.line && s.column === fromSuite.column
1307
2209
  );
2210
+ if (toSuite) {
2211
+ this.mergeSuites(toSuite, fromSuite);
2212
+ } else {
2213
+ to.suites = to.suites ?? [];
2214
+ to.suites.push(fromSuite);
2215
+ }
1308
2216
  }
1309
- await Promise.all(extractionPromises);
2217
+ to.specs.push(...from.specs);
2218
+ }
2219
+ serializeSpec(data) {
2220
+ const { testCase } = data;
2221
+ const outcome = testCase.outcome();
2222
+ return {
2223
+ id: testCase.id,
2224
+ title: testCase.title,
2225
+ file: this.getFileName(testCase.location.file),
2226
+ line: testCase.location.line,
2227
+ column: testCase.location.column,
2228
+ tags: this.extractTags(testCase),
2229
+ ok: outcome === "expected" || outcome === "flaky" || outcome === "skipped",
2230
+ tests: [this.serializeTest(data)]
2231
+ };
1310
2232
  }
1311
2233
  /**
1312
- * Injects captured steps, warnings, console messages, and network requests into the JSON report
1313
- * Traverses the report structure and matches by test ID + retry
2234
+ * Extract tags from test case - uses testCase.tags (1.42+) or extracts @tags from title
2235
+ * Tags are returned without @ prefix to match native Playwright JSON reporter
1314
2236
  */
1315
- injectDataIntoReport(report) {
1316
- const processSuite = (suite) => {
1317
- for (const spec of suite.specs) {
1318
- for (const test of spec.tests) {
1319
- for (const result of test.results) {
1320
- const key = `${spec.id}:${result.retry}`;
1321
- const steps = this.stepsMap.get(key);
1322
- if (steps) {
1323
- result.steps = steps;
1324
- }
1325
- const warnings = this.warningsMap.get(key);
1326
- const consoleMessages = this.consoleMessagesMap.get(key);
1327
- const networkRequests = this.networkRequestsMap.get(key);
1328
- const hasData = warnings && warnings.length > 0 || consoleMessages && consoleMessages.length > 0 || networkRequests && networkRequests.length > 0;
1329
- if (hasData) {
1330
- result._checkly = {
1331
- ...warnings && warnings.length > 0 ? { warnings } : {},
1332
- ...consoleMessages && consoleMessages.length > 0 ? { console: consoleMessages } : {},
1333
- ...networkRequests && networkRequests.length > 0 ? { network: networkRequests } : {}
1334
- };
1335
- }
1336
- }
1337
- }
1338
- }
1339
- if (suite.suites) {
1340
- for (const nestedSuite of suite.suites) {
1341
- processSuite(nestedSuite);
1342
- }
1343
- }
1344
- };
1345
- for (const suite of report.suites) {
1346
- processSuite(suite);
2237
+ extractTags(testCase) {
2238
+ const runtimeTags = testCase.tags;
2239
+ if (runtimeTags && runtimeTags.length > 0) {
2240
+ return runtimeTags.map((t) => t.replace(/^@/, ""));
1347
2241
  }
1348
- this.reconstructProjectsFromTests(report);
2242
+ const titleTags = testCase.title.match(/@[\w-]+/g);
2243
+ return titleTags?.map((t) => t.replace(/^@/, "")) ?? [];
2244
+ }
2245
+ serializeTest(data) {
2246
+ const { testCase, results } = data;
2247
+ const projectName = this.getProjectName(testCase);
2248
+ return {
2249
+ projectId: projectName,
2250
+ projectName,
2251
+ timeout: testCase.timeout,
2252
+ expectedStatus: testCase.expectedStatus,
2253
+ annotations: this.serializeAnnotations(testCase.annotations),
2254
+ results: results.map((r) => this.serializeTestResult(r)),
2255
+ status: this.mapOutcome(testCase.outcome())
2256
+ };
1349
2257
  }
1350
2258
  /**
1351
- * Reconstructs config.projects and test.projectId from test data
1352
- * This is necessary for blob merge scenarios where Playwright's JSON reporter
1353
- * doesn't populate projects array or projectId fields
2259
+ * Get the project name for a test case, handling merge-reports scenarios
2260
+ * where project() may not be available in the expected way.
1354
2261
  */
1355
- reconstructProjectsFromTests(report) {
1356
- const projectNames = /* @__PURE__ */ new Set();
1357
- let testsWithMissingProjectId = 0;
1358
- const configAny = report.config;
1359
- const originalProjectsCount = configAny.projects?.length ?? 0;
1360
- this.log("reconstructProjectsFromTests started", {
1361
- originalProjectsCount,
1362
- suitesCount: report.suites?.length ?? 0
1363
- });
1364
- const collectProjectNames = (suite) => {
1365
- for (const spec of suite.specs) {
1366
- for (const test of spec.tests) {
1367
- const testAny = test;
1368
- if (testAny.projectName) {
1369
- projectNames.add(testAny.projectName);
1370
- }
1371
- if (testAny.projectName && !testAny.projectId) {
1372
- testAny.projectId = testAny.projectName;
1373
- testsWithMissingProjectId++;
1374
- }
1375
- }
1376
- }
1377
- if (suite.suites) {
1378
- for (const nestedSuite of suite.suites) {
1379
- collectProjectNames(nestedSuite);
1380
- }
2262
+ getProjectName(testCase) {
2263
+ const project = testCase.parent.project();
2264
+ if (project && typeof project.name === "string") {
2265
+ return project.name;
2266
+ }
2267
+ let current = testCase.parent;
2268
+ while (current) {
2269
+ const suiteProject = current.project();
2270
+ if (suiteProject && typeof suiteProject.name === "string") {
2271
+ return suiteProject.name;
1381
2272
  }
2273
+ current = current.parent;
2274
+ }
2275
+ return "default";
2276
+ }
2277
+ serializeTestResult(r) {
2278
+ return {
2279
+ workerIndex: r.workerIndex,
2280
+ parallelIndex: r.parallelIndex ?? 0,
2281
+ status: r.status,
2282
+ duration: r.duration,
2283
+ startTime: r.startTime.toISOString(),
2284
+ retry: r.retry,
2285
+ errors: r.errors.map((e) => this.serializeError(e, true)),
2286
+ error: r.error ? this.serializeError(r.error, false) : void 0,
2287
+ errorLocation: r.error?.location,
2288
+ stdout: r.stdout.map(
2289
+ (s) => typeof s === "string" ? { text: s } : Buffer.isBuffer(s) ? { buffer: s.toString("base64") } : s
2290
+ ),
2291
+ stderr: r.stderr.map(
2292
+ (s) => typeof s === "string" ? { text: s } : Buffer.isBuffer(s) ? { buffer: s.toString("base64") } : s
2293
+ ),
2294
+ attachments: r.attachments.map((a) => ({
2295
+ name: a.name,
2296
+ contentType: a.contentType,
2297
+ path: a.path,
2298
+ body: a.body ? a.body.toString("base64") : void 0
2299
+ })),
2300
+ steps: r.steps.map((s) => this.serializeStep(s)),
2301
+ annotations: this.serializeAnnotations(
2302
+ r.annotations ?? []
2303
+ )
1382
2304
  };
1383
- for (const suite of report.suites) {
1384
- collectProjectNames(suite);
2305
+ }
2306
+ serializeStep(step) {
2307
+ return {
2308
+ title: step.title,
2309
+ duration: step.duration,
2310
+ error: step.error ? this.serializeError(step.error, false) : void 0,
2311
+ steps: step.steps?.map((s) => this.serializeStep(s))
2312
+ };
2313
+ }
2314
+ serializeError(error, includeContext) {
2315
+ if (error.value !== void 0) {
2316
+ return includeContext ? { message: error.value } : { value: error.value };
1385
2317
  }
1386
- this.log("Project names collected from tests", {
1387
- uniqueProjectNames: Array.from(projectNames),
1388
- testsWithMissingProjectId
1389
- });
1390
- if ((!configAny.projects || configAny.projects.length === 0) && projectNames.size > 0) {
1391
- configAny.projects = Array.from(projectNames).map((name) => ({
1392
- id: name,
1393
- name
1394
- }));
1395
- this.log("Reconstructed config.projects", {
1396
- reconstructedProjectsCount: configAny.projects.length,
1397
- projects: configAny.projects
1398
- });
1399
- } else {
1400
- this.log("No project reconstruction needed", {
1401
- reason: configAny.projects?.length > 0 ? "projects already present" : "no project names found in tests"
1402
- });
2318
+ let msg = error.message ?? "Unknown error";
2319
+ if (includeContext && !/^(\w+Error|Error):/.test(msg)) {
2320
+ msg = "Error: " + msg;
1403
2321
  }
2322
+ if (includeContext) {
2323
+ if (error.snippet) msg += "\n\n" + error.snippet;
2324
+ if (error.stack) {
2325
+ const frames = error.stack.split("\n").filter((l) => l.trim().startsWith("at "));
2326
+ if (frames.length) msg += "\n" + frames.join("\n");
2327
+ } else if (error.location) {
2328
+ msg += `
2329
+ at ${error.location.file}:${error.location.line}:${error.location.column}`;
2330
+ }
2331
+ }
2332
+ const result = { message: msg, stack: error.stack, location: error.location, snippet: error.snippet };
2333
+ if (error.matcherResult !== void 0) {
2334
+ result.matcherResult = error.matcherResult;
2335
+ }
2336
+ return result;
1404
2337
  }
1405
- /**
1406
- * Uploads test results to Checkly API
1407
- */
1408
- async uploadResults(_report, zipPath, entries) {
1409
- this.log("uploadResults started", {
1410
- zipPath,
1411
- entriesCount: entries.length,
1412
- hasTestResults: !!this.testResults,
1413
- hasTestSession: !!this.testSession
1414
- });
1415
- if (!this.testResults || !this.testSession) {
1416
- this.log("uploadResults skipped - missing testResults or testSession");
1417
- return;
2338
+ serializeAnnotations(annotations) {
2339
+ return annotations.map((a) => ({ type: a.type, description: a.description, location: a.location }));
2340
+ }
2341
+ countTests(suite) {
2342
+ let count = suite.tests?.length ?? 0;
2343
+ for (const child of suite.suites ?? []) {
2344
+ count += this.countTests(child);
1418
2345
  }
1419
- try {
1420
- const { failed: failedCount, flaky: flakyCount } = this.testCounts;
1421
- const overallStatus = failedCount > 0 ? "FAILED" : "PASSED";
1422
- const isDegraded = failedCount === 0 && flakyCount > 0;
1423
- const endTime = /* @__PURE__ */ new Date();
1424
- const responseTime = this.startTime ? Math.max(0, endTime.getTime() - this.startTime.getTime()) : 0;
1425
- const zipSizeBytes = (await fs4.promises.stat(zipPath)).size;
1426
- this.log("Upload metadata calculated", {
1427
- testCounts: this.testCounts,
1428
- overallStatus,
1429
- isDegraded,
1430
- responseTime,
1431
- zipSizeBytes,
1432
- testSessionId: this.testSession.testSessionId,
1433
- testResultsCount: this.testSession.testResults.length
1434
- });
1435
- if (this.testSession.testResults.length > 0) {
1436
- const firstResult = this.testSession.testResults[0];
1437
- this.log("Using first test result for upload", {
1438
- testResultId: firstResult.testResultId
1439
- });
1440
- let assetId;
1441
- if (zipSizeBytes > 0) {
1442
- this.log("Starting S3 asset upload", { zipSizeBytes });
1443
- try {
1444
- const assets = fs4.createReadStream(zipPath);
1445
- const uploadResponse = await this.testResults.uploadTestResultAsset(
1446
- this.testSession.testSessionId,
1447
- firstResult.testResultId,
1448
- assets
1449
- );
1450
- assetId = uploadResponse.assetId;
1451
- this.log("S3 asset upload completed", { assetId });
1452
- } catch (error) {
1453
- const errorMessage = error instanceof Error ? error.message : String(error);
1454
- console.error("[Checkly Reporter] Asset upload failed:", errorMessage);
1455
- this.log("S3 asset upload failed", { error: errorMessage });
1456
- }
1457
- } else {
1458
- this.log("Skipping S3 upload - ZIP is empty");
1459
- }
1460
- const updatePayload = {
1461
- status: overallStatus,
1462
- assetEntries: assetId ? entries : void 0,
1463
- isDegraded,
1464
- startedAt: this.startTime?.toISOString(),
1465
- stoppedAt: endTime.toISOString(),
1466
- responseTime,
1467
- metadata: {
1468
- usageData: {
1469
- s3PostTotalBytes: zipSizeBytes
1470
- }
1471
- }
1472
- };
1473
- this.log("Updating test result", {
1474
- testResultId: firstResult.testResultId,
1475
- payload: updatePayload,
1476
- assetEntriesCount: assetId ? entries.length : 0
1477
- });
1478
- await this.testResults.updateTestResult(this.testSession.testSessionId, firstResult.testResultId, updatePayload);
1479
- this.log("Test result updated successfully");
1480
- } else {
1481
- this.log("No test results in session to update");
1482
- }
1483
- } catch (error) {
1484
- const errorMessage = error instanceof Error ? error.message : String(error);
1485
- console.error("[Checkly Reporter] Failed to upload results:", errorMessage);
1486
- this.log("uploadResults failed", { error: errorMessage });
2346
+ return count;
2347
+ }
2348
+ getFileName(filePath) {
2349
+ return filePath.split("/").pop() ?? filePath;
2350
+ }
2351
+ mapOutcome(outcome) {
2352
+ if (outcome === "expected" || outcome === "unexpected" || outcome === "flaky" || outcome === "skipped") {
2353
+ return outcome;
1487
2354
  }
2355
+ return "unexpected";
2356
+ }
2357
+ printSummary() {
2358
+ const pkgVersion = getPackageVersion();
2359
+ const playwrightVersion = this.config.version;
2360
+ const projectNames = this.config.projects.length > 0 ? this.config.projects.map((p) => p.name).join(", ") : Array.from(this.reconstructedProjects.keys()).join(", ") || "default";
2361
+ const projectCount = this.config.projects.length > 0 ? this.config.projects.length : this.reconstructedProjects.size || 1;
2362
+ const rule = pluralRules.select(projectCount);
2363
+ console.log("\n======================================================\n");
2364
+ console.log(`\u{1F99D} Checkly reporter: ${pkgVersion}`);
2365
+ console.log(`\u{1F3AD} Playwright: ${playwrightVersion}`);
2366
+ console.log(`\u{1F4D4} ${projectForms[rule]}: ${projectNames}`);
2367
+ for (const line of this.summaryLines) {
2368
+ console.log(line);
2369
+ }
2370
+ console.log("\n======================================================");
1488
2371
  }
1489
- /**
1490
- * Called when a global error occurs
1491
- */
1492
- onError(error) {
1493
- console.error("[Checkly Reporter] Global error:", error);
2372
+ };
2373
+
2374
+ // src/types/output-contract.ts
2375
+ function isValidJSONReport(obj) {
2376
+ if (typeof obj !== "object" || obj === null) return false;
2377
+ const report = obj;
2378
+ return typeof report.config === "object" && report.config !== null && Array.isArray(report.suites) && Array.isArray(report.errors) && typeof report.stats === "object" && report.stats !== null;
2379
+ }
2380
+ function isValidStats(obj) {
2381
+ if (typeof obj !== "object" || obj === null) return false;
2382
+ const stats = obj;
2383
+ 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";
2384
+ }
2385
+ function isValidTestResult(obj) {
2386
+ if (typeof obj !== "object" || obj === null) return false;
2387
+ const result = obj;
2388
+ 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);
2389
+ }
2390
+
2391
+ // src/index.ts
2392
+ var ChecklyReporter = class extends BaseReporter {
2393
+ constructor(options = {}) {
2394
+ super(options);
2395
+ this.use(checklyUpload(options));
1494
2396
  }
1495
2397
  };
2398
+ var index_default = ChecklyReporter;
2399
+ function createChecklyReporter(options = {}) {
2400
+ return ["@checkly/playwright-reporter", options];
2401
+ }
1496
2402
  export {
1497
- AssetCollector,
2403
+ BaseReporter,
1498
2404
  ChecklyReporter,
1499
- Zipper,
1500
- ChecklyReporter as default
2405
+ checklyUpload,
2406
+ createChecklyReporter,
2407
+ index_default as default,
2408
+ isValidJSONReport,
2409
+ isValidStats,
2410
+ isValidTestResult
1501
2411
  };