@reportforge/playwright-pdf 0.3.1 → 0.4.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/CHANGELOG.md +8 -0
- package/README.md +3 -0
- package/dist/cli/export-feedback.js +87 -0
- package/dist/demo/cli.js +413 -59
- package/dist/index.d.mts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +461 -68
- package/dist/index.mjs +538 -145
- 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 +8 -3
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,6 +10837,9 @@ 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",
|
|
@@ -10772,13 +10876,15 @@ var LicenseClient = class {
|
|
|
10772
10876
|
return this.post("/api/license/activate", {
|
|
10773
10877
|
licenseKey,
|
|
10774
10878
|
fingerprint: this.fingerprint.hash,
|
|
10775
|
-
label: this.fingerprint.label
|
|
10879
|
+
label: this.fingerprint.label,
|
|
10880
|
+
modelVersion: this.modelVersionForServer
|
|
10776
10881
|
});
|
|
10777
10882
|
}
|
|
10778
10883
|
async refresh(jwt) {
|
|
10779
10884
|
return this.post("/api/license/refresh", {
|
|
10780
10885
|
jwt,
|
|
10781
|
-
label: this.fingerprint.label
|
|
10886
|
+
label: this.fingerprint.label,
|
|
10887
|
+
modelVersion: this.modelVersionForServer
|
|
10782
10888
|
});
|
|
10783
10889
|
}
|
|
10784
10890
|
async post(route, body) {
|
|
@@ -10836,9 +10942,292 @@ function normalizeUrl(url) {
|
|
|
10836
10942
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
10837
10943
|
}
|
|
10838
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
|
+
|
|
10839
11215
|
// src/collector/DataCollector.ts
|
|
10840
11216
|
init_cjs_shims();
|
|
10841
11217
|
|
|
11218
|
+
// src/utils/strip-ansi.ts
|
|
11219
|
+
init_cjs_shims();
|
|
11220
|
+
var ANSI_PATTERN = new RegExp(
|
|
11221
|
+
[
|
|
11222
|
+
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
|
|
11223
|
+
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"
|
|
11224
|
+
].join("|"),
|
|
11225
|
+
"g"
|
|
11226
|
+
);
|
|
11227
|
+
function stripAnsi(input) {
|
|
11228
|
+
return input.replace(ANSI_PATTERN, "");
|
|
11229
|
+
}
|
|
11230
|
+
|
|
10842
11231
|
// src/collector/SuiteWalker.ts
|
|
10843
11232
|
init_cjs_shims();
|
|
10844
11233
|
|
|
@@ -11073,8 +11462,8 @@ var DataCollector = class {
|
|
|
11073
11462
|
testTitle: test.title,
|
|
11074
11463
|
suitePath,
|
|
11075
11464
|
error: {
|
|
11076
|
-
message: error.message ?? String(error),
|
|
11077
|
-
stack: error.stack
|
|
11465
|
+
message: stripAnsi(error.message ?? String(error)),
|
|
11466
|
+
stack: error.stack ? stripAnsi(error.stack) : void 0
|
|
11078
11467
|
},
|
|
11079
11468
|
screenshotPath: screenshotAttachment?.path,
|
|
11080
11469
|
screenshotBuffer: screenshotAttachment?.body,
|
|
@@ -11396,8 +11785,8 @@ var ShardMerger = class {
|
|
|
11396
11785
|
testTitle: spec.title,
|
|
11397
11786
|
suitePath,
|
|
11398
11787
|
error: {
|
|
11399
|
-
message: error?.message ?? "Test failed",
|
|
11400
|
-
stack: error?.stack
|
|
11788
|
+
message: stripAnsi(error?.message ?? "Test failed"),
|
|
11789
|
+
stack: error?.stack ? stripAnsi(error.stack) : void 0
|
|
11401
11790
|
},
|
|
11402
11791
|
screenshotPath: lastResult?.attachments.find(
|
|
11403
11792
|
(a) => a.name.toLowerCase().includes("screenshot") && a.contentType.startsWith("image/")
|
|
@@ -11704,6 +12093,10 @@ var TemplateEngine = class {
|
|
|
11704
12093
|
);
|
|
11705
12094
|
this.handlebars.registerHelper("lt", (a, b) => a < b);
|
|
11706
12095
|
this.handlebars.registerHelper("or", (a, b) => Boolean(a) || Boolean(b));
|
|
12096
|
+
this.handlebars.registerHelper("categoryLabel", (cat) => CATEGORY_LABEL[cat] ?? cat);
|
|
12097
|
+
this.handlebars.registerHelper("strengthLabel", (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : "");
|
|
12098
|
+
this.handlebars.registerHelper("pluralise", (n, one, many) => n === 1 ? one : many);
|
|
12099
|
+
this.handlebars.registerHelper("subtract", (a, b) => (a ?? 0) - (b ?? 0));
|
|
11707
12100
|
this.handlebars.registerHelper("statusBadge", (status) => {
|
|
11708
12101
|
const map = {
|
|
11709
12102
|
passed: "badge--passed",
|
|
@@ -12304,8 +12697,6 @@ function fmtMb(bytes) {
|
|
|
12304
12697
|
|
|
12305
12698
|
// src/reporter.ts
|
|
12306
12699
|
var import_path12 = __toESM(require("path"));
|
|
12307
|
-
var import_crypto7 = __toESM(require("crypto"));
|
|
12308
|
-
var import_os6 = __toESM(require("os"));
|
|
12309
12700
|
|
|
12310
12701
|
// src/history/HistoryManager.ts
|
|
12311
12702
|
init_cjs_shims();
|
|
@@ -12677,11 +13068,13 @@ var PdfReporter = class {
|
|
|
12677
13068
|
this.dataCollector = new DataCollector();
|
|
12678
13069
|
this.pdfGenerator = new PdfGenerator();
|
|
12679
13070
|
this.options = parseOptions(rawOptions);
|
|
12680
|
-
const version = true ? "0.
|
|
13071
|
+
const version = true ? "0.4.1" : "0.x";
|
|
12681
13072
|
logger.info(`@reportforge/playwright-pdf v${version} initialised`);
|
|
12682
13073
|
this.licenseClient = new LicenseClient({
|
|
12683
13074
|
licenseKey: this.options.licenseKey,
|
|
12684
|
-
serverUrl: this.options.serverUrl
|
|
13075
|
+
serverUrl: this.options.serverUrl,
|
|
13076
|
+
autoUpdateModel: this.options.failureAnalysis.autoUpdateModel,
|
|
13077
|
+
currentModelVersion: MODEL.version
|
|
12685
13078
|
});
|
|
12686
13079
|
}
|
|
12687
13080
|
onBegin(config, suite) {
|
|
@@ -12703,6 +13096,7 @@ var PdfReporter = class {
|
|
|
12703
13096
|
}
|
|
12704
13097
|
}
|
|
12705
13098
|
const reportData = this._buildReportData(collected, licenseInfo);
|
|
13099
|
+
runFailureAnalysis(reportData, this.options, this.cwd);
|
|
12706
13100
|
let pdfPaths = [];
|
|
12707
13101
|
try {
|
|
12708
13102
|
pdfPaths = await this.pdfGenerator.generateAll(reportData, this.options, licenseInfo);
|
|
@@ -12861,8 +13255,7 @@ function resolveHistoryPath(options, cwd) {
|
|
|
12861
13255
|
if (options.historyFile) {
|
|
12862
13256
|
return import_path12.default.isAbsolute(options.historyFile) ? options.historyFile : import_path12.default.resolve(cwd, options.historyFile);
|
|
12863
13257
|
}
|
|
12864
|
-
|
|
12865
|
-
return import_path12.default.join(import_os6.default.homedir(), ".reportforge", projectKey, "history.json");
|
|
13258
|
+
return import_path12.default.join(resolveProjectDir(options, cwd), "history.json");
|
|
12866
13259
|
}
|
|
12867
13260
|
function flattenSuiteNodes(suites) {
|
|
12868
13261
|
return suites.flatMap((s) => [s, ...flattenSuiteNodes(s.suites)]);
|