@reportforge/playwright-pdf 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1221,13 +1221,13 @@ var require_ast = __commonJS({
1221
1221
  helperExpression: function helperExpression(node) {
1222
1222
  return node.type === "SubExpression" || (node.type === "MustacheStatement" || node.type === "BlockStatement") && !!(node.params && node.params.length || node.hash);
1223
1223
  },
1224
- scopedId: function scopedId(path15) {
1225
- return /^\.|this\b/.test(path15.original);
1224
+ scopedId: function scopedId(path16) {
1225
+ return /^\.|this\b/.test(path16.original);
1226
1226
  },
1227
1227
  // an ID is simple if it only has one part, and that part is not
1228
1228
  // `..` or `this`.
1229
- simpleId: function simpleId(path15) {
1230
- return path15.parts.length === 1 && !AST.helpers.scopedId(path15) && !path15.depth;
1229
+ simpleId: function simpleId(path16) {
1230
+ return path16.parts.length === 1 && !AST.helpers.scopedId(path16) && !path16.depth;
1231
1231
  }
1232
1232
  }
1233
1233
  };
@@ -2301,12 +2301,12 @@ var require_helpers2 = __commonJS({
2301
2301
  loc
2302
2302
  };
2303
2303
  }
2304
- function prepareMustache(path15, params, hash, open, strip, locInfo) {
2304
+ function prepareMustache(path16, params, hash, open, strip, locInfo) {
2305
2305
  var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
2306
2306
  var decorator = /\*/.test(open);
2307
2307
  return {
2308
2308
  type: decorator ? "Decorator" : "MustacheStatement",
2309
- path: path15,
2309
+ path: path16,
2310
2310
  params,
2311
2311
  hash,
2312
2312
  escaped,
@@ -2626,9 +2626,9 @@ var require_compiler = __commonJS({
2626
2626
  },
2627
2627
  DecoratorBlock: function DecoratorBlock(decorator) {
2628
2628
  var program = decorator.program && this.compileProgram(decorator.program);
2629
- var params = this.setupFullMustacheParams(decorator, program, void 0), path15 = decorator.path;
2629
+ var params = this.setupFullMustacheParams(decorator, program, void 0), path16 = decorator.path;
2630
2630
  this.useDecorators = true;
2631
- this.opcode("registerDecorator", params.length, path15.original);
2631
+ this.opcode("registerDecorator", params.length, path16.original);
2632
2632
  },
2633
2633
  PartialStatement: function PartialStatement(partial) {
2634
2634
  this.usePartial = true;
@@ -2692,46 +2692,46 @@ var require_compiler = __commonJS({
2692
2692
  }
2693
2693
  },
2694
2694
  ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
2695
- var path15 = sexpr.path, name = path15.parts[0], isBlock = program != null || inverse != null;
2696
- this.opcode("getContext", path15.depth);
2695
+ var path16 = sexpr.path, name = path16.parts[0], isBlock = program != null || inverse != null;
2696
+ this.opcode("getContext", path16.depth);
2697
2697
  this.opcode("pushProgram", program);
2698
2698
  this.opcode("pushProgram", inverse);
2699
- path15.strict = true;
2700
- this.accept(path15);
2699
+ path16.strict = true;
2700
+ this.accept(path16);
2701
2701
  this.opcode("invokeAmbiguous", name, isBlock);
2702
2702
  },
2703
2703
  simpleSexpr: function simpleSexpr(sexpr) {
2704
- var path15 = sexpr.path;
2705
- path15.strict = true;
2706
- this.accept(path15);
2704
+ var path16 = sexpr.path;
2705
+ path16.strict = true;
2706
+ this.accept(path16);
2707
2707
  this.opcode("resolvePossibleLambda");
2708
2708
  },
2709
2709
  helperSexpr: function helperSexpr(sexpr, program, inverse) {
2710
- var params = this.setupFullMustacheParams(sexpr, program, inverse), path15 = sexpr.path, name = path15.parts[0];
2710
+ var params = this.setupFullMustacheParams(sexpr, program, inverse), path16 = sexpr.path, name = path16.parts[0];
2711
2711
  if (this.options.knownHelpers[name]) {
2712
2712
  this.opcode("invokeKnownHelper", params.length, name);
2713
2713
  } else if (this.options.knownHelpersOnly) {
2714
2714
  throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
2715
2715
  } else {
2716
- path15.strict = true;
2717
- path15.falsy = true;
2718
- this.accept(path15);
2719
- this.opcode("invokeHelper", params.length, path15.original, _ast2["default"].helpers.simpleId(path15));
2716
+ path16.strict = true;
2717
+ path16.falsy = true;
2718
+ this.accept(path16);
2719
+ this.opcode("invokeHelper", params.length, path16.original, _ast2["default"].helpers.simpleId(path16));
2720
2720
  }
2721
2721
  },
2722
- PathExpression: function PathExpression(path15) {
2723
- this.addDepth(path15.depth);
2724
- this.opcode("getContext", path15.depth);
2725
- var name = path15.parts[0], scoped = _ast2["default"].helpers.scopedId(path15), blockParamId = !path15.depth && !scoped && this.blockParamIndex(name);
2722
+ PathExpression: function PathExpression(path16) {
2723
+ this.addDepth(path16.depth);
2724
+ this.opcode("getContext", path16.depth);
2725
+ var name = path16.parts[0], scoped = _ast2["default"].helpers.scopedId(path16), blockParamId = !path16.depth && !scoped && this.blockParamIndex(name);
2726
2726
  if (blockParamId) {
2727
- this.opcode("lookupBlockParam", blockParamId, path15.parts);
2727
+ this.opcode("lookupBlockParam", blockParamId, path16.parts);
2728
2728
  } else if (!name) {
2729
2729
  this.opcode("pushContext");
2730
- } else if (path15.data) {
2730
+ } else if (path16.data) {
2731
2731
  this.options.data = true;
2732
- this.opcode("lookupData", path15.depth, path15.parts, path15.strict);
2732
+ this.opcode("lookupData", path16.depth, path16.parts, path16.strict);
2733
2733
  } else {
2734
- this.opcode("lookupOnContext", path15.parts, path15.falsy, path15.strict, scoped);
2734
+ this.opcode("lookupOnContext", path16.parts, path16.falsy, path16.strict, scoped);
2735
2735
  }
2736
2736
  },
2737
2737
  StringLiteral: function StringLiteral(string) {
@@ -3087,16 +3087,16 @@ var require_util = __commonJS({
3087
3087
  }
3088
3088
  exports2.urlGenerate = urlGenerate;
3089
3089
  function normalize(aPath) {
3090
- var path15 = aPath;
3090
+ var path16 = aPath;
3091
3091
  var url = urlParse(aPath);
3092
3092
  if (url) {
3093
3093
  if (!url.path) {
3094
3094
  return aPath;
3095
3095
  }
3096
- path15 = url.path;
3096
+ path16 = url.path;
3097
3097
  }
3098
- var isAbsolute = exports2.isAbsolute(path15);
3099
- var parts = path15.split(/\/+/);
3098
+ var isAbsolute = exports2.isAbsolute(path16);
3099
+ var parts = path16.split(/\/+/);
3100
3100
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
3101
3101
  part = parts[i];
3102
3102
  if (part === ".") {
@@ -3113,15 +3113,15 @@ var require_util = __commonJS({
3113
3113
  }
3114
3114
  }
3115
3115
  }
3116
- path15 = parts.join("/");
3117
- if (path15 === "") {
3118
- path15 = isAbsolute ? "/" : ".";
3116
+ path16 = parts.join("/");
3117
+ if (path16 === "") {
3118
+ path16 = isAbsolute ? "/" : ".";
3119
3119
  }
3120
3120
  if (url) {
3121
- url.path = path15;
3121
+ url.path = path16;
3122
3122
  return urlGenerate(url);
3123
3123
  }
3124
- return path15;
3124
+ return path16;
3125
3125
  }
3126
3126
  exports2.normalize = normalize;
3127
3127
  function join(aRoot, aPath) {
@@ -5924,8 +5924,8 @@ var require_printer = __commonJS({
5924
5924
  return this.accept(sexpr.path) + " " + params + hash;
5925
5925
  };
5926
5926
  PrintVisitor.prototype.PathExpression = function(id) {
5927
- var path15 = id.parts.join("/");
5928
- return (id.data ? "@" : "") + "PATH:" + path15;
5927
+ var path16 = id.parts.join("/");
5928
+ return (id.data ? "@" : "") + "PATH:" + path16;
5929
5929
  };
5930
5930
  PrintVisitor.prototype.StringLiteral = function(string) {
5931
5931
  return '"' + string.value + '"';
@@ -6045,7 +6045,7 @@ var require_win32 = __commonJS({
6045
6045
  "node_modules/chrome-finder/lib/win32.js"(exports2, module2) {
6046
6046
  "use strict";
6047
6047
  init_cjs_shims();
6048
- var path15 = require("path");
6048
+ var path16 = require("path");
6049
6049
  var { canAccess } = require_util2();
6050
6050
  function win32() {
6051
6051
  const installations = [];
@@ -6059,7 +6059,7 @@ var require_win32 = __commonJS({
6059
6059
  const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]];
6060
6060
  prefixes.forEach((prefix) => suffixes.forEach((suffix) => {
6061
6061
  if (prefix) {
6062
- const chromePath = path15.join(prefix, suffix);
6062
+ const chromePath = path16.join(prefix, suffix);
6063
6063
  if (canAccess(chromePath)) {
6064
6064
  installations.push(chromePath);
6065
6065
  }
@@ -6077,7 +6077,7 @@ var require_darwin = __commonJS({
6077
6077
  "use strict";
6078
6078
  init_cjs_shims();
6079
6079
  var { execSync: execSync3 } = require("child_process");
6080
- var path15 = require("path");
6080
+ var path16 = require("path");
6081
6081
  var { canAccess, newLineRegex, sort } = require_util2();
6082
6082
  function darwin() {
6083
6083
  const suffixes = ["/Contents/MacOS/Google Chrome Canary", "/Contents/MacOS/Google Chrome", "/Contents/MacOS/Chromium"];
@@ -6087,7 +6087,7 @@ var require_darwin = __commonJS({
6087
6087
  ${LSREGISTER} -dump | grep -E -i -o '/.+(google chrome( canary)?|chromium)\\.app(\\s|$)' | grep -E -v 'Caches|TimeMachine|Temporary|/Volumes|\\.Trash'
6088
6088
  `).toString().split(newLineRegex).forEach((inst) => {
6089
6089
  suffixes.forEach((suffix) => {
6090
- const execPath = path15.join(inst.trim(), suffix);
6090
+ const execPath = path16.join(inst.trim(), suffix);
6091
6091
  if (canAccess(execPath)) {
6092
6092
  installations.push(execPath);
6093
6093
  }
@@ -6116,7 +6116,7 @@ var require_linux = __commonJS({
6116
6116
  "use strict";
6117
6117
  init_cjs_shims();
6118
6118
  var { execSync: execSync3, execFileSync } = require("child_process");
6119
- var path15 = require("path");
6119
+ var path16 = require("path");
6120
6120
  var fs16 = require("fs");
6121
6121
  var { canAccess, sort, isExecutable, newLineRegex } = require_util2();
6122
6122
  function findChromeExecutablesForLinuxDesktop(folder) {
@@ -6134,7 +6134,7 @@ var require_linux = __commonJS({
6134
6134
  function linux() {
6135
6135
  let installations = [];
6136
6136
  const desktopInstallationFolders = [
6137
- path15.join(require("os").homedir(), ".local/share/applications/"),
6137
+ path16.join(require("os").homedir(), ".local/share/applications/"),
6138
6138
  "/usr/share/applications/"
6139
6139
  ];
6140
6140
  desktopInstallationFolders.forEach((folder) => {
@@ -6749,8 +6749,8 @@ function getErrorMap() {
6749
6749
  // node_modules/zod/v3/helpers/parseUtil.js
6750
6750
  init_cjs_shims();
6751
6751
  var makeIssue = (params) => {
6752
- const { data, path: path15, errorMaps, issueData } = params;
6753
- const fullPath = [...path15, ...issueData.path || []];
6752
+ const { data, path: path16, errorMaps, issueData } = params;
6753
+ const fullPath = [...path16, ...issueData.path || []];
6754
6754
  const fullIssue = {
6755
6755
  ...issueData,
6756
6756
  path: fullPath
@@ -6870,11 +6870,11 @@ var errorUtil;
6870
6870
 
6871
6871
  // node_modules/zod/v3/types.js
6872
6872
  var ParseInputLazyPath = class {
6873
- constructor(parent, value, path15, key) {
6873
+ constructor(parent, value, path16, key) {
6874
6874
  this._cachedPath = [];
6875
6875
  this.parent = parent;
6876
6876
  this.data = value;
6877
- this._path = path15;
6877
+ this._path = path16;
6878
6878
  this._key = key;
6879
6879
  }
6880
6880
  get path() {
@@ -10330,6 +10330,7 @@ var DEFAULT_OPTIONS = {
10330
10330
  historySize: 10,
10331
10331
  showTrend: true,
10332
10332
  flakinessTopN: 5,
10333
+ quarantineThreshold: 40,
10333
10334
  slowTestThreshold: 10,
10334
10335
  // Ticket-ID shape: @ODP-5328, REQ-001, JIRA-123. Tags matching this land in
10335
10336
  // the Requirements Traceability matrix; the rest fall to the tag summary.
@@ -10584,6 +10585,10 @@ var ReporterOptionsSchema = external_exports.object({
10584
10585
  // local history file is wiped every run. Aggregate numbers only.
10585
10586
  remoteHistory: external_exports.boolean().default(false),
10586
10587
  flakinessTopN: external_exports.number().int().min(0, "flakinessTopN must be 0 or greater").optional().default(DEFAULT_OPTIONS.flakinessTopN),
10588
+ quarantineThreshold: external_exports.number().int().min(0, "quarantineThreshold must be between 0 and 100").max(100, "quarantineThreshold must be between 0 and 100").optional().default(DEFAULT_OPTIONS.quarantineThreshold),
10589
+ evidenceUrlTemplate: external_exports.string().refine((s) => s.includes("{path}"), {
10590
+ message: "evidenceUrlTemplate must contain the {path} token"
10591
+ }).optional(),
10587
10592
  slowTestThreshold: external_exports.number().int().min(0, "slowTestThreshold must be 0 or greater").optional().default(DEFAULT_OPTIONS.slowTestThreshold),
10588
10593
  requirementTagPattern: external_exports.string().refine((v) => {
10589
10594
  try {
@@ -13790,8 +13795,8 @@ function buildBrowserMatrix(projects, cap = DEFAULT_ROW_CAP) {
13790
13795
  let projectNames = projects.map((p, i) => p.name || `project ${i + 1}`);
13791
13796
  const byTest = /* @__PURE__ */ new Map();
13792
13797
  projects.forEach((project, column) => {
13793
- walk(project.suites, [], (path15, test) => {
13794
- const key = [...path15, test.title].join(" > ");
13798
+ walk(project.suites, [], (path16, test) => {
13799
+ const key = [...path16, test.title].join(" > ");
13795
13800
  let statuses = byTest.get(key);
13796
13801
  if (!statuses) {
13797
13802
  statuses = projectNames.map(() => null);
@@ -13834,14 +13839,66 @@ function buildBrowserMatrix(projects, cap = DEFAULT_ROW_CAP) {
13834
13839
  function severityRank(row) {
13835
13840
  return row.statuses.some((s) => s === "failed" || s === "timedOut") ? 1 : 0;
13836
13841
  }
13837
- function walk(suites, path15, visit) {
13842
+ function walk(suites, path16, visit) {
13838
13843
  for (const suite of suites) {
13839
- const next = [...path15, suite.title];
13844
+ const next = [...path16, suite.title];
13840
13845
  for (const test of suite.tests) visit(next, test);
13841
13846
  walk(suite.suites, next, visit);
13842
13847
  }
13843
13848
  }
13844
13849
 
13850
+ // src/utils/evidence-links.ts
13851
+ init_cjs_shims();
13852
+ var import_node_path6 = __toESM(require("path"));
13853
+ var import_qrcode_generator = __toESM(require("qrcode-generator"));
13854
+ function applyEvidenceUrls(data, template, cwd) {
13855
+ let warned = false;
13856
+ const warnOnce = (err) => {
13857
+ if (warned) return;
13858
+ warned = true;
13859
+ const message = err instanceof Error ? err.message : String(err);
13860
+ logger.warn(`evidenceUrlTemplate: link generation failed for at least one artifact \u2014 ${message}`);
13861
+ };
13862
+ for (const failure of data.failures) {
13863
+ const evidence = failure.execution?.evidence;
13864
+ if (!evidence) continue;
13865
+ if (evidence.tracePath) {
13866
+ try {
13867
+ evidence.traceUrl = expandTemplate(template, relativizeArtifact(evidence.tracePath, cwd));
13868
+ evidence.traceQr = qrDataUri(evidence.traceUrl);
13869
+ } catch (err) {
13870
+ warnOnce(err);
13871
+ }
13872
+ }
13873
+ if (evidence.videoPath) {
13874
+ try {
13875
+ evidence.videoUrl = expandTemplate(template, relativizeArtifact(evidence.videoPath, cwd));
13876
+ evidence.videoQr = qrDataUri(evidence.videoUrl);
13877
+ } catch (err) {
13878
+ warnOnce(err);
13879
+ }
13880
+ }
13881
+ }
13882
+ }
13883
+ function relativizeArtifact(artifactPath, cwd) {
13884
+ if (!import_node_path6.default.isAbsolute(artifactPath)) return artifactPath;
13885
+ const rel = import_node_path6.default.relative(cwd, artifactPath);
13886
+ if (rel === ".." || rel.startsWith(`..${import_node_path6.default.sep}`) || import_node_path6.default.isAbsolute(rel)) return artifactPath;
13887
+ return rel;
13888
+ }
13889
+ function expandTemplate(template, artifactPath) {
13890
+ const normalized = artifactPath.replace(/\\/g, "/");
13891
+ const encoded = normalized.split("/").map((seg) => encodeURIComponent(seg)).join("/");
13892
+ return template.split("{path}").join(encoded);
13893
+ }
13894
+ function qrDataUri(url) {
13895
+ const qr = (0, import_qrcode_generator.default)(0, "M");
13896
+ qr.addData(url);
13897
+ qr.make();
13898
+ const svg = qr.createSvgTag({ cellSize: 2, margin: 2, scalable: true });
13899
+ return `data:image/svg+xml;base64,${Buffer.from(svg, "utf8").toString("base64")}`;
13900
+ }
13901
+
13845
13902
  // src/redact/redact-report.ts
13846
13903
  init_cjs_shims();
13847
13904
  function redactReportData(data, r) {
@@ -14007,7 +14064,7 @@ function splitBaselineResolved(baselineFailed, currentFailedIds, currentStatuses
14007
14064
  return { fixed, noLongerRun };
14008
14065
  }
14009
14066
  function computeRunDiff(args) {
14010
- const { currentFailing, currentStatuses, priorEntries, branch } = args;
14067
+ const { currentFailing, currentFlaky = [], currentStatuses, priorEntries, branch } = args;
14011
14068
  const baseline = priorEntries.find((e) => e.branch === branch && Array.isArray(e.failedTests));
14012
14069
  if (!baseline) return null;
14013
14070
  const baselineFailed = /* @__PURE__ */ new Map();
@@ -14016,7 +14073,15 @@ function computeRunDiff(args) {
14016
14073
  }
14017
14074
  const { newFailures, stillFailing, currentFailedIds } = splitCurrentFailing(currentFailing, baselineFailed);
14018
14075
  const { fixed, noLongerRun } = splitBaselineResolved(baselineFailed, currentFailedIds, currentStatuses);
14019
- for (const bucket of [newFailures, stillFailing, fixed, noLongerRun]) {
14076
+ const baselineFlakyIds = new Set((baseline.flakyTests ?? []).map((t) => t.id));
14077
+ const newlyFlaky = [];
14078
+ const seenFlaky = /* @__PURE__ */ new Set();
14079
+ for (const item of currentFlaky) {
14080
+ if (seenFlaky.has(item.id) || baselineFlakyIds.has(item.id)) continue;
14081
+ seenFlaky.add(item.id);
14082
+ newlyFlaky.push(item);
14083
+ }
14084
+ for (const bucket of [newFailures, stillFailing, fixed, noLongerRun, newlyFlaky]) {
14020
14085
  bucket.sort((a, b) => a.title.localeCompare(b.title));
14021
14086
  }
14022
14087
  return {
@@ -14024,6 +14089,7 @@ function computeRunDiff(args) {
14024
14089
  stillFailing,
14025
14090
  fixed,
14026
14091
  noLongerRun,
14092
+ newlyFlaky,
14027
14093
  baselineRunId: baseline.runId,
14028
14094
  baselineTimestamp: baseline.timestamp,
14029
14095
  baselineTruncated: baseline.failedTestsTruncated === true
@@ -14032,10 +14098,11 @@ function computeRunDiff(args) {
14032
14098
 
14033
14099
  // src/history/FlakinessAnalyser.ts
14034
14100
  init_cjs_shims();
14035
- function computeTopN(entries, topN) {
14036
- if (topN === 0) return [];
14101
+ var QUARANTINE_MIN_RUNS = 3;
14102
+ function computeTopN(entries, topN, quarantineThreshold = 0) {
14103
+ if (topN === 0) return { rows: [], quarantineTotal: 0 };
14037
14104
  const qualifying = entries.filter((e) => Array.isArray(e.flakyTests));
14038
- if (qualifying.length === 0) return [];
14105
+ if (qualifying.length === 0) return { rows: [], quarantineTotal: 0 };
14039
14106
  const map = /* @__PURE__ */ new Map();
14040
14107
  qualifying.forEach((entry, idx) => {
14041
14108
  const seen = /* @__PURE__ */ new Set();
@@ -14053,17 +14120,26 @@ function computeTopN(entries, topN) {
14053
14120
  const originalIdx = qualifying.length - 1 - reversedIdx;
14054
14121
  return hits.has(originalIdx);
14055
14122
  });
14123
+ let streak = 0;
14124
+ while (streak < totalRuns && hits.has(streak)) streak++;
14125
+ const flakeRate = Math.round(flakeCount / totalRuns * 100);
14056
14126
  return {
14057
14127
  title,
14058
- flakeRate: Math.round(flakeCount / totalRuns * 100),
14128
+ flakeRate,
14059
14129
  flakeCount,
14060
14130
  totalRuns,
14061
- sparkline
14131
+ sparkline,
14132
+ streak,
14133
+ quarantine: quarantineThreshold > 0 && totalRuns >= QUARANTINE_MIN_RUNS && flakeRate >= quarantineThreshold
14062
14134
  };
14063
14135
  });
14064
- return rows.sort(
14065
- (a, b) => b.flakeRate - a.flakeRate || b.flakeCount - a.flakeCount || a.title.localeCompare(b.title)
14066
- ).slice(0, topN);
14136
+ const quarantineTotal = rows.filter((r) => r.quarantine).length;
14137
+ return {
14138
+ rows: rows.sort(
14139
+ (a, b) => b.flakeRate - a.flakeRate || b.flakeCount - a.flakeCount || a.title.localeCompare(b.title)
14140
+ ).slice(0, topN),
14141
+ quarantineTotal
14142
+ };
14067
14143
  }
14068
14144
 
14069
14145
  // src/notify/index.ts
@@ -14506,6 +14582,9 @@ async function runReportPipeline(hooks) {
14506
14582
  }
14507
14583
  }
14508
14584
  const reportData = buildReportData(collected, licenseInfo, hooks);
14585
+ if (options.evidenceUrlTemplate) {
14586
+ applyEvidenceUrls(reportData, options.evidenceUrlTemplate, hooks.cwd);
14587
+ }
14509
14588
  runFailureAnalysis(reportData, options, hooks.cwd);
14510
14589
  reportData.briefSentence = buildBriefSentence(reportData.stats, reportData.analysis, reportData.charts.trend);
14511
14590
  if (hooks.redactor) {
@@ -14554,7 +14633,7 @@ async function appendTrend(collected, licenseInfo, hooks) {
14554
14633
  branch: collected.environment.branch
14555
14634
  };
14556
14635
  const localEntries = hm.append(entry, options.historySize);
14557
- collected.runDiff = resolveRunDiff(collected.projects, redactedFailing, localEntries.slice(1), collected.environment.branch);
14636
+ collected.runDiff = resolveRunDiff(collected.projects, redactedFailing, localEntries.slice(1), collected.environment.branch, flakyTests);
14558
14637
  let trendEntries = localEntries;
14559
14638
  if (options.remoteHistory && licenseInfo.jwt) {
14560
14639
  const serverUrl = options.serverUrl ?? process.env.REPORTFORGE_SERVER_URL ?? DEFAULT_SERVER_URL;
@@ -14587,9 +14666,17 @@ function populateHistoryCharts(trendEntries, localEntries, collected, options) {
14587
14666
  };
14588
14667
  }
14589
14668
  if (options.flakinessTopN > 0) {
14590
- const table = computeTopN(localEntries, options.flakinessTopN);
14591
- if (table.length > 0) {
14592
- collected.charts.flakinessTable = table;
14669
+ const { rows, quarantineTotal } = computeTopN(
14670
+ localEntries,
14671
+ options.flakinessTopN,
14672
+ options.quarantineThreshold
14673
+ );
14674
+ if (rows.length > 0) {
14675
+ collected.charts.flakinessTable = rows;
14676
+ if (quarantineTotal > 0) {
14677
+ collected.charts.flakinessQuarantineTotal = quarantineTotal;
14678
+ collected.charts.flakinessQuarantineThreshold = options.quarantineThreshold;
14679
+ }
14593
14680
  }
14594
14681
  }
14595
14682
  }
@@ -14726,9 +14813,10 @@ function buildFailedTestsFields(projects, redactor) {
14726
14813
  const failedTests = redactedFailing.slice(0, FAILED_TESTS_CAP);
14727
14814
  return redactedFailing.length > FAILED_TESTS_CAP ? { redactedFailing, failedTests, failedTestsTruncated: true } : { redactedFailing, failedTests };
14728
14815
  }
14729
- function resolveRunDiff(projects, currentFailing, priorEntries, branch) {
14816
+ function resolveRunDiff(projects, currentFailing, priorEntries, branch, currentFlaky) {
14730
14817
  return computeRunDiff({
14731
14818
  currentFailing,
14819
+ currentFlaky,
14732
14820
  currentStatuses: collectCurrentStatuses(projects),
14733
14821
  priorEntries,
14734
14822
  branch
@@ -15248,9 +15336,9 @@ var MAX_FILE_CHARS = 300;
15248
15336
  var MAX_PROJECT_CHARS = 100;
15249
15337
  var MAX_SUITE_SEGMENTS = 10;
15250
15338
  function suiteIdentity(test) {
15251
- const path15 = typeof test.titlePath === "function" ? test.titlePath() : [];
15252
- if (!Array.isArray(path15) || path15.length < 4) return {};
15253
- const [, project, file, ...rest] = path15;
15339
+ const path16 = typeof test.titlePath === "function" ? test.titlePath() : [];
15340
+ if (!Array.isArray(path16) || path16.length < 4) return {};
15341
+ const [, project, file, ...rest] = path16;
15254
15342
  const describes = rest.slice(0, -1);
15255
15343
  const out = {};
15256
15344
  if (typeof file === "string" && file) {
@@ -15369,7 +15457,7 @@ var PdfReporter = class {
15369
15457
  if (this.options.redact.enabled) {
15370
15458
  this.redactor = new Redactor(this.options.redact);
15371
15459
  }
15372
- this.version = true ? "0.29.0" : "0.x";
15460
+ this.version = true ? "0.31.0" : "0.x";
15373
15461
  logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
15374
15462
  this.licenseClient = new LicenseClient({
15375
15463
  licenseKey: this.options.licenseKey,