@reportforge/playwright-pdf 0.19.1 → 0.20.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 +23 -0
- package/README.md +3 -2
- package/dist/cli/index.js +1004 -175
- package/dist/index.d.mts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +299 -149
- package/dist/index.mjs +374 -224
- package/package.json +1 -1
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(path16) {
|
|
1225
|
+
return /^\.|this\b/.test(path16.original);
|
|
1226
1226
|
},
|
|
1227
1227
|
// an ID is simple if it only has one part, and that part is not
|
|
1228
1228
|
// `..` or `this`.
|
|
1229
|
-
simpleId: function simpleId(
|
|
1230
|
-
return
|
|
1229
|
+
simpleId: function simpleId(path16) {
|
|
1230
|
+
return path16.parts.length === 1 && !AST.helpers.scopedId(path16) && !path16.depth;
|
|
1231
1231
|
}
|
|
1232
1232
|
}
|
|
1233
1233
|
};
|
|
@@ -2301,12 +2301,12 @@ var require_helpers2 = __commonJS({
|
|
|
2301
2301
|
loc
|
|
2302
2302
|
};
|
|
2303
2303
|
}
|
|
2304
|
-
function prepareMustache(
|
|
2304
|
+
function prepareMustache(path16, params, hash, open, strip, locInfo) {
|
|
2305
2305
|
var escapeFlag = open.charAt(3) || open.charAt(2), escaped = escapeFlag !== "{" && escapeFlag !== "&";
|
|
2306
2306
|
var decorator = /\*/.test(open);
|
|
2307
2307
|
return {
|
|
2308
2308
|
type: decorator ? "Decorator" : "MustacheStatement",
|
|
2309
|
-
path:
|
|
2309
|
+
path: path16,
|
|
2310
2310
|
params,
|
|
2311
2311
|
hash,
|
|
2312
2312
|
escaped,
|
|
@@ -2626,9 +2626,9 @@ var require_compiler = __commonJS({
|
|
|
2626
2626
|
},
|
|
2627
2627
|
DecoratorBlock: function DecoratorBlock(decorator) {
|
|
2628
2628
|
var program = decorator.program && this.compileProgram(decorator.program);
|
|
2629
|
-
var params = this.setupFullMustacheParams(decorator, program, void 0),
|
|
2629
|
+
var params = this.setupFullMustacheParams(decorator, program, void 0), path16 = decorator.path;
|
|
2630
2630
|
this.useDecorators = true;
|
|
2631
|
-
this.opcode("registerDecorator", params.length,
|
|
2631
|
+
this.opcode("registerDecorator", params.length, path16.original);
|
|
2632
2632
|
},
|
|
2633
2633
|
PartialStatement: function PartialStatement(partial) {
|
|
2634
2634
|
this.usePartial = true;
|
|
@@ -2692,46 +2692,46 @@ var require_compiler = __commonJS({
|
|
|
2692
2692
|
}
|
|
2693
2693
|
},
|
|
2694
2694
|
ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) {
|
|
2695
|
-
var
|
|
2696
|
-
this.opcode("getContext",
|
|
2695
|
+
var path16 = sexpr.path, name = path16.parts[0], isBlock = program != null || inverse != null;
|
|
2696
|
+
this.opcode("getContext", path16.depth);
|
|
2697
2697
|
this.opcode("pushProgram", program);
|
|
2698
2698
|
this.opcode("pushProgram", inverse);
|
|
2699
|
-
|
|
2700
|
-
this.accept(
|
|
2699
|
+
path16.strict = true;
|
|
2700
|
+
this.accept(path16);
|
|
2701
2701
|
this.opcode("invokeAmbiguous", name, isBlock);
|
|
2702
2702
|
},
|
|
2703
2703
|
simpleSexpr: function simpleSexpr(sexpr) {
|
|
2704
|
-
var
|
|
2705
|
-
|
|
2706
|
-
this.accept(
|
|
2704
|
+
var path16 = sexpr.path;
|
|
2705
|
+
path16.strict = true;
|
|
2706
|
+
this.accept(path16);
|
|
2707
2707
|
this.opcode("resolvePossibleLambda");
|
|
2708
2708
|
},
|
|
2709
2709
|
helperSexpr: function helperSexpr(sexpr, program, inverse) {
|
|
2710
|
-
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
|
2710
|
+
var params = this.setupFullMustacheParams(sexpr, program, inverse), path16 = sexpr.path, name = path16.parts[0];
|
|
2711
2711
|
if (this.options.knownHelpers[name]) {
|
|
2712
2712
|
this.opcode("invokeKnownHelper", params.length, name);
|
|
2713
2713
|
} else if (this.options.knownHelpersOnly) {
|
|
2714
2714
|
throw new _exception2["default"]("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
|
2715
2715
|
} else {
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
this.accept(
|
|
2719
|
-
this.opcode("invokeHelper", params.length,
|
|
2716
|
+
path16.strict = true;
|
|
2717
|
+
path16.falsy = true;
|
|
2718
|
+
this.accept(path16);
|
|
2719
|
+
this.opcode("invokeHelper", params.length, path16.original, _ast2["default"].helpers.simpleId(path16));
|
|
2720
2720
|
}
|
|
2721
2721
|
},
|
|
2722
|
-
PathExpression: function PathExpression(
|
|
2723
|
-
this.addDepth(
|
|
2724
|
-
this.opcode("getContext",
|
|
2725
|
-
var name =
|
|
2722
|
+
PathExpression: function PathExpression(path16) {
|
|
2723
|
+
this.addDepth(path16.depth);
|
|
2724
|
+
this.opcode("getContext", path16.depth);
|
|
2725
|
+
var name = path16.parts[0], scoped = _ast2["default"].helpers.scopedId(path16), blockParamId = !path16.depth && !scoped && this.blockParamIndex(name);
|
|
2726
2726
|
if (blockParamId) {
|
|
2727
|
-
this.opcode("lookupBlockParam", blockParamId,
|
|
2727
|
+
this.opcode("lookupBlockParam", blockParamId, path16.parts);
|
|
2728
2728
|
} else if (!name) {
|
|
2729
2729
|
this.opcode("pushContext");
|
|
2730
|
-
} else if (
|
|
2730
|
+
} else if (path16.data) {
|
|
2731
2731
|
this.options.data = true;
|
|
2732
|
-
this.opcode("lookupData",
|
|
2732
|
+
this.opcode("lookupData", path16.depth, path16.parts, path16.strict);
|
|
2733
2733
|
} else {
|
|
2734
|
-
this.opcode("lookupOnContext",
|
|
2734
|
+
this.opcode("lookupOnContext", path16.parts, path16.falsy, path16.strict, scoped);
|
|
2735
2735
|
}
|
|
2736
2736
|
},
|
|
2737
2737
|
StringLiteral: function StringLiteral(string) {
|
|
@@ -3087,16 +3087,16 @@ var require_util = __commonJS({
|
|
|
3087
3087
|
}
|
|
3088
3088
|
exports2.urlGenerate = urlGenerate;
|
|
3089
3089
|
function normalize(aPath) {
|
|
3090
|
-
var
|
|
3090
|
+
var path16 = aPath;
|
|
3091
3091
|
var url = urlParse(aPath);
|
|
3092
3092
|
if (url) {
|
|
3093
3093
|
if (!url.path) {
|
|
3094
3094
|
return aPath;
|
|
3095
3095
|
}
|
|
3096
|
-
|
|
3096
|
+
path16 = url.path;
|
|
3097
3097
|
}
|
|
3098
|
-
var isAbsolute = exports2.isAbsolute(
|
|
3099
|
-
var parts =
|
|
3098
|
+
var isAbsolute = exports2.isAbsolute(path16);
|
|
3099
|
+
var parts = path16.split(/\/+/);
|
|
3100
3100
|
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
|
3101
3101
|
part = parts[i];
|
|
3102
3102
|
if (part === ".") {
|
|
@@ -3113,15 +3113,15 @@ var require_util = __commonJS({
|
|
|
3113
3113
|
}
|
|
3114
3114
|
}
|
|
3115
3115
|
}
|
|
3116
|
-
|
|
3117
|
-
if (
|
|
3118
|
-
|
|
3116
|
+
path16 = parts.join("/");
|
|
3117
|
+
if (path16 === "") {
|
|
3118
|
+
path16 = isAbsolute ? "/" : ".";
|
|
3119
3119
|
}
|
|
3120
3120
|
if (url) {
|
|
3121
|
-
url.path =
|
|
3121
|
+
url.path = path16;
|
|
3122
3122
|
return urlGenerate(url);
|
|
3123
3123
|
}
|
|
3124
|
-
return
|
|
3124
|
+
return path16;
|
|
3125
3125
|
}
|
|
3126
3126
|
exports2.normalize = normalize;
|
|
3127
3127
|
function join(aRoot, aPath) {
|
|
@@ -5924,8 +5924,8 @@ var require_printer = __commonJS({
|
|
|
5924
5924
|
return this.accept(sexpr.path) + " " + params + hash;
|
|
5925
5925
|
};
|
|
5926
5926
|
PrintVisitor.prototype.PathExpression = function(id) {
|
|
5927
|
-
var
|
|
5928
|
-
return (id.data ? "@" : "") + "PATH:" +
|
|
5927
|
+
var path16 = id.parts.join("/");
|
|
5928
|
+
return (id.data ? "@" : "") + "PATH:" + path16;
|
|
5929
5929
|
};
|
|
5930
5930
|
PrintVisitor.prototype.StringLiteral = function(string) {
|
|
5931
5931
|
return '"' + string.value + '"';
|
|
@@ -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 fs16 = require("fs");
|
|
5970
|
+
var templateString = fs16.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 fs16 = 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
|
+
fs16.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 = fs16.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 path16 = require("path");
|
|
6049
6049
|
var { canAccess } = require_util2();
|
|
6050
6050
|
function win32() {
|
|
6051
6051
|
const installations = [];
|
|
@@ -6059,7 +6059,7 @@ var require_win32 = __commonJS({
|
|
|
6059
6059
|
const prefixes = [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"]];
|
|
6060
6060
|
prefixes.forEach((prefix) => suffixes.forEach((suffix) => {
|
|
6061
6061
|
if (prefix) {
|
|
6062
|
-
const chromePath =
|
|
6062
|
+
const chromePath = path16.join(prefix, suffix);
|
|
6063
6063
|
if (canAccess(chromePath)) {
|
|
6064
6064
|
installations.push(chromePath);
|
|
6065
6065
|
}
|
|
@@ -6077,7 +6077,7 @@ var require_darwin = __commonJS({
|
|
|
6077
6077
|
"use strict";
|
|
6078
6078
|
init_cjs_shims();
|
|
6079
6079
|
var { execSync: execSync3 } = require("child_process");
|
|
6080
|
-
var
|
|
6080
|
+
var path16 = require("path");
|
|
6081
6081
|
var { canAccess, newLineRegex, sort } = require_util2();
|
|
6082
6082
|
function darwin() {
|
|
6083
6083
|
const suffixes = ["/Contents/MacOS/Google Chrome Canary", "/Contents/MacOS/Google Chrome", "/Contents/MacOS/Chromium"];
|
|
@@ -6087,7 +6087,7 @@ var require_darwin = __commonJS({
|
|
|
6087
6087
|
${LSREGISTER} -dump | grep -E -i -o '/.+(google chrome( canary)?|chromium)\\.app(\\s|$)' | grep -E -v 'Caches|TimeMachine|Temporary|/Volumes|\\.Trash'
|
|
6088
6088
|
`).toString().split(newLineRegex).forEach((inst) => {
|
|
6089
6089
|
suffixes.forEach((suffix) => {
|
|
6090
|
-
const execPath =
|
|
6090
|
+
const execPath = path16.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 path16 = require("path");
|
|
6120
|
+
var fs16 = 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
|
+
path16.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 (fs16.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: path16, errorMaps, issueData } = params;
|
|
6753
|
+
const fullPath = [...path16, ...issueData.path || []];
|
|
6754
6754
|
const fullIssue = {
|
|
6755
6755
|
...issueData,
|
|
6756
6756
|
path: fullPath
|
|
@@ -6870,11 +6870,11 @@ var errorUtil;
|
|
|
6870
6870
|
|
|
6871
6871
|
// node_modules/zod/v3/types.js
|
|
6872
6872
|
var ParseInputLazyPath = class {
|
|
6873
|
-
constructor(parent, value,
|
|
6873
|
+
constructor(parent, value, path16, key) {
|
|
6874
6874
|
this._cachedPath = [];
|
|
6875
6875
|
this.parent = parent;
|
|
6876
6876
|
this.data = value;
|
|
6877
|
-
this._path =
|
|
6877
|
+
this._path = path16;
|
|
6878
6878
|
this._key = key;
|
|
6879
6879
|
}
|
|
6880
6880
|
get path() {
|
|
@@ -10481,9 +10481,17 @@ var failureAnalysisConfigSchema = external_exports.object({
|
|
|
10481
10481
|
// Qualitative floor (not a numeric % — consistent with the match-strength design).
|
|
10482
10482
|
minStrength: external_exports.enum(["weak", "moderate", "strong"]).default("weak"),
|
|
10483
10483
|
maxFailuresToAnalyse: external_exports.number().int().min(1).default(500),
|
|
10484
|
-
// Consumed by the Plan 3 feedback collector; inert until then.
|
|
10485
10484
|
collectUnclassified: external_exports.boolean().default(true),
|
|
10486
|
-
|
|
10485
|
+
// What the feedback collector stores (while collectUnclassified is on):
|
|
10486
|
+
// 'blind-spots' = unknown/low-confidence only; 'all' = every model-scored
|
|
10487
|
+
// failure, so wrong-but-confident verdicts can be corrected by labeling.
|
|
10488
|
+
collectScope: external_exports.enum(["blind-spots", "all"]).default("blind-spots"),
|
|
10489
|
+
autoUpdateModel: external_exports.boolean().default(true),
|
|
10490
|
+
// Use the on-device trained model (when present and its evaluation gate passed).
|
|
10491
|
+
localModel: external_exports.boolean().default(true),
|
|
10492
|
+
// Override the local model file location (default ~/.reportforge/model-local.json).
|
|
10493
|
+
// Point at a repo-committed file to share a team-trained model with ephemeral CI.
|
|
10494
|
+
localModelPath: external_exports.string().optional()
|
|
10487
10495
|
}).default({});
|
|
10488
10496
|
var captureConfigSchema = external_exports.object({
|
|
10489
10497
|
steps: external_exports.boolean().default(false),
|
|
@@ -10851,6 +10859,47 @@ var import_node_crypto = require("crypto");
|
|
|
10851
10859
|
init_cjs_shims();
|
|
10852
10860
|
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 };
|
|
10853
10861
|
|
|
10862
|
+
// src/analysis/NaiveBayesClassifier.ts
|
|
10863
|
+
init_cjs_shims();
|
|
10864
|
+
|
|
10865
|
+
// src/analysis/tokenize.ts
|
|
10866
|
+
init_cjs_shims();
|
|
10867
|
+
function tokenize(text) {
|
|
10868
|
+
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);
|
|
10869
|
+
}
|
|
10870
|
+
|
|
10871
|
+
// src/analysis/NaiveBayesClassifier.ts
|
|
10872
|
+
function classify(model, text) {
|
|
10873
|
+
return classifyTokens(model, tokenize(text));
|
|
10874
|
+
}
|
|
10875
|
+
function classifyTokens(model, tokens) {
|
|
10876
|
+
const vocabTokens = tokens.filter(
|
|
10877
|
+
(tok) => tok.length > 0 && model.labels.some((l) => model.logLikelihoods[l]?.[tok] !== void 0)
|
|
10878
|
+
);
|
|
10879
|
+
if (vocabTokens.length === 0) {
|
|
10880
|
+
return { label: "unknown", margin: 0 };
|
|
10881
|
+
}
|
|
10882
|
+
const scores = {};
|
|
10883
|
+
for (const label of model.labels) {
|
|
10884
|
+
let s = model.priors[label];
|
|
10885
|
+
const oov = Math.log(model.alpha / (model.totals[label] + model.alpha * model.vocabSize));
|
|
10886
|
+
const ll = model.logLikelihoods[label];
|
|
10887
|
+
for (const tok of vocabTokens) {
|
|
10888
|
+
s += ll[tok] ?? oov;
|
|
10889
|
+
}
|
|
10890
|
+
scores[label] = s;
|
|
10891
|
+
}
|
|
10892
|
+
const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
10893
|
+
const topLog = ranked[0][1];
|
|
10894
|
+
const secondLog = ranked[1]?.[1] ?? topLog;
|
|
10895
|
+
const margin = (topLog - secondLog) / vocabTokens.length;
|
|
10896
|
+
return { label: ranked[0][0], margin };
|
|
10897
|
+
}
|
|
10898
|
+
function isValidNaiveBayesModel(m) {
|
|
10899
|
+
const x = m;
|
|
10900
|
+
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";
|
|
10901
|
+
}
|
|
10902
|
+
|
|
10854
10903
|
// src/analysis/ModelStore.ts
|
|
10855
10904
|
var MODEL_SIG_DOMAIN = "reportforge-model-sig:v1\n";
|
|
10856
10905
|
var testDeps = null;
|
|
@@ -10874,10 +10923,7 @@ function verifyModelSignature(modelJson, signatureB64url) {
|
|
|
10874
10923
|
return false;
|
|
10875
10924
|
}
|
|
10876
10925
|
}
|
|
10877
|
-
|
|
10878
|
-
const x = m;
|
|
10879
|
-
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";
|
|
10880
|
-
}
|
|
10926
|
+
var schemaValid = isValidNaiveBayesModel;
|
|
10881
10927
|
function readCache2() {
|
|
10882
10928
|
try {
|
|
10883
10929
|
const raw = import_node_fs.default.readFileSync(cacheFile(), "utf8");
|
|
@@ -11194,42 +11240,61 @@ function maybePrintUpdateNotice(licenseInfo, currentVersion) {
|
|
|
11194
11240
|
// src/analysis/index.ts
|
|
11195
11241
|
init_cjs_shims();
|
|
11196
11242
|
|
|
11197
|
-
// src/analysis/
|
|
11243
|
+
// src/analysis/LocalModelStore.ts
|
|
11198
11244
|
init_cjs_shims();
|
|
11245
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
11246
|
+
var import_node_os2 = __toESM(require("os"));
|
|
11247
|
+
var import_node_path2 = __toESM(require("path"));
|
|
11248
|
+
var testDeps2 = null;
|
|
11249
|
+
function defaultLocalModelPath() {
|
|
11250
|
+
return testDeps2?.file ?? import_node_path2.default.join(import_node_os2.default.homedir(), ".reportforge", "model-local.json");
|
|
11251
|
+
}
|
|
11252
|
+
function loadLocalModel(overridePath) {
|
|
11253
|
+
const file = overridePath ?? defaultLocalModelPath();
|
|
11254
|
+
const explicit = overridePath !== void 0;
|
|
11255
|
+
const reject = (why) => {
|
|
11256
|
+
const msg = `local model at ${file} not used: ${why}`;
|
|
11257
|
+
if (explicit) logger.warn(msg);
|
|
11258
|
+
else logger.debug(msg);
|
|
11259
|
+
return null;
|
|
11260
|
+
};
|
|
11261
|
+
let parsed;
|
|
11262
|
+
try {
|
|
11263
|
+
parsed = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
|
|
11264
|
+
} catch (e) {
|
|
11265
|
+
return explicit ? reject(`unreadable (${e.message})`) : null;
|
|
11266
|
+
}
|
|
11267
|
+
if (parsed.formatVersion !== 1) return reject(`unsupported formatVersion ${String(parsed.formatVersion)}`);
|
|
11268
|
+
if (!isValidNaiveBayesModel(parsed.model)) return reject("embedded model failed schema validation");
|
|
11269
|
+
const meta = parsed.meta;
|
|
11270
|
+
if (!meta || typeof meta.gatePassed !== "boolean") return reject("missing meta");
|
|
11271
|
+
if (!meta.gatePassed) return reject("its evaluation gate did not pass - run train-model after labeling more feedback");
|
|
11272
|
+
if (typeof meta.acceptThreshold !== "number") return reject("gate passed but no calibrated threshold (corrupt meta)");
|
|
11273
|
+
return {
|
|
11274
|
+
model: parsed.model,
|
|
11275
|
+
acceptThreshold: meta.acceptThreshold,
|
|
11276
|
+
strongThreshold: typeof meta.strongThreshold === "number" ? meta.strongThreshold : null,
|
|
11277
|
+
meta
|
|
11278
|
+
};
|
|
11279
|
+
}
|
|
11199
11280
|
|
|
11200
|
-
// src/analysis/
|
|
11281
|
+
// src/analysis/FailureClusterer.ts
|
|
11201
11282
|
init_cjs_shims();
|
|
11202
11283
|
|
|
11203
|
-
// src/analysis/
|
|
11284
|
+
// src/analysis/redact.ts
|
|
11204
11285
|
init_cjs_shims();
|
|
11205
|
-
|
|
11206
|
-
|
|
11286
|
+
var URI_RE = /[a-z][\w+.-]*:\/\/\S+/gi;
|
|
11287
|
+
var EMAIL_RE = /\b[\w.+-]+@[\w.-]+\.\w+\b/g;
|
|
11288
|
+
var SECRET_RE = /\b(?=[A-Za-z0-9_+/=-]*[0-9])(?=[A-Za-z0-9_+/=-]*[A-Za-z])[A-Za-z0-9_+/=-]{16,}\b/g;
|
|
11289
|
+
var IP_RE = /\b\d{1,3}(?:\.\d{1,3}){3}\b/g;
|
|
11290
|
+
var WIN_PATH_RE = /[A-Za-z]:\\[^\s'"]+/g;
|
|
11291
|
+
var UNIX_PATH_RE = /(?:\/[\w.-]+){2,}/g;
|
|
11292
|
+
function redactForStorage(raw) {
|
|
11293
|
+
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 ");
|
|
11294
|
+
return tokenize(redacted).join(" ");
|
|
11207
11295
|
}
|
|
11208
|
-
|
|
11209
|
-
|
|
11210
|
-
function classify(model, text) {
|
|
11211
|
-
const tokens = tokenize(text);
|
|
11212
|
-
const vocabTokens = tokens.filter(
|
|
11213
|
-
(tok) => model.labels.some((l) => model.logLikelihoods[l]?.[tok] !== void 0)
|
|
11214
|
-
);
|
|
11215
|
-
if (vocabTokens.length === 0) {
|
|
11216
|
-
return { label: "unknown", margin: 0 };
|
|
11217
|
-
}
|
|
11218
|
-
const scores = {};
|
|
11219
|
-
for (const label of model.labels) {
|
|
11220
|
-
let s = model.priors[label];
|
|
11221
|
-
const oov = Math.log(model.alpha / (model.totals[label] + model.alpha * model.vocabSize));
|
|
11222
|
-
const ll = model.logLikelihoods[label];
|
|
11223
|
-
for (const tok of vocabTokens) {
|
|
11224
|
-
s += ll[tok] ?? oov;
|
|
11225
|
-
}
|
|
11226
|
-
scores[label] = s;
|
|
11227
|
-
}
|
|
11228
|
-
const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
11229
|
-
const topLog = ranked[0][1];
|
|
11230
|
-
const secondLog = ranked[1]?.[1] ?? topLog;
|
|
11231
|
-
const margin = (topLog - secondLog) / vocabTokens.length;
|
|
11232
|
-
return { label: ranked[0][0], margin };
|
|
11296
|
+
function localInferenceTokens(message) {
|
|
11297
|
+
return redactForStorage(message).split(" ").filter(Boolean);
|
|
11233
11298
|
}
|
|
11234
11299
|
|
|
11235
11300
|
// src/analysis/constants.ts
|
|
@@ -11258,7 +11323,7 @@ function errorHeader(message) {
|
|
|
11258
11323
|
return beforeCallLog.split("\n").filter((line) => !/^\s*[-+]?\s*(?:Expected|Received)\b/i.test(line)).join("\n");
|
|
11259
11324
|
}
|
|
11260
11325
|
function isAssertionTimeout(message) {
|
|
11261
|
-
return /Expect "[^"]+" with timeout/.test(message) && /resolved to [1-9]\d* element/.test(message);
|
|
11326
|
+
return /(?:Expect "[^"]+"|expect\.\w+) with timeout/.test(message) && /resolved to [1-9]\d* element/.test(message);
|
|
11262
11327
|
}
|
|
11263
11328
|
function isNetwork(message) {
|
|
11264
11329
|
return /net::ERR_(CONNECTION|NAME_NOT_RESOLVED|INTERNET_DISCONNECTED|SSL|TIMED_OUT|ADDRESS|NETWORK_CHANGED|EMPTY_RESPONSE)|\bECONN(?:REFUSED|RESET)\b|\bENOTFOUND\b|\bEAI_AGAIN\b|getaddrinfo|socket hang up|fetch failed/.test(errorHeader(message));
|
|
@@ -11295,26 +11360,37 @@ function topFrame(stack) {
|
|
|
11295
11360
|
function representativeMessage(message) {
|
|
11296
11361
|
return clampWithEllipsis(message.split("\n")[0], MAX_MESSAGE_CHARS);
|
|
11297
11362
|
}
|
|
11298
|
-
function classifyOne(f,
|
|
11363
|
+
function classifyOne(f, chain) {
|
|
11299
11364
|
const frame = topFrame(f.error.stack);
|
|
11300
11365
|
if (f.retryHistory.some((r) => r.status === "passed")) {
|
|
11301
|
-
return { category: "flaky-by-retry", margin: OVERRIDE_MARGIN, failure: f, frame };
|
|
11366
|
+
return { category: "flaky-by-retry", margin: OVERRIDE_MARGIN, strength: "strong", source: "retry", failure: f, frame };
|
|
11302
11367
|
}
|
|
11303
11368
|
for (const [match, category] of MESSAGE_RULES) {
|
|
11304
|
-
if (match(f.error.message))
|
|
11369
|
+
if (match(f.error.message)) {
|
|
11370
|
+
return { category, margin: OVERRIDE_MARGIN, strength: "strong", source: "rule", failure: f, frame };
|
|
11371
|
+
}
|
|
11305
11372
|
}
|
|
11306
|
-
|
|
11307
|
-
|
|
11373
|
+
if (chain.local) {
|
|
11374
|
+
const { model, acceptThreshold, strongThreshold } = chain.local;
|
|
11375
|
+
const r = classifyTokens(model, localInferenceTokens(f.error.message));
|
|
11376
|
+
if (r.label !== "unknown" && r.margin >= acceptThreshold) {
|
|
11377
|
+
const strength = strongThreshold !== null && r.margin >= strongThreshold ? "strong" : "moderate";
|
|
11378
|
+
return { category: r.label, margin: r.margin, strength, source: "local", failure: f, frame };
|
|
11379
|
+
}
|
|
11380
|
+
}
|
|
11381
|
+
const { label, margin } = classify(chain.base, classifyInput(f));
|
|
11382
|
+
return { category: label, margin, strength: strengthOf(margin), source: "base", failure: f, frame };
|
|
11308
11383
|
}
|
|
11309
|
-
function classifyAll(failures,
|
|
11310
|
-
return failures.map((f) => classifyOne(f,
|
|
11384
|
+
function classifyAll(failures, chain) {
|
|
11385
|
+
return failures.map((f) => classifyOne(f, chain));
|
|
11311
11386
|
}
|
|
11312
11387
|
function perTestClassifications(classified) {
|
|
11313
11388
|
return classified.map((c) => ({
|
|
11314
11389
|
testId: c.failure.testId,
|
|
11315
11390
|
category: c.category,
|
|
11316
11391
|
message: representativeMessage(c.failure.error.message),
|
|
11317
|
-
strength:
|
|
11392
|
+
strength: c.strength
|
|
11393
|
+
// resolved at classification time, per producing source
|
|
11318
11394
|
}));
|
|
11319
11395
|
}
|
|
11320
11396
|
function clusterClassified(classified) {
|
|
@@ -11325,13 +11401,16 @@ function clusterClassified(classified) {
|
|
|
11325
11401
|
if (bucket) bucket.push(c);
|
|
11326
11402
|
else groups.set(key, [c]);
|
|
11327
11403
|
}
|
|
11404
|
+
const rank = Object.fromEntries(
|
|
11405
|
+
STRENGTH_LEVELS.map((level, i) => [level, i])
|
|
11406
|
+
);
|
|
11328
11407
|
const clusters = [];
|
|
11329
11408
|
for (const members of groups.values()) {
|
|
11330
|
-
const
|
|
11409
|
+
const strongest = members.reduce((a, b) => rank[b.strength] > rank[a.strength] ? b : a);
|
|
11331
11410
|
clusters.push({
|
|
11332
11411
|
category: members[0].category,
|
|
11333
|
-
strength:
|
|
11334
|
-
margin,
|
|
11412
|
+
strength: strongest.strength,
|
|
11413
|
+
margin: Math.max(...members.map((m) => m.margin)),
|
|
11335
11414
|
count: members.length,
|
|
11336
11415
|
tests: members.slice(0, MAX_TESTS_PER_CLUSTER).map((m) => m.failure.testTitle),
|
|
11337
11416
|
testsTotal: members.length,
|
|
@@ -11407,82 +11486,133 @@ function buildBriefSentence(stats, analysis, trend) {
|
|
|
11407
11486
|
|
|
11408
11487
|
// src/analysis/UnclassifiedCollector.ts
|
|
11409
11488
|
init_cjs_shims();
|
|
11410
|
-
var
|
|
11411
|
-
var
|
|
11412
|
-
var
|
|
11489
|
+
var import_node_fs4 = __toESM(require("fs"));
|
|
11490
|
+
var import_node_os4 = __toESM(require("os"));
|
|
11491
|
+
var import_node_path4 = __toESM(require("path"));
|
|
11413
11492
|
|
|
11414
11493
|
// src/analysis/feedback-csv.ts
|
|
11415
11494
|
init_cjs_shims();
|
|
11416
|
-
var FEEDBACK_HEADER = "category,margin,text";
|
|
11495
|
+
var FEEDBACK_HEADER = "category,margin,label,text";
|
|
11496
|
+
var LEGACY_FEEDBACK_HEADER = "category,margin,text";
|
|
11417
11497
|
function parseFeedbackRows(content) {
|
|
11418
|
-
|
|
11498
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
11499
|
+
if (lines.length === 0) return [];
|
|
11500
|
+
const header = lines[0].trim();
|
|
11501
|
+
const isV2 = header === FEEDBACK_HEADER;
|
|
11502
|
+
if (!isV2 && header !== LEGACY_FEEDBACK_HEADER) return [];
|
|
11503
|
+
const minParts = isV2 ? 4 : 3;
|
|
11504
|
+
const rows = [];
|
|
11505
|
+
for (const line of lines.slice(1)) {
|
|
11419
11506
|
const parts = line.split(",");
|
|
11420
|
-
|
|
11421
|
-
|
|
11507
|
+
if (parts.length < minParts) continue;
|
|
11508
|
+
const row = isV2 ? { category: parts[0], margin: parts[1], label: parts[2], text: parts.slice(3).join(",") } : { category: parts[0], margin: parts[1], label: "", text: parts.slice(2).join(",") };
|
|
11509
|
+
if (row.text.trim().length === 0) continue;
|
|
11510
|
+
rows.push(row);
|
|
11511
|
+
}
|
|
11512
|
+
return rows;
|
|
11513
|
+
}
|
|
11514
|
+
function serializeFeedbackRow(r) {
|
|
11515
|
+
const label = r.label.replace(/[,\r\n]/g, "").trim();
|
|
11516
|
+
return `${r.category},${r.margin},${label},${r.text}`;
|
|
11422
11517
|
}
|
|
11423
11518
|
function feedbackIdentity(category, text) {
|
|
11424
11519
|
return `${category} ${text}`;
|
|
11425
11520
|
}
|
|
11426
11521
|
|
|
11522
|
+
// src/analysis/FeedbackStore.ts
|
|
11523
|
+
init_cjs_shims();
|
|
11524
|
+
var import_node_fs3 = __toESM(require("fs"));
|
|
11525
|
+
var import_node_os3 = __toESM(require("os"));
|
|
11526
|
+
var import_node_path3 = __toESM(require("path"));
|
|
11527
|
+
var MARKER_FILENAME = "project.json";
|
|
11528
|
+
function writeProjectMarker(projectDir, marker) {
|
|
11529
|
+
try {
|
|
11530
|
+
atomicWrite(import_node_path3.default.join(projectDir, MARKER_FILENAME), JSON.stringify(marker));
|
|
11531
|
+
} catch {
|
|
11532
|
+
}
|
|
11533
|
+
}
|
|
11534
|
+
function rewriteFeedbackFile(file, rows) {
|
|
11535
|
+
const body = rows.map(serializeFeedbackRow).join("\n");
|
|
11536
|
+
atomicWrite(file, `${FEEDBACK_HEADER}
|
|
11537
|
+
${body}${body ? "\n" : ""}`);
|
|
11538
|
+
}
|
|
11539
|
+
function atomicWrite(file, content) {
|
|
11540
|
+
import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
|
|
11541
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
11542
|
+
import_node_fs3.default.writeFileSync(tmp, content, "utf8");
|
|
11543
|
+
import_node_fs3.default.renameSync(tmp, file);
|
|
11544
|
+
}
|
|
11545
|
+
|
|
11427
11546
|
// src/analysis/UnclassifiedCollector.ts
|
|
11428
11547
|
var MAX_ROWS = 500;
|
|
11429
11548
|
var FILENAME = "unclassified.csv";
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
var WIN_PATH_RE = /[A-Za-z]:\\[^\s'"]+/g;
|
|
11435
|
-
var UNIX_PATH_RE = /(?:\/[\w.-]+){2,}/g;
|
|
11436
|
-
function redactForStorage(raw) {
|
|
11437
|
-
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 ");
|
|
11438
|
-
return tokenize(redacted).join(" ");
|
|
11439
|
-
}
|
|
11440
|
-
function shouldCollect(c) {
|
|
11441
|
-
return c.category === "unknown" || c.margin <= STRENGTH.collectMaxMargin;
|
|
11549
|
+
function shouldCollect(c, scope) {
|
|
11550
|
+
if (c.source !== "base") return false;
|
|
11551
|
+
if (c.category === "unknown" || c.margin <= STRENGTH.collectMaxMargin) return true;
|
|
11552
|
+
return scope === "all";
|
|
11442
11553
|
}
|
|
11443
11554
|
function tildeify(p) {
|
|
11444
|
-
const home =
|
|
11555
|
+
const home = import_node_os4.default.homedir();
|
|
11445
11556
|
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
11446
11557
|
}
|
|
11447
|
-
function collectUnclassified(classified, projectDir) {
|
|
11448
|
-
const
|
|
11558
|
+
function collectUnclassified(classified, projectDir, opts = {}) {
|
|
11559
|
+
const scope = opts.scope ?? "blind-spots";
|
|
11560
|
+
const rows = classified.filter((c) => shouldCollect(c, scope)).map((c) => ({ text: redactForStorage(c.failure.error.message), category: c.category, margin: c.margin })).filter((r) => r.text.length > 0);
|
|
11449
11561
|
if (rows.length === 0) return;
|
|
11450
|
-
const file =
|
|
11451
|
-
const firstTime = !
|
|
11562
|
+
const file = import_node_path4.default.join(projectDir, FILENAME);
|
|
11563
|
+
const firstTime = !import_node_fs4.default.existsSync(file);
|
|
11452
11564
|
const byIdentity = /* @__PURE__ */ new Map();
|
|
11453
11565
|
if (!firstTime) {
|
|
11454
|
-
for (const r of parseFeedbackRows(
|
|
11455
|
-
byIdentity.set(feedbackIdentity(r.category, r.text), r
|
|
11566
|
+
for (const r of parseFeedbackRows(import_node_fs4.default.readFileSync(file, "utf8"))) {
|
|
11567
|
+
byIdentity.set(feedbackIdentity(r.category, r.text), r);
|
|
11456
11568
|
}
|
|
11457
11569
|
}
|
|
11458
|
-
let added = false;
|
|
11459
11570
|
for (const r of rows) {
|
|
11460
11571
|
const id = feedbackIdentity(r.category, r.text);
|
|
11572
|
+
const label = byIdentity.get(id)?.label ?? "";
|
|
11461
11573
|
byIdentity.delete(id);
|
|
11462
|
-
byIdentity.set(id,
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
`, "utf8");
|
|
11574
|
+
byIdentity.set(id, { category: r.category, margin: String(r.margin), label, text: r.text });
|
|
11575
|
+
}
|
|
11576
|
+
const all = evictOldestUnlabeledFirst([...byIdentity.values()], MAX_ROWS);
|
|
11577
|
+
import_node_fs4.default.mkdirSync(projectDir, { recursive: true });
|
|
11578
|
+
rewriteFeedbackFile(file, all);
|
|
11579
|
+
if (opts.project) {
|
|
11580
|
+
writeProjectMarker(projectDir, { cwd: opts.project.cwd, outputFile: opts.project.outputFile, updatedAt: Date.now() });
|
|
11581
|
+
}
|
|
11471
11582
|
if (firstTime) {
|
|
11472
11583
|
logger.info(
|
|
11473
11584
|
`Unrecognized failures collected locally at ${tildeify(file)} to improve offline classification. Local only - never sent anywhere. Disable: failureAnalysis.collectUnclassified=false`
|
|
11474
11585
|
);
|
|
11475
11586
|
}
|
|
11476
11587
|
}
|
|
11588
|
+
function evictOldestUnlabeledFirst(rows, max) {
|
|
11589
|
+
const excess = rows.length - max;
|
|
11590
|
+
if (excess <= 0) return rows;
|
|
11591
|
+
let unlabeledToDrop = Math.min(excess, rows.filter((r) => r.label === "").length);
|
|
11592
|
+
let labeledToDrop = excess - unlabeledToDrop;
|
|
11593
|
+
const out = [];
|
|
11594
|
+
for (const r of rows) {
|
|
11595
|
+
if (r.label === "" && unlabeledToDrop > 0) {
|
|
11596
|
+
unlabeledToDrop--;
|
|
11597
|
+
continue;
|
|
11598
|
+
}
|
|
11599
|
+
if (r.label !== "" && labeledToDrop > 0) {
|
|
11600
|
+
labeledToDrop--;
|
|
11601
|
+
continue;
|
|
11602
|
+
}
|
|
11603
|
+
out.push(r);
|
|
11604
|
+
}
|
|
11605
|
+
return out;
|
|
11606
|
+
}
|
|
11477
11607
|
|
|
11478
11608
|
// src/utils/paths.ts
|
|
11479
11609
|
init_cjs_shims();
|
|
11480
|
-
var
|
|
11481
|
-
var
|
|
11610
|
+
var import_node_os5 = __toESM(require("os"));
|
|
11611
|
+
var import_node_path5 = __toESM(require("path"));
|
|
11482
11612
|
var import_node_crypto2 = __toESM(require("crypto"));
|
|
11483
11613
|
function resolveProjectDir(options, cwd) {
|
|
11484
11614
|
const projectKey = import_node_crypto2.default.createHash("sha256").update(cwd + (options.outputFile ?? "")).digest("hex").slice(0, 8);
|
|
11485
|
-
return
|
|
11615
|
+
return import_node_path5.default.join(import_node_os5.default.homedir(), ".reportforge", projectKey);
|
|
11486
11616
|
}
|
|
11487
11617
|
|
|
11488
11618
|
// src/analysis/index.ts
|
|
@@ -11517,16 +11647,36 @@ function runFailureAnalysis(reportData, options, cwd) {
|
|
|
11517
11647
|
if (reportData.stats.failed === 0 || !fa.enabled) return;
|
|
11518
11648
|
const cap = fa.maxFailuresToAnalyse;
|
|
11519
11649
|
const analysed = reportData.failures.slice(0, cap);
|
|
11520
|
-
const
|
|
11521
|
-
const
|
|
11650
|
+
const base = loadModel(fa.autoUpdateModel);
|
|
11651
|
+
const local = fa.localModel ? loadLocalModel(fa.localModelPath) ?? void 0 : void 0;
|
|
11652
|
+
if (local) {
|
|
11653
|
+
logger.debug(
|
|
11654
|
+
`local model active (trained ${local.meta.trainedAt}, ${local.meta.labeledCount} labeled rows, accept margin >= ${local.acceptThreshold})`
|
|
11655
|
+
);
|
|
11656
|
+
if (local.meta.baseVersionAtEval !== base.version) {
|
|
11657
|
+
logger.debug(
|
|
11658
|
+
`local model was gate-checked against base v${local.meta.baseVersionAtEval} but base v${base.version} is active - re-run train-model to re-validate against the current base`
|
|
11659
|
+
);
|
|
11660
|
+
}
|
|
11661
|
+
}
|
|
11662
|
+
const classified = classifyAll(analysed, { base, local });
|
|
11663
|
+
logger.debug(`failure analysis: ${classified.length} classified - ${sourceTally(classified)}`);
|
|
11522
11664
|
reportData.classifications = perTestClassifications(classified);
|
|
11523
11665
|
if (templatesConsumeAnalysis(options)) {
|
|
11524
11666
|
reportData.analysis = analyseClassified(classified, reportData.failures.length, fa);
|
|
11525
11667
|
}
|
|
11526
11668
|
if (fa.collectUnclassified) {
|
|
11527
|
-
collectUnclassified(classified, resolveProjectDir(options, cwd)
|
|
11669
|
+
collectUnclassified(classified, resolveProjectDir(options, cwd), {
|
|
11670
|
+
scope: fa.collectScope,
|
|
11671
|
+
project: { cwd, outputFile: options.outputFile }
|
|
11672
|
+
});
|
|
11528
11673
|
}
|
|
11529
11674
|
}
|
|
11675
|
+
function sourceTally(classified) {
|
|
11676
|
+
const counts = /* @__PURE__ */ new Map();
|
|
11677
|
+
for (const c of classified) counts.set(c.source, (counts.get(c.source) ?? 0) + 1);
|
|
11678
|
+
return ["rule", "retry", "local", "base"].filter((s) => counts.has(s)).map((s) => `${counts.get(s)} ${s}`).join(" / ");
|
|
11679
|
+
}
|
|
11530
11680
|
|
|
11531
11681
|
// src/collector/DataCollector.ts
|
|
11532
11682
|
init_cjs_shims();
|
|
@@ -11802,8 +11952,8 @@ function buildSuiteResults(projects, limit) {
|
|
|
11802
11952
|
const top = projects.flatMap((p) => p.suites);
|
|
11803
11953
|
if (top.length <= 1 && top[0] && top[0].suites.length > 0) {
|
|
11804
11954
|
const file = top[0];
|
|
11805
|
-
const
|
|
11806
|
-
const rootRow = file.tests.length > 0 ? [{ label: file.title, passed:
|
|
11955
|
+
const fs16 = statsFromTests(file.tests);
|
|
11956
|
+
const rootRow = file.tests.length > 0 ? [{ label: file.title, passed: fs16.passed, failed: fs16.failed, timedOut: fs16.timedOut, skipped: fs16.skipped }] : [];
|
|
11807
11957
|
return capRows([...rootRow, ...file.suites.map(toRow)], limit);
|
|
11808
11958
|
}
|
|
11809
11959
|
return capRows(top.map(toRow), limit);
|
|
@@ -14204,7 +14354,7 @@ var PdfReporter = class {
|
|
|
14204
14354
|
this.options = parseOptions(rawOptions);
|
|
14205
14355
|
setLogLevel(this.options.logLevel);
|
|
14206
14356
|
this.dataCollector = new DataCollector(this.options.capture);
|
|
14207
|
-
this.version = true ? "0.
|
|
14357
|
+
this.version = true ? "0.20.0" : "0.x";
|
|
14208
14358
|
logger.info(`@reportforge/playwright-pdf v${this.version} initialised`);
|
|
14209
14359
|
this.licenseClient = new LicenseClient({
|
|
14210
14360
|
licenseKey: this.options.licenseKey,
|