@reportforge/playwright-pdf 0.2.2 → 0.4.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/CHANGELOG.md +24 -0
- package/README.md +6 -0
- package/dist/cli/export-feedback.js +87 -0
- package/dist/demo/cli.js +11912 -0
- package/dist/index.d.mts +31 -2
- package/dist/index.d.ts +31 -2
- package/dist/index.js +503 -116
- package/dist/index.mjs +580 -193
- package/dist/templates/layouts/detailed.hbs +2 -0
- package/dist/templates/layouts/executive.hbs +2 -0
- package/dist/templates/partials/analysis-oneliner.hbs +3 -0
- package/dist/templates/partials/failure-analysis.hbs +42 -0
- package/dist/templates/styles/base.css +3 -0
- package/dist/templates/styles/detailed.css +21 -0
- package/package.json +10 -2
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(
|
|
1225
|
-
return /^\.|this\b/.test(
|
|
1224
|
+
scopedId: function scopedId(path14) {
|
|
1225
|
+
return /^\.|this\b/.test(path14.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(
|
|
1230
|
-
return
|
|
1229
|
+
simpleId: function simpleId(path14) {
|
|
1230
|
+
return path14.parts.length === 1 && !AST.helpers.scopedId(path14) && !path14.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(
|
|
2304
|
+
function prepareMustache(path14, 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:
|
|
2309
|
+
path: path14,
|
|
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),
|
|
2629
|
+
var params = this.setupFullMustacheParams(decorator, program, void 0), path14 = decorator.path;
|
|
2630
2630
|
this.useDecorators = true;
|
|
2631
|
-
this.opcode("registerDecorator", params.length,
|
|
2631
|
+
this.opcode("registerDecorator", params.length, path14.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
|
|
2696
|
-
this.opcode("getContext",
|
|
2695
|
+
var path14 = sexpr.path, name = path14.parts[0], isBlock = program != null || inverse != null;
|
|
2696
|
+
this.opcode("getContext", path14.depth);
|
|
2697
2697
|
this.opcode("pushProgram", program);
|
|
2698
2698
|
this.opcode("pushProgram", inverse);
|
|
2699
|
-
|
|
2700
|
-
this.accept(
|
|
2699
|
+
path14.strict = true;
|
|
2700
|
+
this.accept(path14);
|
|
2701
2701
|
this.opcode("invokeAmbiguous", name, isBlock);
|
|
2702
2702
|
},
|
|
2703
2703
|
simpleSexpr: function simpleSexpr(sexpr) {
|
|
2704
|
-
var
|
|
2705
|
-
|
|
2706
|
-
this.accept(
|
|
2704
|
+
var path14 = sexpr.path;
|
|
2705
|
+
path14.strict = true;
|
|
2706
|
+
this.accept(path14);
|
|
2707
2707
|
this.opcode("resolvePossibleLambda");
|
|
2708
2708
|
},
|
|
2709
2709
|
helperSexpr: function helperSexpr(sexpr, program, inverse) {
|
|
2710
|
-
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
|
2710
|
+
var params = this.setupFullMustacheParams(sexpr, program, inverse), path14 = sexpr.path, name = path14.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
|
-
|
|
2717
|
-
|
|
2718
|
-
this.accept(
|
|
2719
|
-
this.opcode("invokeHelper", params.length,
|
|
2716
|
+
path14.strict = true;
|
|
2717
|
+
path14.falsy = true;
|
|
2718
|
+
this.accept(path14);
|
|
2719
|
+
this.opcode("invokeHelper", params.length, path14.original, _ast2["default"].helpers.simpleId(path14));
|
|
2720
2720
|
}
|
|
2721
2721
|
},
|
|
2722
|
-
PathExpression: function PathExpression(
|
|
2723
|
-
this.addDepth(
|
|
2724
|
-
this.opcode("getContext",
|
|
2725
|
-
var name =
|
|
2722
|
+
PathExpression: function PathExpression(path14) {
|
|
2723
|
+
this.addDepth(path14.depth);
|
|
2724
|
+
this.opcode("getContext", path14.depth);
|
|
2725
|
+
var name = path14.parts[0], scoped = _ast2["default"].helpers.scopedId(path14), blockParamId = !path14.depth && !scoped && this.blockParamIndex(name);
|
|
2726
2726
|
if (blockParamId) {
|
|
2727
|
-
this.opcode("lookupBlockParam", blockParamId,
|
|
2727
|
+
this.opcode("lookupBlockParam", blockParamId, path14.parts);
|
|
2728
2728
|
} else if (!name) {
|
|
2729
2729
|
this.opcode("pushContext");
|
|
2730
|
-
} else if (
|
|
2730
|
+
} else if (path14.data) {
|
|
2731
2731
|
this.options.data = true;
|
|
2732
|
-
this.opcode("lookupData",
|
|
2732
|
+
this.opcode("lookupData", path14.depth, path14.parts, path14.strict);
|
|
2733
2733
|
} else {
|
|
2734
|
-
this.opcode("lookupOnContext",
|
|
2734
|
+
this.opcode("lookupOnContext", path14.parts, path14.falsy, path14.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
|
|
3090
|
+
var path14 = aPath;
|
|
3091
3091
|
var url = urlParse(aPath);
|
|
3092
3092
|
if (url) {
|
|
3093
3093
|
if (!url.path) {
|
|
3094
3094
|
return aPath;
|
|
3095
3095
|
}
|
|
3096
|
-
|
|
3096
|
+
path14 = url.path;
|
|
3097
3097
|
}
|
|
3098
|
-
var isAbsolute = exports2.isAbsolute(
|
|
3099
|
-
var parts =
|
|
3098
|
+
var isAbsolute = exports2.isAbsolute(path14);
|
|
3099
|
+
var parts = path14.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
|
-
|
|
3117
|
-
if (
|
|
3118
|
-
|
|
3116
|
+
path14 = parts.join("/");
|
|
3117
|
+
if (path14 === "") {
|
|
3118
|
+
path14 = isAbsolute ? "/" : ".";
|
|
3119
3119
|
}
|
|
3120
3120
|
if (url) {
|
|
3121
|
-
url.path =
|
|
3121
|
+
url.path = path14;
|
|
3122
3122
|
return urlGenerate(url);
|
|
3123
3123
|
}
|
|
3124
|
-
return
|
|
3124
|
+
return path14;
|
|
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
|
|
5928
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
5927
|
+
var path14 = id.parts.join("/");
|
|
5928
|
+
return (id.data ? "@" : "") + "PATH:" + path14;
|
|
5929
5929
|
};
|
|
5930
5930
|
PrintVisitor.prototype.StringLiteral = function(string) {
|
|
5931
5931
|
return '"' + string.value + '"';
|
|
@@ -5966,8 +5966,8 @@ var require_lib = __commonJS({
|
|
|
5966
5966
|
handlebars.print = printer.print;
|
|
5967
5967
|
module2.exports = handlebars;
|
|
5968
5968
|
function extension(module3, filename) {
|
|
5969
|
-
var
|
|
5970
|
-
var templateString =
|
|
5969
|
+
var fs14 = require("fs");
|
|
5970
|
+
var templateString = fs14.readFileSync(filename, "utf8");
|
|
5971
5971
|
module3.exports = handlebars.compile(templateString);
|
|
5972
5972
|
}
|
|
5973
5973
|
if (typeof require !== "undefined" && require.extensions) {
|
|
@@ -5996,7 +5996,7 @@ var require_util2 = __commonJS({
|
|
|
5996
5996
|
"node_modules/chrome-finder/lib/util.js"(exports2, module2) {
|
|
5997
5997
|
"use strict";
|
|
5998
5998
|
init_cjs_shims();
|
|
5999
|
-
var
|
|
5999
|
+
var fs14 = require("fs");
|
|
6000
6000
|
var newLineRegex = /\r?\n/;
|
|
6001
6001
|
function sort(installations, priorities) {
|
|
6002
6002
|
const defaultPriority = 10;
|
|
@@ -6014,7 +6014,7 @@ var require_util2 = __commonJS({
|
|
|
6014
6014
|
return false;
|
|
6015
6015
|
}
|
|
6016
6016
|
try {
|
|
6017
|
-
|
|
6017
|
+
fs14.accessSync(file);
|
|
6018
6018
|
return true;
|
|
6019
6019
|
} catch (e) {
|
|
6020
6020
|
return false;
|
|
@@ -6025,7 +6025,7 @@ var require_util2 = __commonJS({
|
|
|
6025
6025
|
return false;
|
|
6026
6026
|
}
|
|
6027
6027
|
try {
|
|
6028
|
-
var stat =
|
|
6028
|
+
var stat = fs14.statSync(file);
|
|
6029
6029
|
return stat && typeof stat.isFile === "function" && stat.isFile();
|
|
6030
6030
|
} catch (e) {
|
|
6031
6031
|
return false;
|
|
@@ -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
|
|
6048
|
+
var path14 = 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 =
|
|
6062
|
+
const chromePath = path14.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
|
|
6080
|
+
var path14 = 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 =
|
|
6090
|
+
const execPath = path14.join(inst.trim(), suffix);
|
|
6091
6091
|
if (canAccess(execPath)) {
|
|
6092
6092
|
installations.push(execPath);
|
|
6093
6093
|
}
|
|
@@ -6116,8 +6116,8 @@ var require_linux = __commonJS({
|
|
|
6116
6116
|
"use strict";
|
|
6117
6117
|
init_cjs_shims();
|
|
6118
6118
|
var { execSync: execSync3, execFileSync: execFileSync2 } = require("child_process");
|
|
6119
|
-
var
|
|
6120
|
-
var
|
|
6119
|
+
var path14 = require("path");
|
|
6120
|
+
var fs14 = require("fs");
|
|
6121
6121
|
var { canAccess, sort, isExecutable, newLineRegex } = require_util2();
|
|
6122
6122
|
function findChromeExecutablesForLinuxDesktop(folder) {
|
|
6123
6123
|
const argumentsRegex = /(^[^ ]+).*/;
|
|
@@ -6134,7 +6134,7 @@ var require_linux = __commonJS({
|
|
|
6134
6134
|
function linux() {
|
|
6135
6135
|
let installations = [];
|
|
6136
6136
|
const desktopInstallationFolders = [
|
|
6137
|
-
|
|
6137
|
+
path14.join(require("os").homedir(), ".local/share/applications/"),
|
|
6138
6138
|
"/usr/share/applications/"
|
|
6139
6139
|
];
|
|
6140
6140
|
desktopInstallationFolders.forEach((folder) => {
|
|
@@ -6160,7 +6160,7 @@ var require_linux = __commonJS({
|
|
|
6160
6160
|
].map((possiblePath) => {
|
|
6161
6161
|
try {
|
|
6162
6162
|
const chromePathToTest = possiblePath + "/" + executable;
|
|
6163
|
-
if (
|
|
6163
|
+
if (fs14.existsSync(chromePathToTest) && canAccess(chromePathToTest) && isExecutable(chromePathToTest)) {
|
|
6164
6164
|
installations.push(chromePathToTest);
|
|
6165
6165
|
return chromePathToTest;
|
|
6166
6166
|
}
|
|
@@ -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:
|
|
6753
|
-
const fullPath = [...
|
|
6752
|
+
const { data, path: path14, errorMaps, issueData } = params;
|
|
6753
|
+
const fullPath = [...path14, ...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,
|
|
6873
|
+
constructor(parent, value, path14, key) {
|
|
6874
6874
|
this._cachedPath = [];
|
|
6875
6875
|
this.parent = parent;
|
|
6876
6876
|
this.data = value;
|
|
6877
|
-
this._path =
|
|
6877
|
+
this._path = path14;
|
|
6878
6878
|
this._key = key;
|
|
6879
6879
|
}
|
|
6880
6880
|
get path() {
|
|
@@ -10354,6 +10354,16 @@ var notifyConfigSchema = external_exports.object({
|
|
|
10354
10354
|
discord: channelConfigSchema.optional(),
|
|
10355
10355
|
email: emailChannelConfigSchema.optional()
|
|
10356
10356
|
}).optional();
|
|
10357
|
+
var failureAnalysisConfigSchema = external_exports.object({
|
|
10358
|
+
enabled: external_exports.boolean().default(true),
|
|
10359
|
+
maxClusters: external_exports.number().int().min(1).max(50).default(10),
|
|
10360
|
+
// Qualitative floor (not a numeric % — consistent with the match-strength design).
|
|
10361
|
+
minStrength: external_exports.enum(["weak", "moderate", "strong"]).default("weak"),
|
|
10362
|
+
maxFailuresToAnalyse: external_exports.number().int().min(1).default(500),
|
|
10363
|
+
// Consumed by the Plan 3 feedback collector; inert until then.
|
|
10364
|
+
collectUnclassified: external_exports.boolean().default(true),
|
|
10365
|
+
autoUpdateModel: external_exports.boolean().default(true)
|
|
10366
|
+
}).default({});
|
|
10357
10367
|
var ReporterOptionsSchema = external_exports.object({
|
|
10358
10368
|
outputFile: external_exports.string().optional().default(DEFAULT_OPTIONS.outputFile),
|
|
10359
10369
|
template: external_exports.union([
|
|
@@ -10383,6 +10393,7 @@ var ReporterOptionsSchema = external_exports.object({
|
|
|
10383
10393
|
historySize: external_exports.number().int().min(2).optional().default(DEFAULT_OPTIONS.historySize),
|
|
10384
10394
|
showTrend: external_exports.boolean().optional().default(DEFAULT_OPTIONS.showTrend),
|
|
10385
10395
|
flakinessTopN: external_exports.number().int().min(0, "flakinessTopN must be 0 or greater").optional().default(DEFAULT_OPTIONS.flakinessTopN),
|
|
10396
|
+
failureAnalysis: failureAnalysisConfigSchema,
|
|
10386
10397
|
templatePath: external_exports.union([
|
|
10387
10398
|
external_exports.string().regex(/\.hbs$/, "templatePath must end with .hbs").refine((v) => v.slice(0, -4).trim().length > 0, "templatePath basename must not be empty"),
|
|
10388
10399
|
external_exports.array(
|
|
@@ -10622,6 +10633,17 @@ function clearCache() {
|
|
|
10622
10633
|
init_cjs_shims();
|
|
10623
10634
|
var LICENSE_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAbvknstk0gxoJ7UwATdlxR8btxvO7u7RdpO1uV90PtuE=\n-----END PUBLIC KEY-----";
|
|
10624
10635
|
|
|
10636
|
+
// src/analysis/ModelStore.ts
|
|
10637
|
+
init_cjs_shims();
|
|
10638
|
+
var import_node_fs = __toESM(require("fs"));
|
|
10639
|
+
var import_node_os = __toESM(require("os"));
|
|
10640
|
+
var import_node_path = __toESM(require("path"));
|
|
10641
|
+
var import_node_crypto = require("crypto");
|
|
10642
|
+
|
|
10643
|
+
// src/analysis/model-bundle.ts
|
|
10644
|
+
init_cjs_shims();
|
|
10645
|
+
var MODEL = { "alpha": 1, "labels": ["assertion", "env-config", "flaky-by-retry", "locator-not-found", "navigation", "network", "timeout"], "logLikelihoods": { "assertion": { "NUM": -4.574710978503383, "assertionerror": -4.980176086611547, "be": -4.980176086611547, "cart": -4.980176086611547, "checkout": -4.980176086611547, "com": -4.980176086611547, "dashboard": -4.980176086611547, "deep": -4.980176086611547, "equal": -4.980176086611547, "equality": -4.980176086611547, "error": -4.980176086611547, "expect": -3.5938817254916566, "expected": -3.1083739097099556, "failed": -4.980176086611547, "false": -4.980176086611547, "got": -4.980176086611547, "id": -4.574710978503383, "john": -4.980176086611547, "locator": -4.574710978503383, "login": -4.980176086611547, "must": -4.980176086611547, "page": -4.980176086611547, "pattern": -4.980176086611547, "received": -3.275427994373122, "string": -4.980176086611547, "to": -4.980176086611547, "tobe": -4.980176086611547, "tobetruthy": -4.980176086611547, "toequal": -4.980176086611547, "tohavecount": -4.980176086611547, "tohavetext": -4.980176086611547, "tohavetitle": -4.980176086611547, "tohavevalue": -4.980176086611547, "truthy": -4.980176086611547, "value": -4.980176086611547 }, "env-config": { "NUM": -4.997212273764115, "api": -4.997212273764115, "at": -4.997212273764115, "back": -4.997212273764115, "base": -4.997212273764115, "baseurl": -4.997212273764115, "browsers": -4.997212273764115, "cannot": -4.997212273764115, "ci": -4.997212273764115, "config": -4.304065093204169, "directory": -4.591747165655951, "dotenv": -4.997212273764115, "enoent": -4.997212273764115, "env": -4.591747165655951, "environment": -4.591747165655951, "error": -4.08092154188996, "existent": -4.997212273764115, "failed": -4.997212273764115, "falling": -4.997212273764115, "file": -4.997212273764115, "find": -4.997212273764115, "from": -4.997212273764115, "in": -4.591747165655951, "invalid": -4.997212273764115, "is": -4.997212273764115, "json": -4.591747165655951, "key": -4.997212273764115, "load": -4.997212273764115, "missing": -4.997212273764115, "module": -4.997212273764115, "no": -4.997212273764115, "non": -4.997212273764115, "not": -4.997212273764115, "open": -4.997212273764115, "or": -4.997212273764115, "path": -4.997212273764115, "playwright": -4.591747165655951, "points": -4.997212273764115, "position": -4.997212273764115, "required": -4.997212273764115, "set": -4.997212273764115, "spec": -4.997212273764115, "such": -4.997212273764115, "syntaxerror": -4.997212273764115, "test": -4.997212273764115, "to": -4.304065093204169, "token": -4.997212273764115, "ts": -4.997212273764115, "tsconfig": -4.997212273764115, "undefined": -4.591747165655951, "unexpected": -4.997212273764115, "url": -4.997212273764115, "values": -4.997212273764115, "variable": -4.591747165655951 }, "flaky-by-retry": { "after": -4.58836306767171, "and": -4.993828175779875, "animation": -4.993828175779875, "attempt": -4.07753744390572, "between": -4.993828175779875, "blip": -4.993828175779875, "cause": -4.993828175779875, "change": -4.993828175779875, "click": -4.993828175779875, "code": -4.993828175779875, "commit": -4.993828175779875, "condition": -4.993828175779875, "dependency": -4.993828175779875, "detached": -4.993828175779875, "deterministic": -4.993828175779875, "element": -4.993828175779875, "failed": -4.300680995219929, "fails": -4.993828175779875, "flaky": -4.07753744390572, "green": -4.993828175779875, "in": -4.993828175779875, "intermittent": -4.993828175779875, "locator": -4.993828175779875, "marked": -4.993828175779875, "network": -4.993828175779875, "no": -4.993828175779875, "non": -4.993828175779875, "on": -3.895215887111765, "order": -4.993828175779875, "passed": -3.895215887111765, "quarantined": -4.993828175779875, "race": -4.993828175779875, "reproducible": -4.993828175779875, "rerun": -4.993828175779875, "retry": -4.07753744390572, "runs": -4.993828175779875, "same": -4.993828175779875, "second": -4.993828175779875, "stale": -4.993828175779875, "succeeded": -4.993828175779875, "test": -4.58836306767171, "the": -4.993828175779875, "then": -4.993828175779875, "timeout": -4.993828175779875, "unstable": -4.993828175779875, "with": -4.993828175779875, "without": -4.993828175779875 }, "locator-not-found": { "an": -4.993828175779875, "attached": -4.993828175779875, "btn": -4.993828175779875, "button": -4.58836306767171, "cart": -4.993828175779875, "checkout": -4.993828175779875, "click": -4.993828175779875, "data": -4.993828175779875, "div": -4.993828175779875, "dom": -4.993828175779875, "element": -4.07753744390572, "elements": -4.300680995219929, "error": -4.58836306767171, "find": -4.993828175779875, "for": -4.58836306767171, "found": -4.58836306767171, "getbyrole": -4.993828175779875, "getbytext": -4.993828175779875, "icon": -4.993828175779875, "id": -4.993828175779875, "is": -4.993828175779875, "locator": -4.07753744390572, "matched": -4.993828175779875, "matches": -4.993828175779875, "missing": -4.993828175779875, "mode": -4.58836306767171, "name": -4.993828175779875, "no": -4.58836306767171, "node": -4.993828175779875, "not": -4.58836306767171, "resolved": -4.58836306767171, "save": -4.993828175779875, "selector": -4.58836306767171, "strict": -4.58836306767171, "submit": -4.993828175779875, "testid": -4.993828175779875, "textcontent": -4.993828175779875, "the": -4.58836306767171, "to": -4.07753744390572, "total": -4.993828175779875, "unable": -4.993828175779875, "violation": -4.58836306767171, "waiting": -4.993828175779875, "with": -4.993828175779875, "xpath": -4.993828175779875 }, "navigation": { "NUM": -4.983606621708336, "aborted": -4.983606621708336, "another": -4.983606621708336, "back": -4.983606621708336, "because": -4.578141513600172, "by": -4.578141513600172, "checkout": -4.983606621708336, "context": -4.983606621708336, "crashed": -4.983606621708336, "dashboard": -4.983606621708336, "destroyed": -4.983606621708336, "detached": -4.983606621708336, "entry": -4.983606621708336, "err": -4.578141513600172, "exceeded": -4.983606621708336, "execution": -4.983606621708336, "failed": -4.983606621708336, "frame": -4.578141513600172, "goback": -4.983606621708336, "goto": -4.290459441148391, "history": -4.983606621708336, "home": -4.983606621708336, "interrupted": -4.578141513600172, "is": -4.983606621708336, "likely": -4.983606621708336, "login": -4.983606621708336, "many": -4.983606621708336, "maybe": -4.983606621708336, "most": -4.983606621708336, "navigate": -4.983606621708336, "navigating": -4.983606621708336, "navigation": -3.7308436532129683, "net": -4.578141513600172, "no": -4.983606621708336, "of": -4.983606621708336, "page": -3.884994333040227, "redirects": -4.983606621708336, "timeout": -4.983606621708336, "to": -3.7308436532129683, "too": -4.983606621708336, "waitforurl": -4.983606621708336, "was": -4.578141513600172 }, "network": { "NUM": -4.57126863431241, "URL": -4.283586561860629, "again": -4.976733742420574, "api": -4.976733742420574, "apirequestcontext": -4.976733742420574, "at": -4.976733742420574, "body": -4.976733742420574, "connect": -4.976733742420574, "connection": -4.57126863431241, "disconnected": -4.976733742420574, "eai": -4.976733742420574, "econnrefused": -4.976733742420574, "err": -3.8781214537524646, "error": -4.976733742420574, "failed": -4.283586561860629, "fetch": -4.976733742420574, "fetching": -4.976733742420574, "from": -4.976733742420574, "get": -4.976733742420574, "getaddrinfo": -4.976733742420574, "goto": -4.976733742420574, "hang": -4.976733742420574, "internet": -4.976733742420574, "loading": -4.976733742420574, "name": -4.976733742420574, "net": -3.8781214537524646, "not": -4.976733742420574, "on": -4.976733742420574, "out": -4.976733742420574, "page": -4.57126863431241, "products": -4.976733742420574, "protocol": -4.976733742420574, "reading": -4.976733742420574, "reason": -4.976733742420574, "refused": -4.976733742420574, "request": -4.57126863431241, "resolved": -4.976733742420574, "resource": -4.976733742420574, "response": -4.976733742420574, "route": -4.976733742420574, "socket": -4.976733742420574, "ssl": -4.976733742420574, "timed": -4.976733742420574, "to": -4.976733742420574, "up": -4.976733742420574, "upstream": -4.976733742420574, "while": -4.976733742420574 }, "timeout": { "NUM": -3.4965075614664802, "after": -5.000584958242754, "be": -4.59511985013459, "cart": -5.000584958242754, "click": -5.000584958242754, "element": -5.000584958242754, "event": -5.000584958242754, "exceeded": -3.6142905971228636, "expect": -5.000584958242754, "fill": -5.000584958242754, "for": -3.7478219897473863, "function": -5.000584958242754, "load": -5.000584958242754, "locator": -4.084294226368599, "navigation": -5.000584958242754, "of": -5.000584958242754, "out": -5.000584958242754, "page": -4.59511985013459, "running": -5.000584958242754, "selector": -5.000584958242754, "submit": -5.000584958242754, "test": -4.59511985013459, "timed": -5.000584958242754, "timeout": -3.6142905971228636, "timeouterror": -4.59511985013459, "to": -4.59511985013459, "tobevisible": -5.000584958242754, "visible": -4.59511985013459, "waitforloadstate": -5.000584958242754, "waitforselector": -5.000584958242754, "waiting": -3.7478219897473863, "while": -5.000584958242754 } }, "playwrightVersion": "1.59.1", "priors": { "assertion": -1.9636097261547143, "env-config": -1.9636097261547143, "flaky-by-retry": -1.9636097261547143, "locator-not-found": -1.9636097261547143, "navigation": -1.9636097261547143, "network": -1.9636097261547143, "timeout": -1.845826690498331 }, "totals": { "assertion": 64, "env-config": 69, "flaky-by-retry": 68, "locator-not-found": 68, "navigation": 65, "network": 63, "timeout": 70 }, "version": 1, "vocabSize": 227 };
|
|
10646
|
+
|
|
10625
10647
|
// src/utils/logger.ts
|
|
10626
10648
|
init_cjs_shims();
|
|
10627
10649
|
var PREFIX = "[reportforge]";
|
|
@@ -10643,6 +10665,83 @@ ${JSON.stringify(data, null, 2)}` : "";
|
|
|
10643
10665
|
}
|
|
10644
10666
|
};
|
|
10645
10667
|
|
|
10668
|
+
// src/analysis/ModelStore.ts
|
|
10669
|
+
var MODEL_SIG_DOMAIN = "reportforge-model-sig:v1\n";
|
|
10670
|
+
var testDeps = null;
|
|
10671
|
+
function cacheFile() {
|
|
10672
|
+
return testDeps?.cacheFile ?? import_node_path.default.join(import_node_os.default.homedir(), ".reportforge", "model.json");
|
|
10673
|
+
}
|
|
10674
|
+
function publicKey() {
|
|
10675
|
+
const pem = testDeps?.publicKeyPem ?? LICENSE_PUBLIC_KEY_PEM;
|
|
10676
|
+
try {
|
|
10677
|
+
return pem && pem.includes("PUBLIC KEY") ? (0, import_node_crypto.createPublicKey)(pem) : null;
|
|
10678
|
+
} catch {
|
|
10679
|
+
return null;
|
|
10680
|
+
}
|
|
10681
|
+
}
|
|
10682
|
+
function verifyModelSignature(modelJson, signatureB64url) {
|
|
10683
|
+
const key = publicKey();
|
|
10684
|
+
if (!key) return false;
|
|
10685
|
+
try {
|
|
10686
|
+
return (0, import_node_crypto.verify)(null, Buffer.from(MODEL_SIG_DOMAIN + modelJson), key, Buffer.from(signatureB64url, "base64url"));
|
|
10687
|
+
} catch {
|
|
10688
|
+
return false;
|
|
10689
|
+
}
|
|
10690
|
+
}
|
|
10691
|
+
function schemaValid(m) {
|
|
10692
|
+
const x = m;
|
|
10693
|
+
return !!x && Array.isArray(x.labels) && x.labels.length > 0 && typeof x.version === "number" && !!x.priors && !!x.totals && !!x.logLikelihoods && typeof x.vocabSize === "number" && typeof x.alpha === "number";
|
|
10694
|
+
}
|
|
10695
|
+
function readCache2() {
|
|
10696
|
+
try {
|
|
10697
|
+
const raw = import_node_fs.default.readFileSync(cacheFile(), "utf8");
|
|
10698
|
+
const c = JSON.parse(raw);
|
|
10699
|
+
if (typeof c.version === "number" && typeof c.modelJson === "string" && typeof c.signature === "string") {
|
|
10700
|
+
return c;
|
|
10701
|
+
}
|
|
10702
|
+
} catch {
|
|
10703
|
+
}
|
|
10704
|
+
return null;
|
|
10705
|
+
}
|
|
10706
|
+
function loadModel(autoUpdate) {
|
|
10707
|
+
if (!autoUpdate) return MODEL;
|
|
10708
|
+
const c = readCache2();
|
|
10709
|
+
if (!c || c.version <= MODEL.version) return MODEL;
|
|
10710
|
+
if (!verifyModelSignature(c.modelJson, c.signature)) {
|
|
10711
|
+
logger.debug(`cached model v${c.version} signature invalid \u2014 using bundled (possible tampering or key mismatch)`);
|
|
10712
|
+
return MODEL;
|
|
10713
|
+
}
|
|
10714
|
+
try {
|
|
10715
|
+
const m = JSON.parse(c.modelJson);
|
|
10716
|
+
if (schemaValid(m)) return m;
|
|
10717
|
+
logger.debug(`cached model v${c.version} failed schema validation \u2014 using bundled`);
|
|
10718
|
+
} catch {
|
|
10719
|
+
logger.debug(`cached model v${c.version} JSON parse failed \u2014 using bundled`);
|
|
10720
|
+
}
|
|
10721
|
+
return MODEL;
|
|
10722
|
+
}
|
|
10723
|
+
function acceptDeliveredModel(d) {
|
|
10724
|
+
try {
|
|
10725
|
+
const current = readCache2()?.version ?? MODEL.version;
|
|
10726
|
+
if (d.version <= current) return;
|
|
10727
|
+
if (!verifyModelSignature(d.modelJson, d.signature)) {
|
|
10728
|
+
logger.debug(`delivered model v${d.version} signature invalid \u2014 rejected`);
|
|
10729
|
+
return;
|
|
10730
|
+
}
|
|
10731
|
+
const m = JSON.parse(d.modelJson);
|
|
10732
|
+
if (!schemaValid(m)) {
|
|
10733
|
+
logger.debug(`delivered model v${d.version} failed schema validation \u2014 rejected`);
|
|
10734
|
+
return;
|
|
10735
|
+
}
|
|
10736
|
+
const file = cacheFile();
|
|
10737
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(file), { recursive: true });
|
|
10738
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
10739
|
+
import_node_fs.default.writeFileSync(tmp, JSON.stringify({ version: d.version, modelJson: d.modelJson, signature: d.signature }), { mode: 384 });
|
|
10740
|
+
import_node_fs.default.renameSync(tmp, file);
|
|
10741
|
+
} catch {
|
|
10742
|
+
}
|
|
10743
|
+
}
|
|
10744
|
+
|
|
10646
10745
|
// src/license/LicenseClient.ts
|
|
10647
10746
|
var DEFAULT_SERVER_URL = "https://reportforge.org";
|
|
10648
10747
|
var REFRESH_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -10656,6 +10755,8 @@ var LicenseClient = class {
|
|
|
10656
10755
|
this.serverUrl = normalizeUrl(
|
|
10657
10756
|
opts.serverUrl ?? process.env.REPORTFORGE_SERVER_URL ?? DEFAULT_SERVER_URL
|
|
10658
10757
|
);
|
|
10758
|
+
this.autoUpdateModel = opts.autoUpdateModel ?? true;
|
|
10759
|
+
this.modelVersionForServer = this.autoUpdateModel ? opts.currentModelVersion ?? 0 : Number.MAX_SAFE_INTEGER;
|
|
10659
10760
|
}
|
|
10660
10761
|
async resolve() {
|
|
10661
10762
|
const key = this.rawKey || process.env.RF_LICENSE_KEY?.trim();
|
|
@@ -10736,10 +10837,14 @@ var LicenseClient = class {
|
|
|
10736
10837
|
machinesUsed: result.machinesUsed,
|
|
10737
10838
|
machineLimit: result.machineLimit
|
|
10738
10839
|
});
|
|
10840
|
+
if (this.autoUpdateModel && result.model) {
|
|
10841
|
+
acceptDeliveredModel(result.model);
|
|
10842
|
+
}
|
|
10739
10843
|
return {
|
|
10740
10844
|
plan: "subscription",
|
|
10741
10845
|
source: "jwt-online",
|
|
10742
10846
|
key: normalizedKey,
|
|
10847
|
+
jwt: result.jwt,
|
|
10743
10848
|
expiry: new Date(payload.exp * 1e3),
|
|
10744
10849
|
machinesUsed: result.machinesUsed,
|
|
10745
10850
|
machineLimit: result.machineLimit
|
|
@@ -10771,13 +10876,15 @@ var LicenseClient = class {
|
|
|
10771
10876
|
return this.post("/api/license/activate", {
|
|
10772
10877
|
licenseKey,
|
|
10773
10878
|
fingerprint: this.fingerprint.hash,
|
|
10774
|
-
label: this.fingerprint.label
|
|
10879
|
+
label: this.fingerprint.label,
|
|
10880
|
+
modelVersion: this.modelVersionForServer
|
|
10775
10881
|
});
|
|
10776
10882
|
}
|
|
10777
10883
|
async refresh(jwt) {
|
|
10778
10884
|
return this.post("/api/license/refresh", {
|
|
10779
10885
|
jwt,
|
|
10780
|
-
label: this.fingerprint.label
|
|
10886
|
+
label: this.fingerprint.label,
|
|
10887
|
+
modelVersion: this.modelVersionForServer
|
|
10781
10888
|
});
|
|
10782
10889
|
}
|
|
10783
10890
|
async post(route, body) {
|
|
@@ -10822,6 +10929,7 @@ function toLicenseInfo(payload, cached, normalizedKey, source) {
|
|
|
10822
10929
|
plan: "subscription",
|
|
10823
10930
|
source,
|
|
10824
10931
|
key: normalizedKey,
|
|
10932
|
+
jwt: cached.jwt,
|
|
10825
10933
|
expiry: new Date(payload.exp * 1e3),
|
|
10826
10934
|
// Surface the snapshot from the last activate/refresh. Undefined for
|
|
10827
10935
|
// caches written by older reporter versions before these fields were
|
|
@@ -10834,11 +10942,307 @@ function normalizeUrl(url) {
|
|
|
10834
10942
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
10835
10943
|
}
|
|
10836
10944
|
|
|
10945
|
+
// src/analysis/index.ts
|
|
10946
|
+
init_cjs_shims();
|
|
10947
|
+
|
|
10948
|
+
// src/analysis/FailureClusterer.ts
|
|
10949
|
+
init_cjs_shims();
|
|
10950
|
+
|
|
10951
|
+
// src/analysis/NaiveBayesClassifier.ts
|
|
10952
|
+
init_cjs_shims();
|
|
10953
|
+
|
|
10954
|
+
// src/analysis/tokenize.ts
|
|
10955
|
+
init_cjs_shims();
|
|
10956
|
+
function tokenize(text) {
|
|
10957
|
+
return text.toLowerCase().replace(/https?:\/\/\S+/gi, " URL ").replace(/[^a-zA-Z0-9\s]/g, " ").replace(/\b\d{3,}[a-z]*\b/g, " NUM ").split(/\s+/).filter((t) => t.length > 1);
|
|
10958
|
+
}
|
|
10959
|
+
|
|
10960
|
+
// src/analysis/NaiveBayesClassifier.ts
|
|
10961
|
+
function classify(model, text) {
|
|
10962
|
+
const tokens = tokenize(text);
|
|
10963
|
+
const vocabTokens = tokens.filter(
|
|
10964
|
+
(tok) => model.labels.some((l) => model.logLikelihoods[l]?.[tok] !== void 0)
|
|
10965
|
+
);
|
|
10966
|
+
if (vocabTokens.length === 0) {
|
|
10967
|
+
return { label: "unknown", margin: 0 };
|
|
10968
|
+
}
|
|
10969
|
+
const scores = {};
|
|
10970
|
+
for (const label of model.labels) {
|
|
10971
|
+
let s = model.priors[label];
|
|
10972
|
+
const oov = Math.log(model.alpha / (model.totals[label] + model.alpha * model.vocabSize));
|
|
10973
|
+
const ll = model.logLikelihoods[label];
|
|
10974
|
+
for (const tok of vocabTokens) {
|
|
10975
|
+
s += ll[tok] ?? oov;
|
|
10976
|
+
}
|
|
10977
|
+
scores[label] = s;
|
|
10978
|
+
}
|
|
10979
|
+
const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
10980
|
+
const topLog = ranked[0][1];
|
|
10981
|
+
const secondLog = ranked[1]?.[1] ?? topLog;
|
|
10982
|
+
const margin = (topLog - secondLog) / vocabTokens.length;
|
|
10983
|
+
return { label: ranked[0][0], margin };
|
|
10984
|
+
}
|
|
10985
|
+
|
|
10986
|
+
// src/analysis/constants.ts
|
|
10987
|
+
init_cjs_shims();
|
|
10988
|
+
var STRENGTH = {
|
|
10989
|
+
unknownMaxMargin: 0.05,
|
|
10990
|
+
collectMaxMargin: 0.1,
|
|
10991
|
+
harvestMinMargin: 0.25,
|
|
10992
|
+
moderateMinMargin: 0.1,
|
|
10993
|
+
strongMinMargin: 0.25
|
|
10994
|
+
};
|
|
10995
|
+
var STRENGTH_LEVELS = ["weak", "moderate", "strong"];
|
|
10996
|
+
var FLAKY_MARGIN = 999;
|
|
10997
|
+
|
|
10998
|
+
// src/analysis/FailureClusterer.ts
|
|
10999
|
+
var MAX_TESTS_PER_CLUSTER = 20;
|
|
11000
|
+
var MAX_MESSAGE_CHARS = 200;
|
|
11001
|
+
function strengthOf(margin) {
|
|
11002
|
+
if (margin >= STRENGTH.strongMinMargin) return "strong";
|
|
11003
|
+
if (margin >= STRENGTH.moderateMinMargin) return "moderate";
|
|
11004
|
+
return "weak";
|
|
11005
|
+
}
|
|
11006
|
+
function classifyInput(f) {
|
|
11007
|
+
const head = (f.error.stack ?? "").split("\n").slice(0, 3).join(" ");
|
|
11008
|
+
return `${f.error.message} ${head}`;
|
|
11009
|
+
}
|
|
11010
|
+
function topFrame(stack) {
|
|
11011
|
+
if (!stack) return void 0;
|
|
11012
|
+
for (const line of stack.split("\n")) {
|
|
11013
|
+
const t = line.trim();
|
|
11014
|
+
if (t.startsWith("at ") && !t.replace(/\\/g, "/").includes("node_modules/playwright")) return t;
|
|
11015
|
+
}
|
|
11016
|
+
return void 0;
|
|
11017
|
+
}
|
|
11018
|
+
function representativeMessage(message) {
|
|
11019
|
+
const firstLine = message.split("\n")[0];
|
|
11020
|
+
return firstLine.length > MAX_MESSAGE_CHARS ? `${firstLine.slice(0, MAX_MESSAGE_CHARS)}\u2026` : firstLine;
|
|
11021
|
+
}
|
|
11022
|
+
function classifyOne(f, model) {
|
|
11023
|
+
if (f.retryHistory.some((r) => r.status === "passed")) {
|
|
11024
|
+
return { category: "flaky-by-retry", margin: FLAKY_MARGIN, failure: f, frame: topFrame(f.error.stack) };
|
|
11025
|
+
}
|
|
11026
|
+
const { label, margin } = classify(model, classifyInput(f));
|
|
11027
|
+
return { category: label, margin, failure: f, frame: topFrame(f.error.stack) };
|
|
11028
|
+
}
|
|
11029
|
+
function classifyAll(failures, model) {
|
|
11030
|
+
return failures.map((f) => classifyOne(f, model));
|
|
11031
|
+
}
|
|
11032
|
+
function clusterClassified(classified) {
|
|
11033
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11034
|
+
for (const c of classified) {
|
|
11035
|
+
const key = `${c.category}::${c.frame ?? "__no-stack__"}`;
|
|
11036
|
+
const bucket = groups.get(key);
|
|
11037
|
+
if (bucket) bucket.push(c);
|
|
11038
|
+
else groups.set(key, [c]);
|
|
11039
|
+
}
|
|
11040
|
+
const clusters = [];
|
|
11041
|
+
for (const members of groups.values()) {
|
|
11042
|
+
const margin = Math.max(...members.map((m) => m.margin));
|
|
11043
|
+
clusters.push({
|
|
11044
|
+
category: members[0].category,
|
|
11045
|
+
strength: strengthOf(margin),
|
|
11046
|
+
margin,
|
|
11047
|
+
count: members.length,
|
|
11048
|
+
tests: members.slice(0, MAX_TESTS_PER_CLUSTER).map((m) => m.failure.testTitle),
|
|
11049
|
+
testsTotal: members.length,
|
|
11050
|
+
representativeMessage: representativeMessage(members[0].failure.error.message),
|
|
11051
|
+
stackFrame: members[0].frame
|
|
11052
|
+
});
|
|
11053
|
+
}
|
|
11054
|
+
return clusters;
|
|
11055
|
+
}
|
|
11056
|
+
|
|
11057
|
+
// src/analysis/SummaryWriter.ts
|
|
11058
|
+
init_cjs_shims();
|
|
11059
|
+
var CATEGORY_LABEL = {
|
|
11060
|
+
assertion: "Assertion failure",
|
|
11061
|
+
timeout: "Timeout",
|
|
11062
|
+
"locator-not-found": "Element not found",
|
|
11063
|
+
network: "Network error",
|
|
11064
|
+
navigation: "Navigation error",
|
|
11065
|
+
"env-config": "Environment / config",
|
|
11066
|
+
"flaky-by-retry": "Flaky (passed on retry)",
|
|
11067
|
+
unknown: "Unclassified"
|
|
11068
|
+
};
|
|
11069
|
+
var SHORT_LABEL = {
|
|
11070
|
+
assertion: "assertion",
|
|
11071
|
+
timeout: "timeout",
|
|
11072
|
+
"locator-not-found": "element not found",
|
|
11073
|
+
network: "network",
|
|
11074
|
+
navigation: "navigation",
|
|
11075
|
+
"env-config": "env/config",
|
|
11076
|
+
"flaky-by-retry": "flaky",
|
|
11077
|
+
unknown: "unclassified"
|
|
11078
|
+
};
|
|
11079
|
+
function buildOneLiner(clusters) {
|
|
11080
|
+
if (clusters.length === 0) return "";
|
|
11081
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
11082
|
+
for (const c of clusters) {
|
|
11083
|
+
byCategory.set(c.category, (byCategory.get(c.category) ?? 0) + c.count);
|
|
11084
|
+
}
|
|
11085
|
+
const head = `${clusters.length} root ${clusters.length === 1 ? "cause" : "causes"}`;
|
|
11086
|
+
const tally = [...byCategory.entries()].sort((a, b) => b[1] - a[1]).map(([cat, count]) => `${count} ${SHORT_LABEL[cat]}`);
|
|
11087
|
+
return [head, ...tally].join(" \xB7 ");
|
|
11088
|
+
}
|
|
11089
|
+
function dominantCategory(clusters) {
|
|
11090
|
+
return clusters[0]?.category ?? "unknown";
|
|
11091
|
+
}
|
|
11092
|
+
|
|
11093
|
+
// src/analysis/UnclassifiedCollector.ts
|
|
11094
|
+
init_cjs_shims();
|
|
11095
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
11096
|
+
var import_node_os2 = __toESM(require("os"));
|
|
11097
|
+
var import_node_path2 = __toESM(require("path"));
|
|
11098
|
+
|
|
11099
|
+
// src/analysis/feedback-csv.ts
|
|
11100
|
+
init_cjs_shims();
|
|
11101
|
+
var FEEDBACK_HEADER = "category,margin,text";
|
|
11102
|
+
function parseFeedbackRows(content) {
|
|
11103
|
+
return content.trim().split("\n").slice(1).filter(Boolean).map((line) => {
|
|
11104
|
+
const parts = line.split(",");
|
|
11105
|
+
return { category: parts[0], margin: parts[1], text: parts.slice(2).join(","), raw: line };
|
|
11106
|
+
});
|
|
11107
|
+
}
|
|
11108
|
+
function feedbackIdentity(category, text) {
|
|
11109
|
+
return `${category} ${text}`;
|
|
11110
|
+
}
|
|
11111
|
+
|
|
11112
|
+
// src/analysis/UnclassifiedCollector.ts
|
|
11113
|
+
var MAX_ROWS = 500;
|
|
11114
|
+
var FILENAME = "unclassified.csv";
|
|
11115
|
+
var URI_RE = /[a-z][\w+.-]*:\/\/\S+/gi;
|
|
11116
|
+
var EMAIL_RE = /\b[\w.+-]+@[\w.-]+\.\w+\b/g;
|
|
11117
|
+
var SECRET_RE = /\b(?=[A-Za-z0-9_+/=-]*[0-9])(?=[A-Za-z0-9_+/=-]*[A-Za-z])[A-Za-z0-9_+/=-]{16,}\b/g;
|
|
11118
|
+
var IP_RE = /\b\d{1,3}(?:\.\d{1,3}){3}\b/g;
|
|
11119
|
+
var WIN_PATH_RE = /[A-Za-z]:\\[^\s'"]+/g;
|
|
11120
|
+
var UNIX_PATH_RE = /(?:\/[\w.-]+){2,}/g;
|
|
11121
|
+
function redactForStorage(raw) {
|
|
11122
|
+
const redacted = raw.replace(URI_RE, " URL ").replace(EMAIL_RE, " EMAIL ").replace(SECRET_RE, " SECRET ").replace(IP_RE, " IP ").replace(WIN_PATH_RE, " PATH ").replace(UNIX_PATH_RE, " PATH ");
|
|
11123
|
+
return tokenize(redacted).join(" ");
|
|
11124
|
+
}
|
|
11125
|
+
function shouldCollect(c) {
|
|
11126
|
+
return c.category === "unknown" || c.margin <= STRENGTH.collectMaxMargin;
|
|
11127
|
+
}
|
|
11128
|
+
function tildeify(p) {
|
|
11129
|
+
const home = import_node_os2.default.homedir();
|
|
11130
|
+
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
11131
|
+
}
|
|
11132
|
+
function collectUnclassified(classified, projectDir) {
|
|
11133
|
+
const rows = classified.filter(shouldCollect).map((c) => ({ text: redactForStorage(c.failure.error.message), category: c.category, margin: c.margin })).filter((r) => r.text.length > 0);
|
|
11134
|
+
if (rows.length === 0) return;
|
|
11135
|
+
const file = import_node_path2.default.join(projectDir, FILENAME);
|
|
11136
|
+
const firstTime = !import_node_fs2.default.existsSync(file);
|
|
11137
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
11138
|
+
if (!firstTime) {
|
|
11139
|
+
for (const r of parseFeedbackRows(import_node_fs2.default.readFileSync(file, "utf8"))) {
|
|
11140
|
+
byIdentity.set(feedbackIdentity(r.category, r.text), r.raw);
|
|
11141
|
+
}
|
|
11142
|
+
}
|
|
11143
|
+
let added = false;
|
|
11144
|
+
for (const r of rows) {
|
|
11145
|
+
const id = feedbackIdentity(r.category, r.text);
|
|
11146
|
+
byIdentity.delete(id);
|
|
11147
|
+
byIdentity.set(id, `${r.category},${r.margin},${r.text}`);
|
|
11148
|
+
added = true;
|
|
11149
|
+
}
|
|
11150
|
+
if (!added) return;
|
|
11151
|
+
const all = [...byIdentity.values()].slice(-MAX_ROWS);
|
|
11152
|
+
import_node_fs2.default.mkdirSync(projectDir, { recursive: true });
|
|
11153
|
+
import_node_fs2.default.writeFileSync(file, `${FEEDBACK_HEADER}
|
|
11154
|
+
${all.join("\n")}
|
|
11155
|
+
`, "utf8");
|
|
11156
|
+
if (firstTime) {
|
|
11157
|
+
logger.info(
|
|
11158
|
+
`Unrecognized failures collected locally at ${tildeify(file)} to improve offline classification. Local only - never sent anywhere. Disable: failureAnalysis.collectUnclassified=false`
|
|
11159
|
+
);
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
|
|
11163
|
+
// src/utils/paths.ts
|
|
11164
|
+
init_cjs_shims();
|
|
11165
|
+
var import_node_os3 = __toESM(require("os"));
|
|
11166
|
+
var import_node_path3 = __toESM(require("path"));
|
|
11167
|
+
var import_node_crypto2 = __toESM(require("crypto"));
|
|
11168
|
+
function resolveProjectDir(options, cwd) {
|
|
11169
|
+
const projectKey = import_node_crypto2.default.createHash("sha256").update(cwd + (options.outputFile ?? "")).digest("hex").slice(0, 8);
|
|
11170
|
+
return import_node_path3.default.join(import_node_os3.default.homedir(), ".reportforge", projectKey);
|
|
11171
|
+
}
|
|
11172
|
+
|
|
11173
|
+
// src/analysis/index.ts
|
|
11174
|
+
var DEFAULTS = { maxClusters: 10, minStrength: "weak", maxFailuresToAnalyse: 500 };
|
|
11175
|
+
var STRENGTH_RANK = Object.fromEntries(
|
|
11176
|
+
STRENGTH_LEVELS.map((level, i) => [level, i])
|
|
11177
|
+
);
|
|
11178
|
+
function analyseClassified(classified, totalFailures, opts = {}) {
|
|
11179
|
+
const maxClusters = opts.maxClusters ?? DEFAULTS.maxClusters;
|
|
11180
|
+
const minRank = STRENGTH_RANK[opts.minStrength ?? DEFAULTS.minStrength];
|
|
11181
|
+
const cap = opts.maxFailuresToAnalyse ?? DEFAULTS.maxFailuresToAnalyse;
|
|
11182
|
+
const clusters = clusterClassified(classified).filter((c) => STRENGTH_RANK[c.strength] >= minRank).sort((a, b) => b.count - a.count).slice(0, maxClusters);
|
|
11183
|
+
return {
|
|
11184
|
+
clusters,
|
|
11185
|
+
totalFailures,
|
|
11186
|
+
analysedCount: classified.length,
|
|
11187
|
+
truncated: totalFailures > cap,
|
|
11188
|
+
// dominantCategory is part of the AnalysisResult contract; reserved for
|
|
11189
|
+
// consumers (e.g. a future executive badge / notify summary). Not rendered
|
|
11190
|
+
// by the v1 partials.
|
|
11191
|
+
dominantCategory: dominantCategory(clusters),
|
|
11192
|
+
oneLiner: buildOneLiner(clusters)
|
|
11193
|
+
};
|
|
11194
|
+
}
|
|
11195
|
+
function templatesConsumeAnalysis(options) {
|
|
11196
|
+
if (options.templatePath && options.templatePath.length > 0) return true;
|
|
11197
|
+
const templates = Array.isArray(options.template) ? options.template : [options.template];
|
|
11198
|
+
return templates.some((t) => t === "detailed" || t === "executive");
|
|
11199
|
+
}
|
|
11200
|
+
function runFailureAnalysis(reportData, options, cwd) {
|
|
11201
|
+
const fa = options.failureAnalysis;
|
|
11202
|
+
if (reportData.stats.failed === 0 || !fa.enabled) return;
|
|
11203
|
+
const cap = fa.maxFailuresToAnalyse;
|
|
11204
|
+
const analysed = reportData.failures.slice(0, cap);
|
|
11205
|
+
const model = loadModel(fa.autoUpdateModel);
|
|
11206
|
+
const classified = classifyAll(analysed, model);
|
|
11207
|
+
if (templatesConsumeAnalysis(options)) {
|
|
11208
|
+
reportData.analysis = analyseClassified(classified, reportData.failures.length, fa);
|
|
11209
|
+
}
|
|
11210
|
+
if (fa.collectUnclassified) {
|
|
11211
|
+
collectUnclassified(classified, resolveProjectDir(options, cwd));
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
|
|
10837
11215
|
// src/collector/DataCollector.ts
|
|
10838
11216
|
init_cjs_shims();
|
|
10839
11217
|
|
|
10840
11218
|
// src/collector/SuiteWalker.ts
|
|
10841
11219
|
init_cjs_shims();
|
|
11220
|
+
|
|
11221
|
+
// src/collector/stats-utils.ts
|
|
11222
|
+
init_cjs_shims();
|
|
11223
|
+
function statsFromTests(tests) {
|
|
11224
|
+
return {
|
|
11225
|
+
total: tests.length,
|
|
11226
|
+
passed: tests.filter((t) => t.status === "passed").length,
|
|
11227
|
+
failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
|
|
11228
|
+
skipped: tests.filter((t) => t.status === "skipped").length,
|
|
11229
|
+
flaky: tests.filter((t) => t.status === "flaky").length
|
|
11230
|
+
};
|
|
11231
|
+
}
|
|
11232
|
+
function aggregateStats(parts) {
|
|
11233
|
+
return parts.reduce(
|
|
11234
|
+
(acc, s) => ({
|
|
11235
|
+
total: acc.total + s.total,
|
|
11236
|
+
passed: acc.passed + s.passed,
|
|
11237
|
+
failed: acc.failed + s.failed,
|
|
11238
|
+
skipped: acc.skipped + s.skipped,
|
|
11239
|
+
flaky: acc.flaky + s.flaky
|
|
11240
|
+
}),
|
|
11241
|
+
{ total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
|
|
11242
|
+
);
|
|
11243
|
+
}
|
|
11244
|
+
|
|
11245
|
+
// src/collector/SuiteWalker.ts
|
|
10842
11246
|
var SuiteWalker = class {
|
|
10843
11247
|
build(rootSuite, resultMap) {
|
|
10844
11248
|
return rootSuite.suites.map(
|
|
@@ -10849,15 +11253,15 @@ var SuiteWalker = class {
|
|
|
10849
11253
|
const suites = projectSuite.suites.map(
|
|
10850
11254
|
(s) => this.buildSuite(s, resultMap)
|
|
10851
11255
|
);
|
|
10852
|
-
const stats =
|
|
11256
|
+
const stats = aggregateStats(suites.map((s) => s.stats));
|
|
10853
11257
|
return { name: projectSuite.title || "default", suites, stats };
|
|
10854
11258
|
}
|
|
10855
11259
|
buildSuite(suite, resultMap) {
|
|
10856
11260
|
const childSuites = suite.suites.map((s) => this.buildSuite(s, resultMap));
|
|
10857
11261
|
const tests = suite.tests.map((t) => this.buildTest(t, resultMap));
|
|
10858
|
-
const stats =
|
|
11262
|
+
const stats = aggregateStats([
|
|
10859
11263
|
...childSuites.map((s) => s.stats),
|
|
10860
|
-
|
|
11264
|
+
statsFromTests(tests)
|
|
10861
11265
|
]);
|
|
10862
11266
|
return {
|
|
10863
11267
|
title: suite.title,
|
|
@@ -10894,27 +11298,6 @@ var SuiteWalker = class {
|
|
|
10894
11298
|
if (actual === "failed" && expected === "failed") return "passed";
|
|
10895
11299
|
return "failed";
|
|
10896
11300
|
}
|
|
10897
|
-
statsFromTests(tests) {
|
|
10898
|
-
return {
|
|
10899
|
-
total: tests.length,
|
|
10900
|
-
passed: tests.filter((t) => t.status === "passed").length,
|
|
10901
|
-
failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
|
|
10902
|
-
skipped: tests.filter((t) => t.status === "skipped").length,
|
|
10903
|
-
flaky: tests.filter((t) => t.status === "flaky").length
|
|
10904
|
-
};
|
|
10905
|
-
}
|
|
10906
|
-
aggregateStats(parts) {
|
|
10907
|
-
return parts.reduce(
|
|
10908
|
-
(acc, s) => ({
|
|
10909
|
-
total: acc.total + s.total,
|
|
10910
|
-
passed: acc.passed + s.passed,
|
|
10911
|
-
failed: acc.failed + s.failed,
|
|
10912
|
-
skipped: acc.skipped + s.skipped,
|
|
10913
|
-
flaky: acc.flaky + s.flaky
|
|
10914
|
-
}),
|
|
10915
|
-
{ total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
|
|
10916
|
-
);
|
|
10917
|
-
}
|
|
10918
11301
|
};
|
|
10919
11302
|
|
|
10920
11303
|
// src/utils/env.ts
|
|
@@ -11250,7 +11633,7 @@ var ShardMerger = class {
|
|
|
11250
11633
|
}
|
|
11251
11634
|
return Array.from(projectMap.entries()).map(([name, projectSuites]) => {
|
|
11252
11635
|
const suites = this.mergeFileSuites(projectSuites);
|
|
11253
|
-
const stats =
|
|
11636
|
+
const stats = aggregateStats(suites.map((s) => s.stats));
|
|
11254
11637
|
return { name, suites, stats };
|
|
11255
11638
|
});
|
|
11256
11639
|
}
|
|
@@ -11271,9 +11654,9 @@ var ShardMerger = class {
|
|
|
11271
11654
|
const suitePath = [ref.title ?? ""];
|
|
11272
11655
|
const tests = allSpecs.map((spec) => this.specToTestNode(spec, projectName, suitePath));
|
|
11273
11656
|
const childSuiteNodes = this.buildDescribeSuites(allChildSuites, projectName, suitePath);
|
|
11274
|
-
const stats =
|
|
11657
|
+
const stats = aggregateStats([
|
|
11275
11658
|
...childSuiteNodes.map((s) => s.stats),
|
|
11276
|
-
|
|
11659
|
+
statsFromTests(tests)
|
|
11277
11660
|
]);
|
|
11278
11661
|
return {
|
|
11279
11662
|
title: ref.title ?? ref.file ?? "unknown",
|
|
@@ -11290,9 +11673,9 @@ var ShardMerger = class {
|
|
|
11290
11673
|
const suitePath = [...parentPath, suite.title];
|
|
11291
11674
|
const tests = suite.specs.map((spec) => this.specToTestNode(spec, projectName, suitePath));
|
|
11292
11675
|
const childSuites = this.buildDescribeSuites(suite.suites ?? [], projectName, suitePath);
|
|
11293
|
-
const stats =
|
|
11676
|
+
const stats = aggregateStats([
|
|
11294
11677
|
...childSuites.map((s) => s.stats),
|
|
11295
|
-
|
|
11678
|
+
statsFromTests(tests)
|
|
11296
11679
|
]);
|
|
11297
11680
|
return {
|
|
11298
11681
|
title: suite.title,
|
|
@@ -11447,27 +11830,6 @@ var ShardMerger = class {
|
|
|
11447
11830
|
suiteResults
|
|
11448
11831
|
};
|
|
11449
11832
|
}
|
|
11450
|
-
statsFromTests(tests) {
|
|
11451
|
-
return {
|
|
11452
|
-
total: tests.length,
|
|
11453
|
-
passed: tests.filter((t) => t.status === "passed").length,
|
|
11454
|
-
failed: tests.filter((t) => t.status === "failed" || t.status === "timedOut").length,
|
|
11455
|
-
skipped: tests.filter((t) => t.status === "skipped").length,
|
|
11456
|
-
flaky: tests.filter((t) => t.status === "flaky").length
|
|
11457
|
-
};
|
|
11458
|
-
}
|
|
11459
|
-
aggregateStats(parts) {
|
|
11460
|
-
return parts.reduce(
|
|
11461
|
-
(acc, s) => ({
|
|
11462
|
-
total: acc.total + s.total,
|
|
11463
|
-
passed: acc.passed + s.passed,
|
|
11464
|
-
failed: acc.failed + s.failed,
|
|
11465
|
-
skipped: acc.skipped + s.skipped,
|
|
11466
|
-
flaky: acc.flaky + s.flaky
|
|
11467
|
-
}),
|
|
11468
|
-
{ total: 0, passed: 0, failed: 0, skipped: 0, flaky: 0 }
|
|
11469
|
-
);
|
|
11470
|
-
}
|
|
11471
11833
|
};
|
|
11472
11834
|
|
|
11473
11835
|
// src/pdf/PdfGenerator.ts
|
|
@@ -11718,6 +12080,10 @@ var TemplateEngine = class {
|
|
|
11718
12080
|
);
|
|
11719
12081
|
this.handlebars.registerHelper("lt", (a, b) => a < b);
|
|
11720
12082
|
this.handlebars.registerHelper("or", (a, b) => Boolean(a) || Boolean(b));
|
|
12083
|
+
this.handlebars.registerHelper("categoryLabel", (cat) => CATEGORY_LABEL[cat] ?? cat);
|
|
12084
|
+
this.handlebars.registerHelper("strengthLabel", (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : "");
|
|
12085
|
+
this.handlebars.registerHelper("pluralise", (n, one, many) => n === 1 ? one : many);
|
|
12086
|
+
this.handlebars.registerHelper("subtract", (a, b) => (a ?? 0) - (b ?? 0));
|
|
11721
12087
|
this.handlebars.registerHelper("statusBadge", (status) => {
|
|
11722
12088
|
const map = {
|
|
11723
12089
|
passed: "badge--passed",
|
|
@@ -12137,10 +12503,31 @@ var PdfGenerator = class {
|
|
|
12137
12503
|
*
|
|
12138
12504
|
* @param data - Assembled `ReportData` (projects, stats, failures, charts).
|
|
12139
12505
|
* @param options - Validated reporter options.
|
|
12140
|
-
* @param
|
|
12506
|
+
* @param licenseInfo - Verified license. JWT must be present and valid for
|
|
12507
|
+
* non-demo sources; `source: 'demo'` is the only allowed JWT-free path.
|
|
12141
12508
|
* @returns Absolute paths of written PDF files.
|
|
12142
12509
|
*/
|
|
12143
|
-
async generateAll(data, options,
|
|
12510
|
+
async generateAll(data, options, licenseInfo) {
|
|
12511
|
+
if (false) {
|
|
12512
|
+
if (licenseInfo.expiry <= /* @__PURE__ */ new Date()) {
|
|
12513
|
+
throw new Error("[reportforge] Demo license expired \u2014 PDF generation skipped.");
|
|
12514
|
+
}
|
|
12515
|
+
} else {
|
|
12516
|
+
if (!licenseInfo.jwt) {
|
|
12517
|
+
throw new Error("[reportforge] License JWT missing \u2014 PDF generation skipped.");
|
|
12518
|
+
}
|
|
12519
|
+
if (!LICENSE_PUBLIC_KEY_PEM) {
|
|
12520
|
+
throw new Error("[reportforge] LICENSE_PUBLIC_KEY_PEM missing \u2014 cannot verify license JWT.");
|
|
12521
|
+
}
|
|
12522
|
+
try {
|
|
12523
|
+
const payload = new JwtVerifier(LICENSE_PUBLIC_KEY_PEM).verify(licenseInfo.jwt);
|
|
12524
|
+
if (payload.exp * 1e3 <= Date.now()) {
|
|
12525
|
+
throw new Error("JWT expired");
|
|
12526
|
+
}
|
|
12527
|
+
} catch (err) {
|
|
12528
|
+
throw new Error(`[reportforge] License JWT verification failed: ${err.message}`);
|
|
12529
|
+
}
|
|
12530
|
+
}
|
|
12144
12531
|
const sources = this.resolveTemplateSources(options);
|
|
12145
12532
|
const isMulti = sources.length > 1;
|
|
12146
12533
|
for (const source of sources) {
|
|
@@ -12297,8 +12684,6 @@ function fmtMb(bytes) {
|
|
|
12297
12684
|
|
|
12298
12685
|
// src/reporter.ts
|
|
12299
12686
|
var import_path12 = __toESM(require("path"));
|
|
12300
|
-
var import_crypto7 = __toESM(require("crypto"));
|
|
12301
|
-
var import_os6 = __toESM(require("os"));
|
|
12302
12687
|
|
|
12303
12688
|
// src/history/HistoryManager.ts
|
|
12304
12689
|
init_cjs_shims();
|
|
@@ -12670,11 +13055,13 @@ var PdfReporter = class {
|
|
|
12670
13055
|
this.dataCollector = new DataCollector();
|
|
12671
13056
|
this.pdfGenerator = new PdfGenerator();
|
|
12672
13057
|
this.options = parseOptions(rawOptions);
|
|
12673
|
-
const version = true ? "0.
|
|
13058
|
+
const version = true ? "0.4.0" : "0.x";
|
|
12674
13059
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
12675
13060
|
this.licenseClient = new LicenseClient({
|
|
12676
13061
|
licenseKey: this.options.licenseKey,
|
|
12677
|
-
serverUrl: this.options.serverUrl
|
|
13062
|
+
serverUrl: this.options.serverUrl,
|
|
13063
|
+
autoUpdateModel: this.options.failureAnalysis.autoUpdateModel,
|
|
13064
|
+
currentModelVersion: MODEL.version
|
|
12678
13065
|
});
|
|
12679
13066
|
}
|
|
12680
13067
|
onBegin(config, suite) {
|
|
@@ -12696,6 +13083,7 @@ var PdfReporter = class {
|
|
|
12696
13083
|
}
|
|
12697
13084
|
}
|
|
12698
13085
|
const reportData = this._buildReportData(collected, licenseInfo);
|
|
13086
|
+
runFailureAnalysis(reportData, this.options, this.cwd);
|
|
12699
13087
|
let pdfPaths = [];
|
|
12700
13088
|
try {
|
|
12701
13089
|
pdfPaths = await this.pdfGenerator.generateAll(reportData, this.options, licenseInfo);
|
|
@@ -12854,8 +13242,7 @@ function resolveHistoryPath(options, cwd) {
|
|
|
12854
13242
|
if (options.historyFile) {
|
|
12855
13243
|
return import_path12.default.isAbsolute(options.historyFile) ? options.historyFile : import_path12.default.resolve(cwd, options.historyFile);
|
|
12856
13244
|
}
|
|
12857
|
-
|
|
12858
|
-
return import_path12.default.join(import_os6.default.homedir(), ".reportforge", projectKey, "history.json");
|
|
13245
|
+
return import_path12.default.join(resolveProjectDir(options, cwd), "history.json");
|
|
12859
13246
|
}
|
|
12860
13247
|
function flattenSuiteNodes(suites) {
|
|
12861
13248
|
return suites.flatMap((s) => [s, ...flattenSuiteNodes(s.suites)]);
|