@skyramp/mcp 0.1.7 → 0.2.0-rc.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/build/playwright/registerPlaywrightTools.js +12 -0
- package/build/playwright/traceRecordingPrompt.js +15 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +1 -1
- package/build/prompts/test-recommendation/diffExecutionPlan.js +31 -0
- package/build/prompts/test-recommendation/recommendationSections.js +1 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +94 -0
- package/build/prompts/testbot/testbot-prompts.js +115 -11
- package/build/prompts/testbot/testbot-prompts.test.js +79 -0
- package/build/resources/testbotResource.js +1 -1
- package/build/services/ScenarioGenerationService.integration.test.js +158 -0
- package/build/services/ScenarioGenerationService.js +36 -3
- package/build/services/ScenarioGenerationService.test.js +158 -22
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +16 -4
- package/build/tools/generate-tests/generateIntegrationRestTool.js +2 -0
- package/build/tools/generate-tests/generateUIRestTool.js +2 -0
- package/build/tools/test-management/analyzeChangesTool.js +7 -1
- package/build/utils/routeParsers.js +12 -0
- package/node_modules/playwright/ThirdPartyNotices.txt +6 -6
- package/node_modules/playwright/lib/dom-analyzer/analyze.js +111 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1161 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.test.js +396 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.js +250 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.test.js +298 -0
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +384 -0
- package/node_modules/playwright/lib/dom-analyzer/curatedWidgets.js +73 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +85 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.js +90 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.test.js +231 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.fixtures.js +145 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.test.js +41 -0
- package/node_modules/playwright/lib/dom-analyzer/graph.js +36 -0
- package/node_modules/playwright/lib/dom-analyzer/liveFingerprints.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.js +72 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.test.js +182 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.js +169 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.test.js +269 -0
- package/node_modules/playwright/lib/dom-analyzer/serialization.js +75 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.js +30 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.test.js +84 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.js +127 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.test.js +212 -0
- package/node_modules/playwright/lib/mcp/browser/browserContextFactory.js +3 -1
- package/node_modules/playwright/lib/mcp/browser/config.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/context.js +17 -1
- package/node_modules/playwright/lib/mcp/browser/tab.js +38 -0
- package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +261 -0
- package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +3 -3
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +129 -0
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.test.js +137 -0
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +226 -0
- package/node_modules/playwright/lib/mcp/browser/tools/snapshot.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/widgetContract.js +168 -0
- package/node_modules/playwright/lib/mcp/browser/tools.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +52 -12
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +64 -13
- package/node_modules/playwright/package.json +1 -1
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.3.tgz +0 -0
- package/package.json +2 -2
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var fs = __toESM(require("fs"));
|
|
25
|
+
var path = __toESM(require("path"));
|
|
26
|
+
var import_fingerprint = require("./fingerprint");
|
|
27
|
+
const cases = [];
|
|
28
|
+
function test(name, run) {
|
|
29
|
+
cases.push({ name, run });
|
|
30
|
+
}
|
|
31
|
+
function assertEqual(actual, expected, msg) {
|
|
32
|
+
const a = JSON.stringify(actual);
|
|
33
|
+
const e = JSON.stringify(expected);
|
|
34
|
+
if (a !== e)
|
|
35
|
+
throw new Error(`${msg ?? "assertEqual"} \u2014 expected ${e}, got ${a}`);
|
|
36
|
+
}
|
|
37
|
+
test("normalizeClasses: keeps BEM-ish classes", () => {
|
|
38
|
+
assertEqual(
|
|
39
|
+
(0, import_fingerprint.normalizeClasses)("vue-treeselect vue-treeselect__menu vue-treeselect--single"),
|
|
40
|
+
"vue-treeselect vue-treeselect--single vue-treeselect__menu"
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
test("normalizeClasses: keeps framework-prefixed classes", () => {
|
|
44
|
+
assertEqual(
|
|
45
|
+
(0, import_fingerprint.normalizeClasses)("MuiSelect-root MuiSelect-standard"),
|
|
46
|
+
"MuiSelect-root MuiSelect-standard"
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
test("normalizeClasses: drops tailwind utilities", () => {
|
|
50
|
+
assertEqual(
|
|
51
|
+
(0, import_fingerprint.normalizeClasses)("vue-treeselect bg-red-500 p-4 hover:text-white"),
|
|
52
|
+
"vue-treeselect"
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
test("normalizeClasses: drops CSS-module hashed suffixes", () => {
|
|
56
|
+
assertEqual(
|
|
57
|
+
(0, import_fingerprint.normalizeClasses)("button_abc123 vue-treeselect"),
|
|
58
|
+
"vue-treeselect"
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
test("normalizeClasses: drops numeric-index suffixes", () => {
|
|
62
|
+
assertEqual(
|
|
63
|
+
(0, import_fingerprint.normalizeClasses)("item-1 item-2 vue-treeselect"),
|
|
64
|
+
"vue-treeselect"
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
test("normalizeClasses: drops state tokens", () => {
|
|
68
|
+
assertEqual(
|
|
69
|
+
(0, import_fingerprint.normalizeClasses)("vue-treeselect is-active is-open expanded"),
|
|
70
|
+
"vue-treeselect"
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
test("normalizeClasses: framework prefix beats numeric-suffix deny-list", () => {
|
|
74
|
+
assertEqual(
|
|
75
|
+
(0, import_fingerprint.normalizeClasses)("MuiSelect-root MuiSelect-123 ant-select-item-1"),
|
|
76
|
+
"MuiSelect-123 MuiSelect-root ant-select-item-1"
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
test("normalizeClasses: framework prefix beats tailwind-shape deny-list", () => {
|
|
80
|
+
assertEqual(
|
|
81
|
+
(0, import_fingerprint.normalizeClasses)("data-radix-border-blue"),
|
|
82
|
+
"data-radix-border-blue"
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
test("normalizeClasses: empty string returns empty string", () => {
|
|
86
|
+
assertEqual((0, import_fingerprint.normalizeClasses)(""), "");
|
|
87
|
+
});
|
|
88
|
+
test("normalizeClasses: sorts alphabetically", () => {
|
|
89
|
+
assertEqual(
|
|
90
|
+
(0, import_fingerprint.normalizeClasses)("vue-treeselect__menu vue-treeselect"),
|
|
91
|
+
"vue-treeselect vue-treeselect__menu"
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
test("normalizeClasses: deduplicates", () => {
|
|
95
|
+
assertEqual(
|
|
96
|
+
(0, import_fingerprint.normalizeClasses)("vue-treeselect vue-treeselect"),
|
|
97
|
+
"vue-treeselect"
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
test("computeFingerprintFromInput: identical input yields identical hash", () => {
|
|
101
|
+
const input = {
|
|
102
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "", classPattern: "vue-treeselect", dataAttrKeys: [] },
|
|
103
|
+
descendants: [],
|
|
104
|
+
portal: null
|
|
105
|
+
};
|
|
106
|
+
assertEqual((0, import_fingerprint.computeFingerprintFromInput)(input), (0, import_fingerprint.computeFingerprintFromInput)(input));
|
|
107
|
+
});
|
|
108
|
+
test("computeFingerprintFromInput: different tag yields different hash", () => {
|
|
109
|
+
const a = {
|
|
110
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
111
|
+
descendants: [],
|
|
112
|
+
portal: null
|
|
113
|
+
};
|
|
114
|
+
const b = {
|
|
115
|
+
root: { tag: "button", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
116
|
+
descendants: [],
|
|
117
|
+
portal: null
|
|
118
|
+
};
|
|
119
|
+
if ((0, import_fingerprint.computeFingerprintFromInput)(a) === (0, import_fingerprint.computeFingerprintFromInput)(b))
|
|
120
|
+
throw new Error("expected different hashes");
|
|
121
|
+
});
|
|
122
|
+
test("computeFingerprintFromInput: same descendant order produces same hash (stability)", () => {
|
|
123
|
+
const d1 = { tag: "div", role: "", ariaHasPopup: "", classPattern: "a", dataAttrKeys: [] };
|
|
124
|
+
const d2 = { tag: "div", role: "", ariaHasPopup: "", classPattern: "b", dataAttrKeys: [] };
|
|
125
|
+
const a = {
|
|
126
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
127
|
+
descendants: [d1, d2],
|
|
128
|
+
portal: null
|
|
129
|
+
};
|
|
130
|
+
const b = {
|
|
131
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
132
|
+
descendants: [d1, d2],
|
|
133
|
+
// same order
|
|
134
|
+
portal: null
|
|
135
|
+
};
|
|
136
|
+
assertEqual((0, import_fingerprint.computeFingerprintFromInput)(a), (0, import_fingerprint.computeFingerprintFromInput)(b));
|
|
137
|
+
});
|
|
138
|
+
test("computeFingerprintFromInput: descendant order DOES affect hash (order is part of the fingerprint)", () => {
|
|
139
|
+
const d1 = { tag: "div", role: "", ariaHasPopup: "", classPattern: "a", dataAttrKeys: [] };
|
|
140
|
+
const d2 = { tag: "div", role: "", ariaHasPopup: "", classPattern: "b", dataAttrKeys: [] };
|
|
141
|
+
const a = {
|
|
142
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
143
|
+
descendants: [d1, d2],
|
|
144
|
+
portal: null
|
|
145
|
+
};
|
|
146
|
+
const b = {
|
|
147
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
148
|
+
descendants: [d2, d1],
|
|
149
|
+
// reversed
|
|
150
|
+
portal: null
|
|
151
|
+
};
|
|
152
|
+
if ((0, import_fingerprint.computeFingerprintFromInput)(a) === (0, import_fingerprint.computeFingerprintFromInput)(b))
|
|
153
|
+
throw new Error("expected different hashes when descendant order differs");
|
|
154
|
+
});
|
|
155
|
+
test("computeFingerprintFromInput: returns 16 hex chars", () => {
|
|
156
|
+
const input = {
|
|
157
|
+
root: { tag: "div", role: "", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
158
|
+
descendants: [],
|
|
159
|
+
portal: null
|
|
160
|
+
};
|
|
161
|
+
const hash = (0, import_fingerprint.computeFingerprintFromInput)(input);
|
|
162
|
+
if (!/^[0-9a-f]{16}$/.test(hash))
|
|
163
|
+
throw new Error(`expected 16-hex hash, got "${hash}"`);
|
|
164
|
+
});
|
|
165
|
+
test("computeFingerprintFromInput: null portal differs from populated portal", () => {
|
|
166
|
+
const portalTuple = { tag: "div", role: "", ariaHasPopup: "", classPattern: "data-radix-popper-content-wrapper", dataAttrKeys: [] };
|
|
167
|
+
const a = {
|
|
168
|
+
root: { tag: "button", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
169
|
+
descendants: [],
|
|
170
|
+
portal: null
|
|
171
|
+
};
|
|
172
|
+
const b = {
|
|
173
|
+
root: { tag: "button", role: "combobox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
174
|
+
descendants: [],
|
|
175
|
+
portal: portalTuple
|
|
176
|
+
};
|
|
177
|
+
if ((0, import_fingerprint.computeFingerprintFromInput)(a) === (0, import_fingerprint.computeFingerprintFromInput)(b))
|
|
178
|
+
throw new Error("expected different hashes");
|
|
179
|
+
});
|
|
180
|
+
test("drift detection: blueprint.ts inline rule constants match fingerprint.ts exports", () => {
|
|
181
|
+
const blueprintSrcPath = path.resolve(__dirname, "..", "..", "src", "dom-analyzer", "blueprint.ts");
|
|
182
|
+
const src = fs.readFileSync(blueprintSrcPath, "utf-8");
|
|
183
|
+
const beginMarker = "// INLINE_RULES_BEGIN";
|
|
184
|
+
const endMarker = "// INLINE_RULES_END";
|
|
185
|
+
const beginIdx = src.indexOf(beginMarker);
|
|
186
|
+
const endIdx = src.indexOf(endMarker, beginIdx);
|
|
187
|
+
if (beginIdx === -1 || endIdx === -1)
|
|
188
|
+
throw new Error(`blueprint.ts must contain ${beginMarker} / ${endMarker} markers around the inlined fingerprint rule constants`);
|
|
189
|
+
const newlineAfterBegin = src.indexOf("\n", beginIdx);
|
|
190
|
+
const block = src.slice(newlineAfterBegin + 1, endIdx);
|
|
191
|
+
const inlined = new Function(
|
|
192
|
+
block + "\nreturn { BEM_ISH, FRAMEWORK_PREFIXES, TAILWIND_UTILITY, CSS_MODULE_HASH, NUMERIC_INDEX_SUFFIX, STATE_TOKENS };"
|
|
193
|
+
)();
|
|
194
|
+
function regexEq(a, b, name) {
|
|
195
|
+
if (a.source !== b.source || a.flags !== b.flags)
|
|
196
|
+
throw new Error(`${name} drift: fingerprint.ts has /${a.source}/${a.flags}, blueprint.ts has /${b.source}/${b.flags}`);
|
|
197
|
+
}
|
|
198
|
+
function arrayEq(a, b, name) {
|
|
199
|
+
if (a.length !== b.length || a.some((v, i) => v !== b[i]))
|
|
200
|
+
throw new Error(`${name} drift: fingerprint.ts has ${JSON.stringify(a)}, blueprint.ts has ${JSON.stringify(b)}`);
|
|
201
|
+
}
|
|
202
|
+
function setEq(a, b, name) {
|
|
203
|
+
const aSorted = Array.from(a).sort();
|
|
204
|
+
const bSorted = Array.from(b).sort();
|
|
205
|
+
arrayEq(aSorted, bSorted, name);
|
|
206
|
+
}
|
|
207
|
+
regexEq(import_fingerprint.BEM_ISH, inlined.BEM_ISH, "BEM_ISH");
|
|
208
|
+
arrayEq(import_fingerprint.FRAMEWORK_PREFIXES, inlined.FRAMEWORK_PREFIXES, "FRAMEWORK_PREFIXES");
|
|
209
|
+
regexEq(import_fingerprint.TAILWIND_UTILITY, inlined.TAILWIND_UTILITY, "TAILWIND_UTILITY");
|
|
210
|
+
regexEq(import_fingerprint.CSS_MODULE_HASH, inlined.CSS_MODULE_HASH, "CSS_MODULE_HASH");
|
|
211
|
+
regexEq(import_fingerprint.NUMERIC_INDEX_SUFFIX, inlined.NUMERIC_INDEX_SUFFIX, "NUMERIC_INDEX_SUFFIX");
|
|
212
|
+
setEq(import_fingerprint.STATE_TOKENS, inlined.STATE_TOKENS, "STATE_TOKENS");
|
|
213
|
+
});
|
|
214
|
+
let failed = 0;
|
|
215
|
+
for (const { name, run } of cases) {
|
|
216
|
+
try {
|
|
217
|
+
run();
|
|
218
|
+
console.log(" \u2713", name);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
failed++;
|
|
221
|
+
console.log(" \u2717", name);
|
|
222
|
+
console.log(" ", e.message);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (failed > 0) {
|
|
226
|
+
console.log(`
|
|
227
|
+
${failed}/${cases.length} failed`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
console.log(`
|
|
231
|
+
${cases.length} passed`);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var fingerprintAblation_fixtures_exports = {};
|
|
20
|
+
__export(fingerprintAblation_fixtures_exports, {
|
|
21
|
+
ABLATION_FIXTURES: () => ABLATION_FIXTURES
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(fingerprintAblation_fixtures_exports);
|
|
24
|
+
const ABLATION_FIXTURES = [
|
|
25
|
+
{
|
|
26
|
+
name: "vue-treeselect",
|
|
27
|
+
description: "Vue-Treeselect combobox trigger (in-DOM custom control)",
|
|
28
|
+
input: {
|
|
29
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "listbox", classPattern: "vue-treeselect vue-treeselect--single", dataAttrKeys: [] },
|
|
30
|
+
descendants: [
|
|
31
|
+
{ tag: "div", role: "", ariaHasPopup: "", classPattern: "vue-treeselect__control", dataAttrKeys: [] },
|
|
32
|
+
{ tag: "div", role: "", ariaHasPopup: "", classPattern: "vue-treeselect__menu", dataAttrKeys: [] }
|
|
33
|
+
],
|
|
34
|
+
portal: null
|
|
35
|
+
},
|
|
36
|
+
expectedHash: "d7e908c767fa68cd"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "radix-select",
|
|
40
|
+
description: "Radix Select trigger with portal popper",
|
|
41
|
+
input: {
|
|
42
|
+
root: { tag: "button", role: "combobox", ariaHasPopup: "listbox", classPattern: "", dataAttrKeys: ["data-radix-select-trigger"] },
|
|
43
|
+
descendants: [
|
|
44
|
+
{ tag: "span", role: "", ariaHasPopup: "", classPattern: "", dataAttrKeys: ["data-radix-select-value"] }
|
|
45
|
+
],
|
|
46
|
+
portal: { tag: "div", role: "", ariaHasPopup: "", classPattern: "", dataAttrKeys: ["data-radix-popper-content-wrapper"] }
|
|
47
|
+
},
|
|
48
|
+
expectedHash: "024c3db1d8f29870"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "stripe-card-number",
|
|
52
|
+
description: "Stripe CardNumberElement iframe",
|
|
53
|
+
input: {
|
|
54
|
+
root: { tag: "iframe", role: "", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
55
|
+
descendants: [],
|
|
56
|
+
portal: null
|
|
57
|
+
},
|
|
58
|
+
expectedHash: "27114237c0e4a14a"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "mui-select",
|
|
62
|
+
description: "MUI Select trigger (placeholder for follow-up curated add)",
|
|
63
|
+
input: {
|
|
64
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "listbox", classPattern: "MuiSelect-root MuiSelect-standard", dataAttrKeys: [] },
|
|
65
|
+
descendants: [
|
|
66
|
+
{ tag: "div", role: "", ariaHasPopup: "", classPattern: "MuiSelect-select", dataAttrKeys: [] }
|
|
67
|
+
],
|
|
68
|
+
portal: null
|
|
69
|
+
},
|
|
70
|
+
expectedHash: "bb8e91d32ffd6aa1"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "antd-select",
|
|
74
|
+
description: "Ant Design Select trigger",
|
|
75
|
+
input: {
|
|
76
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "listbox", classPattern: "ant-select-selector", dataAttrKeys: [] },
|
|
77
|
+
descendants: [
|
|
78
|
+
{ tag: "span", role: "", ariaHasPopup: "", classPattern: "ant-select-selection-item", dataAttrKeys: [] }
|
|
79
|
+
],
|
|
80
|
+
portal: null
|
|
81
|
+
},
|
|
82
|
+
expectedHash: "6787396f9da6022e"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "headlessui-listbox",
|
|
86
|
+
description: "Headless UI Listbox trigger with portal",
|
|
87
|
+
input: {
|
|
88
|
+
root: { tag: "button", role: "combobox", ariaHasPopup: "listbox", classPattern: "", dataAttrKeys: ["data-headlessui-state"] },
|
|
89
|
+
descendants: [],
|
|
90
|
+
portal: { tag: "div", role: "listbox", ariaHasPopup: "", classPattern: "", dataAttrKeys: ["data-headlessui-state"] }
|
|
91
|
+
},
|
|
92
|
+
expectedHash: "6016cd2641c63075"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "native-button",
|
|
96
|
+
description: "Plain native button (should not normally be fingerprinted; included to verify low collision surface)",
|
|
97
|
+
input: {
|
|
98
|
+
root: { tag: "button", role: "button", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
99
|
+
descendants: [],
|
|
100
|
+
portal: null
|
|
101
|
+
},
|
|
102
|
+
expectedHash: "15d8dd75a3c6eacf"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "vue-treeselect-multi",
|
|
106
|
+
description: "Vue-Treeselect in multi-select mode (separate fingerprint from single)",
|
|
107
|
+
input: {
|
|
108
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "listbox", classPattern: "vue-treeselect vue-treeselect--multi", dataAttrKeys: [] },
|
|
109
|
+
descendants: [
|
|
110
|
+
{ tag: "div", role: "", ariaHasPopup: "", classPattern: "vue-treeselect__control", dataAttrKeys: [] }
|
|
111
|
+
],
|
|
112
|
+
portal: null
|
|
113
|
+
},
|
|
114
|
+
expectedHash: "7af3161b147762a3"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "date-picker-custom",
|
|
118
|
+
description: "Generic custom date-picker (unrecognized; LLM-inference path target)",
|
|
119
|
+
input: {
|
|
120
|
+
root: { tag: "div", role: "combobox", ariaHasPopup: "dialog", classPattern: "", dataAttrKeys: [] },
|
|
121
|
+
descendants: [
|
|
122
|
+
{ tag: "input", role: "textbox", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
123
|
+
{ tag: "button", role: "button", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] }
|
|
124
|
+
],
|
|
125
|
+
portal: null
|
|
126
|
+
},
|
|
127
|
+
expectedHash: "3e1cd8161b90817f"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "toast-live-region",
|
|
131
|
+
description: "ARIA-compliant live region for toasts",
|
|
132
|
+
input: {
|
|
133
|
+
root: { tag: "div", role: "status", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] },
|
|
134
|
+
descendants: [
|
|
135
|
+
{ tag: "div", role: "alert", ariaHasPopup: "", classPattern: "", dataAttrKeys: [] }
|
|
136
|
+
],
|
|
137
|
+
portal: null
|
|
138
|
+
},
|
|
139
|
+
expectedHash: "3f1e5b1bc0523ba6"
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
143
|
+
0 && (module.exports = {
|
|
144
|
+
ABLATION_FIXTURES
|
|
145
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_fingerprint = require("./fingerprint");
|
|
3
|
+
var import_fingerprintAblation = require("./fingerprintAblation.fixtures");
|
|
4
|
+
const PLACEHOLDER = "__REPLACE_AFTER_FIRST_RUN__";
|
|
5
|
+
const actual = import_fingerprintAblation.ABLATION_FIXTURES.map((entry) => ({
|
|
6
|
+
name: entry.name,
|
|
7
|
+
hash: (0, import_fingerprint.computeFingerprintFromInput)(entry.input),
|
|
8
|
+
expected: entry.expectedHash
|
|
9
|
+
}));
|
|
10
|
+
const placeholders = actual.filter((a) => a.expected === PLACEHOLDER);
|
|
11
|
+
const locked = actual.filter((a) => a.expected !== PLACEHOLDER);
|
|
12
|
+
let drifted = 0;
|
|
13
|
+
for (const a of locked) {
|
|
14
|
+
if (a.hash === a.expected) {
|
|
15
|
+
console.log(" \u2713", a.name, a.hash);
|
|
16
|
+
} else {
|
|
17
|
+
drifted++;
|
|
18
|
+
console.log(" \u2717", a.name);
|
|
19
|
+
console.log(" expected:", a.expected);
|
|
20
|
+
console.log(" got :", a.hash);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const uniq = new Set(actual.map((a) => a.hash));
|
|
24
|
+
if (uniq.size !== actual.length) {
|
|
25
|
+
drifted++;
|
|
26
|
+
console.log(" \u2717 COLLISION \u2014 two or more fixtures share the same fingerprint hash");
|
|
27
|
+
}
|
|
28
|
+
if (placeholders.length > 0) {
|
|
29
|
+
console.log("\nBootstrap mode \u2014 paste these hashes into fingerprintAblation.fixtures.ts:");
|
|
30
|
+
for (const a of placeholders)
|
|
31
|
+
console.log(` ${a.name}: '${a.hash}',`);
|
|
32
|
+
console.log("\nThen re-run the test. (Note: locked entries above were still verified; any \u2717 are real drift and must be investigated, not re-bootstrapped.)");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (drifted > 0) {
|
|
36
|
+
console.log(`
|
|
37
|
+
${drifted} failed`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
console.log(`
|
|
41
|
+
${actual.length} passed`);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var graph_exports = {};
|
|
20
|
+
__export(graph_exports, {
|
|
21
|
+
urlToFilename: () => urlToFilename
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(graph_exports);
|
|
24
|
+
function urlToFilename(url) {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = new URL(url);
|
|
27
|
+
const path = (parsed.hostname + parsed.pathname).replace(/\/+$/, "").replace(/[^a-zA-Z0-9._-]/g, "__");
|
|
28
|
+
return path || "index";
|
|
29
|
+
} catch {
|
|
30
|
+
return url.replace(/[^a-zA-Z0-9._-]/g, "__") || "page";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
34
|
+
0 && (module.exports = {
|
|
35
|
+
urlToFilename
|
|
36
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var liveFingerprints_exports = {};
|
|
20
|
+
__export(liveFingerprints_exports, {
|
|
21
|
+
fp: () => fp,
|
|
22
|
+
liveFingerprintNames: () => liveFingerprintNames
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(liveFingerprints_exports);
|
|
25
|
+
const LIVE_FINGERPRINTS = {
|
|
26
|
+
"vue-treeselect": "b83426fe818e01ee",
|
|
27
|
+
"radix-select": "738a5387548e9319",
|
|
28
|
+
"stripe-frame": "74e8e04695fbc142"
|
|
29
|
+
};
|
|
30
|
+
function fp(name) {
|
|
31
|
+
const hash = LIVE_FINGERPRINTS[name];
|
|
32
|
+
if (!hash)
|
|
33
|
+
throw new Error(`live fingerprint '${name}' not found in liveFingerprints.ts`);
|
|
34
|
+
return hash;
|
|
35
|
+
}
|
|
36
|
+
function liveFingerprintNames() {
|
|
37
|
+
return Object.keys(LIVE_FINGERPRINTS);
|
|
38
|
+
}
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
fp,
|
|
42
|
+
liveFingerprintNames
|
|
43
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var logicalNameResolver_exports = {};
|
|
20
|
+
__export(logicalNameResolver_exports, {
|
|
21
|
+
resolveCollisions: () => resolveCollisions
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(logicalNameResolver_exports);
|
|
24
|
+
var import_slug = require("./slug");
|
|
25
|
+
function resolveCollisions(inputs) {
|
|
26
|
+
const groupsByKey = /* @__PURE__ */ new Map();
|
|
27
|
+
inputs.forEach((inp, idx) => {
|
|
28
|
+
const key = `${inp.sectionName}::${inp.initialName}`;
|
|
29
|
+
if (!groupsByKey.has(key)) groupsByKey.set(key, []);
|
|
30
|
+
groupsByKey.get(key).push(idx);
|
|
31
|
+
});
|
|
32
|
+
const output = new Array(inputs.length);
|
|
33
|
+
const slugOrNull = (s) => {
|
|
34
|
+
if (!s) return null;
|
|
35
|
+
const slugged = (0, import_slug.slug)(s);
|
|
36
|
+
return slugged ? slugged : null;
|
|
37
|
+
};
|
|
38
|
+
for (const [, indices] of groupsByKey.entries()) {
|
|
39
|
+
if (indices.length === 1) {
|
|
40
|
+
output[indices[0]] = inputs[indices[0]].initialName;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const candidateNames = indices.map((i) => {
|
|
44
|
+
const inp = inputs[i];
|
|
45
|
+
const testIdSlug = slugOrNull(inp.testId);
|
|
46
|
+
if (testIdSlug) return `${testIdSlug}_${inp.initialName}`;
|
|
47
|
+
const stableIdSlug = slugOrNull(inp.stableId);
|
|
48
|
+
if (stableIdSlug) return `${stableIdSlug}_${inp.initialName}`;
|
|
49
|
+
const ancestorSlug = slugOrNull(inp.nearestAncestorId);
|
|
50
|
+
if (ancestorSlug) return `${ancestorSlug}_${inp.initialName}`;
|
|
51
|
+
return null;
|
|
52
|
+
});
|
|
53
|
+
const nonNull = candidateNames.filter((n) => n !== null);
|
|
54
|
+
const hasDuplicates = new Set(nonNull).size !== nonNull.length;
|
|
55
|
+
const anyNull = candidateNames.some((n) => n === null);
|
|
56
|
+
if (anyNull || hasDuplicates) {
|
|
57
|
+
indices.forEach((idx, posWithinGroup) => {
|
|
58
|
+
const inp = inputs[idx];
|
|
59
|
+
output[idx] = `${inp.sectionName}_${inp.initialName}_${posWithinGroup + 1}`;
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
indices.forEach((idx, k) => {
|
|
63
|
+
output[idx] = candidateNames[k];
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return output;
|
|
68
|
+
}
|
|
69
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
70
|
+
0 && (module.exports = {
|
|
71
|
+
resolveCollisions
|
|
72
|
+
});
|