pw-core 1.2.0 → 1.2.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/README.md +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +777 -0
- package/dist/codegen/config/context-weights.d.ts +8 -0
- package/dist/codegen/config/context-weights.js +11 -0
- package/dist/codegen/config/ignored-attributes.d.ts +1 -0
- package/dist/codegen/config/ignored-attributes.js +4 -0
- package/dist/codegen/config/ignored-classes.d.ts +1 -0
- package/dist/codegen/config/ignored-classes.js +144 -0
- package/dist/codegen/config/ignored-values.d.ts +1 -0
- package/dist/codegen/config/ignored-values.js +4 -0
- package/dist/codegen/config/index.d.ts +29 -0
- package/dist/codegen/config/index.js +17 -0
- package/dist/codegen/config/strategy-order.d.ts +1 -0
- package/dist/codegen/config/strategy-order.js +18 -0
- package/dist/codegen/config/weights.d.ts +15 -0
- package/dist/codegen/config/weights.js +18 -0
- package/dist/codegen/floating-panel/client.d.ts +1 -0
- package/dist/codegen/floating-panel/client.js +248 -0
- package/dist/codegen/floating-panel/index.d.ts +4 -0
- package/dist/codegen/floating-panel/index.js +20 -0
- package/dist/codegen/floating-panel/manager.d.ts +19 -0
- package/dist/codegen/floating-panel/manager.js +90 -0
- package/dist/codegen/floating-panel/template.d.ts +2 -0
- package/dist/codegen/floating-panel/template.js +58 -0
- package/dist/codegen/floating-panel/types.d.ts +7 -0
- package/dist/codegen/floating-panel/types.js +2 -0
- package/dist/codegen/generator/action.validator.d.ts +5 -0
- package/dist/codegen/generator/action.validator.js +27 -0
- package/dist/codegen/generator/index.d.ts +13 -0
- package/dist/codegen/generator/index.js +724 -0
- package/dist/codegen/generator/locator.restricted.d.ts +36 -0
- package/dist/codegen/generator/locator.restricted.js +127 -0
- package/dist/codegen/generator/locator.validator.d.ts +8 -0
- package/dist/codegen/generator/locator.validator.js +38 -0
- package/dist/codegen/hover-tracker/client.d.ts +1 -0
- package/dist/codegen/hover-tracker/client.js +75 -0
- package/dist/codegen/hover-tracker/index.d.ts +3 -0
- package/dist/codegen/hover-tracker/index.js +19 -0
- package/dist/codegen/hover-tracker/manager.d.ts +11 -0
- package/dist/codegen/hover-tracker/manager.js +28 -0
- package/dist/codegen/hover-tracker/types.d.ts +3 -0
- package/dist/codegen/hover-tracker/types.js +2 -0
- package/dist/codegen/index.d.ts +35 -0
- package/dist/codegen/index.js +1147 -0
- package/dist/codegen/key-utils.d.ts +23 -0
- package/dist/codegen/key-utils.js +68 -0
- package/dist/codegen/scorer/base-score.d.ts +1 -0
- package/dist/codegen/scorer/base-score.js +7 -0
- package/dist/codegen/scorer/context-score.d.ts +2 -0
- package/dist/codegen/scorer/context-score.js +27 -0
- package/dist/codegen/scorer/index.d.ts +5 -0
- package/dist/codegen/scorer/index.js +23 -0
- package/dist/codegen/scorer/locator.ranking.d.ts +15 -0
- package/dist/codegen/scorer/locator.ranking.js +83 -0
- package/dist/codegen/scorer/semantic-score.d.ts +1 -0
- package/dist/codegen/scorer/semantic-score.js +47 -0
- package/dist/codegen/types.d.ts +35 -0
- package/dist/codegen/types.js +2 -0
- package/dist/component/table.js +14 -10
- package/dist/constants/methods.d.ts +2 -0
- package/dist/constants/methods.js +33 -0
- package/dist/constants/strategies.d.ts +2 -0
- package/dist/constants/strategies.js +47 -0
- package/dist/page/actions/locator-actions.js +15 -4
- package/dist/page/assertions/verify-chain.d.ts +39 -32
- package/dist/page/assertions/verify-chain.js +18 -4
- package/dist/page/assertions/verify-helpers.js +12 -3
- package/dist/page/config.d.ts +109 -71
- package/dist/page/config.js +10 -31
- package/dist/page/locators/dynamic-locator-resolver.js +29 -9
- package/dist/page/locators/resolver.d.ts +15 -15
- package/dist/page/locators/resolver.js +151 -32
- package/dist/page/registry.d.ts +43 -6
- package/dist/page/registry.js +90 -18
- package/dist/page/typed-page.d.ts +33 -29
- package/dist/page/typed-page.js +40 -11
- package/dist/page/types/proxy-methods.d.ts +69 -54
- package/dist/page/types/validation.d.ts +48 -1
- package/dist/page/utils/caller-location.js +13 -9
- package/dist/page/utils/formatter.js +6 -3
- package/dist/types/methods.d.ts +4 -0
- package/dist/types/methods.js +2 -0
- package/dist/types/strategies.d.ts +17 -0
- package/dist/types/strategies.js +2 -0
- package/package.json +8 -1
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
36
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.getSelectorValue = getSelectorValue;
|
|
40
|
+
exports.findRegistryFile = findRegistryFile;
|
|
41
|
+
exports.findPlaywrightConfig = findPlaywrightConfig;
|
|
42
|
+
exports.parseRegistry = parseRegistry;
|
|
43
|
+
exports.matchDynamicEntry = matchDynamicEntry;
|
|
44
|
+
exports.writeRegistry = writeRegistry;
|
|
45
|
+
exports.findPageKey = findPageKey;
|
|
46
|
+
exports.toCamelCase = toCamelCase;
|
|
47
|
+
exports.findMatchInDict = findMatchInDict;
|
|
48
|
+
exports.findElementKey = findElementKey;
|
|
49
|
+
exports.formatActionCall = formatActionCall;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const key_utils_1 = require("./key-utils");
|
|
53
|
+
function getSelectorValue(selector) {
|
|
54
|
+
// 1. Try to find data-testid first
|
|
55
|
+
const testIdRegexes = [
|
|
56
|
+
/internal:testid=\[data-testid="([^"]+)"[si]?\]/,
|
|
57
|
+
/data-testid="([^"]+)"[si]?/,
|
|
58
|
+
/\[data-testid="([^"]+)"[si]?\]/,
|
|
59
|
+
/data-testid=([^\[\]\s]+)/
|
|
60
|
+
];
|
|
61
|
+
for (const regex of testIdRegexes) {
|
|
62
|
+
const match = selector.match(regex);
|
|
63
|
+
if (match) {
|
|
64
|
+
let val = match[1];
|
|
65
|
+
val = val.replace(/^["']|["']$/g, '');
|
|
66
|
+
return { type: 'testId', val };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// 2. Playwright getByRole
|
|
70
|
+
const roleRegex = /internal:role=([a-zA-Z0-9_-]+)\[name="([^"]+)"i?\]/;
|
|
71
|
+
const roleMatch = selector.match(roleRegex);
|
|
72
|
+
if (roleMatch) {
|
|
73
|
+
let type = roleMatch[1];
|
|
74
|
+
if (type === 'img')
|
|
75
|
+
type = 'altText';
|
|
76
|
+
return { type, val: roleMatch[2] };
|
|
77
|
+
}
|
|
78
|
+
const roleSimpleRegex = /role=([a-zA-Z0-9_-]+)/;
|
|
79
|
+
const roleSimpleMatch = selector.match(roleSimpleRegex);
|
|
80
|
+
if (roleSimpleMatch) {
|
|
81
|
+
return { type: roleSimpleMatch[1], val: roleSimpleMatch[1] };
|
|
82
|
+
}
|
|
83
|
+
// 3. Playwright getByLabel
|
|
84
|
+
const labelRegex = /internal:label="([^"]+)"i?/;
|
|
85
|
+
const labelMatch = selector.match(labelRegex);
|
|
86
|
+
if (labelMatch) {
|
|
87
|
+
return { type: 'label', val: labelMatch[1] };
|
|
88
|
+
}
|
|
89
|
+
// 4. Playwright getByPlaceholder
|
|
90
|
+
const placeholderRegexes = [
|
|
91
|
+
/internal:placeholder="([^"]+)"i?/,
|
|
92
|
+
/internal:attr=\[placeholder="([^"]+)"[si]?\]/
|
|
93
|
+
];
|
|
94
|
+
for (const regex of placeholderRegexes) {
|
|
95
|
+
const placeholderMatch = selector.match(regex);
|
|
96
|
+
if (placeholderMatch) {
|
|
97
|
+
return { type: 'placeholder', val: placeholderMatch[1] };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// 5. Playwright getByAltText
|
|
101
|
+
const altRegexes = [
|
|
102
|
+
/internal:alt="([^"]+)"i?/,
|
|
103
|
+
/internal:attr=\[alt="([^"]+)"[si]?\]/
|
|
104
|
+
];
|
|
105
|
+
for (const regex of altRegexes) {
|
|
106
|
+
const altMatch = selector.match(regex);
|
|
107
|
+
if (altMatch) {
|
|
108
|
+
return { type: 'altText', val: altMatch[1] };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// 6. Playwright getByTitle
|
|
112
|
+
const titleRegexes = [
|
|
113
|
+
/internal:title="([^"]+)"i?/,
|
|
114
|
+
/internal:attr=\[title="([^"]+)"[si]?\]/
|
|
115
|
+
];
|
|
116
|
+
for (const regex of titleRegexes) {
|
|
117
|
+
const titleMatch = selector.match(regex);
|
|
118
|
+
if (titleMatch) {
|
|
119
|
+
return { type: 'title', val: titleMatch[1] };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// 7. Playwright getByText
|
|
123
|
+
const textRegex = /(?:internal:text|text)="([^"]+)"i?/;
|
|
124
|
+
const textMatch = selector.match(textRegex);
|
|
125
|
+
if (textMatch) {
|
|
126
|
+
return { type: 'text', val: textMatch[1] };
|
|
127
|
+
}
|
|
128
|
+
const textSimpleRegex = /(?:internal:text|text)=([^"\s]+)/;
|
|
129
|
+
const textSimpleMatch = selector.match(textSimpleRegex);
|
|
130
|
+
if (textSimpleMatch) {
|
|
131
|
+
return { type: 'text', val: textSimpleMatch[1] };
|
|
132
|
+
}
|
|
133
|
+
// 8. Try to find ID selector
|
|
134
|
+
const idRegexes = [/id=([a-zA-Z0-9_-]+)/, /#([a-zA-Z0-9_-]+)/];
|
|
135
|
+
for (const regex of idRegexes) {
|
|
136
|
+
const match = selector.match(regex);
|
|
137
|
+
if (match) {
|
|
138
|
+
return { type: 'selector', val: `#${match[1]}` };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// 9. Try to find class selectors
|
|
142
|
+
const classRegexes = [/\.([a-zA-Z0-9_-]+)/];
|
|
143
|
+
for (const regex of classRegexes) {
|
|
144
|
+
const match = selector.match(regex);
|
|
145
|
+
if (match) {
|
|
146
|
+
return { type: 'selector', val: `.${match[1]}` };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// 10. Fallback to raw selector
|
|
150
|
+
return { type: 'selector', val: selector };
|
|
151
|
+
}
|
|
152
|
+
function findRegistryFile(dir) {
|
|
153
|
+
const queue = [dir];
|
|
154
|
+
while (queue.length > 0) {
|
|
155
|
+
const current = queue.shift();
|
|
156
|
+
try {
|
|
157
|
+
const files = fs.readdirSync(current);
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
const fullPath = path.join(current, file);
|
|
160
|
+
if (file === 'node_modules' || file === '.git' || file === 'dist')
|
|
161
|
+
continue;
|
|
162
|
+
const stat = fs.statSync(fullPath);
|
|
163
|
+
if (stat.isDirectory()) {
|
|
164
|
+
queue.push(fullPath);
|
|
165
|
+
}
|
|
166
|
+
else if (file === 'registry.ts' || file === 'registry.js') {
|
|
167
|
+
try {
|
|
168
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
169
|
+
if (content.includes('createPageRegistry(')) {
|
|
170
|
+
return fullPath;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (e) { }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (e) { }
|
|
178
|
+
}
|
|
179
|
+
let parent = path.dirname(dir);
|
|
180
|
+
while (parent !== dir) {
|
|
181
|
+
const checkPath = path.join(parent, 'registry.ts');
|
|
182
|
+
if (fs.existsSync(checkPath)) {
|
|
183
|
+
try {
|
|
184
|
+
const content = fs.readFileSync(checkPath, 'utf8');
|
|
185
|
+
if (content.includes('createPageRegistry('))
|
|
186
|
+
return checkPath;
|
|
187
|
+
}
|
|
188
|
+
catch (e) { }
|
|
189
|
+
}
|
|
190
|
+
const checkPathSrc = path.join(parent, 'src', 'pages', 'registry.ts');
|
|
191
|
+
if (fs.existsSync(checkPathSrc)) {
|
|
192
|
+
try {
|
|
193
|
+
const content = fs.readFileSync(checkPathSrc, 'utf8');
|
|
194
|
+
if (content.includes('createPageRegistry('))
|
|
195
|
+
return checkPathSrc;
|
|
196
|
+
}
|
|
197
|
+
catch (e) { }
|
|
198
|
+
}
|
|
199
|
+
dir = parent;
|
|
200
|
+
parent = path.dirname(dir);
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
function findPlaywrightConfig(dir) {
|
|
205
|
+
const queue = [dir];
|
|
206
|
+
while (queue.length > 0) {
|
|
207
|
+
const current = queue.shift();
|
|
208
|
+
try {
|
|
209
|
+
const files = fs.readdirSync(current);
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
if (file === 'node_modules' || file === '.git' || file === 'dist')
|
|
212
|
+
continue;
|
|
213
|
+
const fullPath = path.join(current, file);
|
|
214
|
+
const stat = fs.statSync(fullPath);
|
|
215
|
+
if (stat.isDirectory()) {
|
|
216
|
+
queue.push(fullPath);
|
|
217
|
+
}
|
|
218
|
+
else if (file === 'playwright.config.ts' || file === 'playwright.config.js') {
|
|
219
|
+
return fullPath;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (e) { }
|
|
224
|
+
}
|
|
225
|
+
let parent = path.dirname(dir);
|
|
226
|
+
while (parent !== dir) {
|
|
227
|
+
const tsPath = path.join(parent, 'playwright.config.ts');
|
|
228
|
+
const jsPath = path.join(parent, 'playwright.config.js');
|
|
229
|
+
if (fs.existsSync(tsPath))
|
|
230
|
+
return tsPath;
|
|
231
|
+
if (fs.existsSync(jsPath))
|
|
232
|
+
return jsPath;
|
|
233
|
+
dir = parent;
|
|
234
|
+
parent = path.dirname(dir);
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
function findRegistryCallBounds(content) {
|
|
239
|
+
const matchIndex = content.indexOf('createPageRegistry(');
|
|
240
|
+
if (matchIndex === -1)
|
|
241
|
+
return null;
|
|
242
|
+
const startIdx = matchIndex + 'createPageRegistry('.length;
|
|
243
|
+
let braceCount = 0;
|
|
244
|
+
for (let i = startIdx; i < content.length; i++) {
|
|
245
|
+
const char = content[i];
|
|
246
|
+
if (char === '(')
|
|
247
|
+
braceCount++;
|
|
248
|
+
else if (char === ')') {
|
|
249
|
+
if (braceCount === 0) {
|
|
250
|
+
return { startIdx, endIdx: i };
|
|
251
|
+
}
|
|
252
|
+
braceCount--;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function parseRegistry(filePath) {
|
|
258
|
+
try {
|
|
259
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
260
|
+
const bounds = findRegistryCallBounds(content);
|
|
261
|
+
if (bounds) {
|
|
262
|
+
const objectLiteralText = content.substring(bounds.startIdx, bounds.endIdx).trim();
|
|
263
|
+
return new Function(`return (${objectLiteralText})`)();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
console.error('Error parsing registry.ts:', e);
|
|
268
|
+
}
|
|
269
|
+
return {};
|
|
270
|
+
}
|
|
271
|
+
function matchDynamicEntry(val, entry, isTestId) {
|
|
272
|
+
const pattern = isTestId ? entry.testId : entry.selector;
|
|
273
|
+
if (typeof pattern !== 'string')
|
|
274
|
+
return { matches: false };
|
|
275
|
+
const placeholders = Object.keys(entry).filter((k) => k !== 'testId' && k !== 'selector');
|
|
276
|
+
if (placeholders.length === 0)
|
|
277
|
+
return { matches: false };
|
|
278
|
+
const placeholder = placeholders[0];
|
|
279
|
+
let placeholderPattern = `{${placeholder}}`;
|
|
280
|
+
if (!pattern.includes(placeholderPattern)) {
|
|
281
|
+
placeholderPattern = placeholder;
|
|
282
|
+
}
|
|
283
|
+
const token = '__PLACEHOLDER_REGEX__';
|
|
284
|
+
const tempPattern = pattern.replace(placeholderPattern, token);
|
|
285
|
+
const escaped = tempPattern.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
286
|
+
const regexStr = '^' + escaped.replace(token, '([a-zA-Z0-9_ -]+)') + '$';
|
|
287
|
+
try {
|
|
288
|
+
const regex = new RegExp(regexStr);
|
|
289
|
+
const match = val.match(regex);
|
|
290
|
+
if (match) {
|
|
291
|
+
return {
|
|
292
|
+
matches: true,
|
|
293
|
+
matchedValue: match[1],
|
|
294
|
+
placeholderName: placeholder
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (e) { }
|
|
299
|
+
return { matches: false };
|
|
300
|
+
}
|
|
301
|
+
function findCommonPrefix(values) {
|
|
302
|
+
if (values.length === 0)
|
|
303
|
+
return '';
|
|
304
|
+
const first = values[0];
|
|
305
|
+
const parts = first.split(/[-_]/);
|
|
306
|
+
let common = '';
|
|
307
|
+
for (let i = 0; i < parts.length; i++) {
|
|
308
|
+
const candidate = parts.slice(0, i + 1).join('-');
|
|
309
|
+
const matchesAll = values.every((v) => v.startsWith(candidate + '-') || v.startsWith(candidate + '_'));
|
|
310
|
+
if (matchesAll) {
|
|
311
|
+
common = candidate;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return common;
|
|
318
|
+
}
|
|
319
|
+
function collapsePageDict(dict, isTestId, keyReplacements) {
|
|
320
|
+
const result = {};
|
|
321
|
+
const dynamicEntries = {};
|
|
322
|
+
const staticEntries = {};
|
|
323
|
+
for (const k of Object.keys(dict)) {
|
|
324
|
+
if (typeof dict[k] === 'object' && dict[k] !== null) {
|
|
325
|
+
dynamicEntries[k] = dict[k];
|
|
326
|
+
}
|
|
327
|
+
else if (typeof dict[k] === 'string') {
|
|
328
|
+
staticEntries[k] = dict[k];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const collapsedKeys = new Set();
|
|
332
|
+
// 1. Try to merge static entries into existing dynamic entries first
|
|
333
|
+
for (const dk of Object.keys(dynamicEntries)) {
|
|
334
|
+
const entry = dynamicEntries[dk];
|
|
335
|
+
const placeholders = Object.keys(entry).filter((pk) => pk !== 'testId' && pk !== 'selector');
|
|
336
|
+
if (placeholders.length === 0)
|
|
337
|
+
continue;
|
|
338
|
+
const placeholder = placeholders[0];
|
|
339
|
+
for (const k of Object.keys(staticEntries)) {
|
|
340
|
+
const val = staticEntries[k];
|
|
341
|
+
const matchRes = matchDynamicEntry(val, entry, isTestId);
|
|
342
|
+
if (matchRes.matches && matchRes.matchedValue) {
|
|
343
|
+
if (!entry[placeholder].includes(matchRes.matchedValue)) {
|
|
344
|
+
entry[placeholder].push(matchRes.matchedValue);
|
|
345
|
+
}
|
|
346
|
+
collapsedKeys.add(k);
|
|
347
|
+
const capValue = matchRes.matchedValue.charAt(0).toUpperCase() + matchRes.matchedValue.slice(1);
|
|
348
|
+
let newKey = dk.replace(`{${placeholder}}`, capValue);
|
|
349
|
+
newKey = newKey.charAt(0).toLowerCase() + newKey.slice(1);
|
|
350
|
+
keyReplacements[k] = newKey;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const groups = {};
|
|
355
|
+
const staticKeys = Object.keys(staticEntries);
|
|
356
|
+
for (const k of staticKeys) {
|
|
357
|
+
if (collapsedKeys.has(k))
|
|
358
|
+
continue;
|
|
359
|
+
const val = staticEntries[k];
|
|
360
|
+
const isCssOrSpecial = val.includes('[') ||
|
|
361
|
+
val.includes(']') ||
|
|
362
|
+
val.includes('=') ||
|
|
363
|
+
val.includes(':') ||
|
|
364
|
+
val.includes('.') ||
|
|
365
|
+
val.includes('#');
|
|
366
|
+
const idSelectorMatch = val.match(/^#([a-zA-Z0-9_-]+)[-_]([a-zA-Z0-9_-]+)$/);
|
|
367
|
+
const classSelectorMatch = val.match(/^\.([a-zA-Z0-9_-]+)[-_]([a-zA-Z0-9_-]+)$/);
|
|
368
|
+
const testIdMatch = !isCssOrSpecial ? val.match(/^(.*?)[-_]([^-]+)$/) : null;
|
|
369
|
+
const attrMatch = val.match(/^\[([a-zA-Z0-9_-]+)="([^"]+)"\]$/);
|
|
370
|
+
const roleMatch = val.match(/^internal:role=([a-zA-Z0-9_-]+)\[name="([^"]+)"i\]$/);
|
|
371
|
+
const textMatch = val.match(/^(?:text|internal:text)=["']?([^"']+)["']?i?$/);
|
|
372
|
+
if (idSelectorMatch) {
|
|
373
|
+
const prefix = idSelectorMatch[1];
|
|
374
|
+
const suffix = idSelectorMatch[2];
|
|
375
|
+
const groupKey = `idSelector:${prefix}`;
|
|
376
|
+
if (!groups[groupKey]) {
|
|
377
|
+
groups[groupKey] = {
|
|
378
|
+
keys: [],
|
|
379
|
+
values: [],
|
|
380
|
+
prefix,
|
|
381
|
+
suffixPattern: 'idSelector'
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
groups[groupKey].keys.push(k);
|
|
385
|
+
groups[groupKey].values.push(suffix);
|
|
386
|
+
}
|
|
387
|
+
else if (classSelectorMatch) {
|
|
388
|
+
const prefix = classSelectorMatch[1];
|
|
389
|
+
const suffix = classSelectorMatch[2];
|
|
390
|
+
const groupKey = `classSelector:${prefix}`;
|
|
391
|
+
if (!groups[groupKey]) {
|
|
392
|
+
groups[groupKey] = {
|
|
393
|
+
keys: [],
|
|
394
|
+
values: [],
|
|
395
|
+
prefix,
|
|
396
|
+
suffixPattern: 'classSelector'
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
groups[groupKey].keys.push(k);
|
|
400
|
+
groups[groupKey].values.push(suffix);
|
|
401
|
+
}
|
|
402
|
+
else if (testIdMatch) {
|
|
403
|
+
const prefix = testIdMatch[1];
|
|
404
|
+
const suffix = testIdMatch[2];
|
|
405
|
+
const groupKey = `testId:${prefix}`;
|
|
406
|
+
if (!groups[groupKey]) {
|
|
407
|
+
groups[groupKey] = {
|
|
408
|
+
keys: [],
|
|
409
|
+
values: [],
|
|
410
|
+
prefix,
|
|
411
|
+
suffixPattern: 'testId'
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
groups[groupKey].keys.push(k);
|
|
415
|
+
groups[groupKey].values.push(suffix);
|
|
416
|
+
}
|
|
417
|
+
else if (attrMatch) {
|
|
418
|
+
const attrName = attrMatch[1];
|
|
419
|
+
const attrValue = attrMatch[2];
|
|
420
|
+
const groupKey = `attr:${attrName}`;
|
|
421
|
+
if (!groups[groupKey]) {
|
|
422
|
+
groups[groupKey] = {
|
|
423
|
+
keys: [],
|
|
424
|
+
values: [],
|
|
425
|
+
prefix: attrName,
|
|
426
|
+
suffixPattern: 'attr'
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
groups[groupKey].keys.push(k);
|
|
430
|
+
groups[groupKey].values.push(attrValue);
|
|
431
|
+
}
|
|
432
|
+
else if (roleMatch) {
|
|
433
|
+
const roleVal = roleMatch[1];
|
|
434
|
+
const nameVal = roleMatch[2];
|
|
435
|
+
const groupKey = `role:${roleVal}`;
|
|
436
|
+
if (!groups[groupKey]) {
|
|
437
|
+
groups[groupKey] = {
|
|
438
|
+
keys: [],
|
|
439
|
+
values: [],
|
|
440
|
+
prefix: roleVal,
|
|
441
|
+
suffixPattern: 'role'
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
groups[groupKey].keys.push(k);
|
|
445
|
+
groups[groupKey].values.push(nameVal);
|
|
446
|
+
}
|
|
447
|
+
else if (textMatch) {
|
|
448
|
+
const textValue = textMatch[1];
|
|
449
|
+
const groupKey = `text:text`;
|
|
450
|
+
if (!groups[groupKey]) {
|
|
451
|
+
groups[groupKey] = {
|
|
452
|
+
keys: [],
|
|
453
|
+
values: [],
|
|
454
|
+
prefix: 'text',
|
|
455
|
+
suffixPattern: 'text'
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
groups[groupKey].keys.push(k);
|
|
459
|
+
groups[groupKey].values.push(textValue);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
for (const gk of Object.keys(groups)) {
|
|
463
|
+
const group = groups[gk];
|
|
464
|
+
if (group.keys.length >= 2 || (group.keys.length === 1 && group.values.every((v) => /^\d+$/.test(v)))) {
|
|
465
|
+
let placeholder = 'item';
|
|
466
|
+
if (group.values.every((v) => /^\d+$/.test(v))) {
|
|
467
|
+
placeholder = 'id';
|
|
468
|
+
}
|
|
469
|
+
else if (group.prefix.toLowerCase().includes('filter') ||
|
|
470
|
+
group.prefix.toLowerCase().includes('button') ||
|
|
471
|
+
group.prefix.toLowerCase().includes('btn')) {
|
|
472
|
+
placeholder = 'btn';
|
|
473
|
+
}
|
|
474
|
+
let dynamicKey = '';
|
|
475
|
+
let entry = {};
|
|
476
|
+
if (group.suffixPattern === 'testId') {
|
|
477
|
+
dynamicKey = toCamelCase(group.prefix) + `{${placeholder}}`;
|
|
478
|
+
entry = {
|
|
479
|
+
[placeholder]: group.values,
|
|
480
|
+
testId: group.prefix + '-' + placeholder
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
else if (group.suffixPattern === 'idSelector') {
|
|
484
|
+
dynamicKey = toCamelCase(group.prefix) + `{${placeholder}}`;
|
|
485
|
+
entry = {
|
|
486
|
+
[placeholder]: group.values,
|
|
487
|
+
selector: `#${group.prefix}-{${placeholder}}`
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
else if (group.suffixPattern === 'classSelector') {
|
|
491
|
+
dynamicKey = toCamelCase(group.prefix) + `{${placeholder}}`;
|
|
492
|
+
entry = {
|
|
493
|
+
[placeholder]: group.values,
|
|
494
|
+
selector: `.${group.prefix}-{${placeholder}}`
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
else if (group.suffixPattern === 'attr') {
|
|
498
|
+
const commonPrefix = findCommonPrefix(group.values);
|
|
499
|
+
if (commonPrefix) {
|
|
500
|
+
dynamicKey = toCamelCase(commonPrefix) + `{${placeholder}}`;
|
|
501
|
+
entry = {
|
|
502
|
+
[placeholder]: group.values.map((v) => v.substring(commonPrefix.length + 1)),
|
|
503
|
+
selector: `[${group.prefix}="${commonPrefix}-{${placeholder}}"]`
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
dynamicKey = `{${placeholder}}`;
|
|
508
|
+
entry = {
|
|
509
|
+
[placeholder]: group.values,
|
|
510
|
+
selector: `[${group.prefix}="{${placeholder}}"]`
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
else if (group.suffixPattern === 'role') {
|
|
515
|
+
const placeholder = group.prefix === 'button' ? 'btn' : 'item';
|
|
516
|
+
dynamicKey = `{${placeholder}}`;
|
|
517
|
+
entry = {
|
|
518
|
+
[placeholder]: group.values,
|
|
519
|
+
selector: `internal:role=${group.prefix}[name="{${placeholder}}"i]`
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
else if (group.suffixPattern === 'text') {
|
|
523
|
+
dynamicKey = `{${placeholder}}`;
|
|
524
|
+
entry = {
|
|
525
|
+
[placeholder]: group.values,
|
|
526
|
+
selector: `text="{${placeholder}}"`
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
if (dynamicKey) {
|
|
530
|
+
dynamicEntries[dynamicKey] = entry;
|
|
531
|
+
group.keys.forEach((oldKey, idx) => {
|
|
532
|
+
collapsedKeys.add(oldKey);
|
|
533
|
+
const val = (entry[placeholder] ? entry[placeholder][idx] : group.values[idx]);
|
|
534
|
+
let newKey = '';
|
|
535
|
+
if (dynamicKey === `{${placeholder}}`) {
|
|
536
|
+
newKey = val;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
const capValue = val.charAt(0).toUpperCase() + val.slice(1);
|
|
540
|
+
newKey = dynamicKey.replace(`{${placeholder}}`, capValue);
|
|
541
|
+
newKey = newKey.charAt(0).toLowerCase() + newKey.slice(1);
|
|
542
|
+
}
|
|
543
|
+
keyReplacements[oldKey] = newKey;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
for (const k of Object.keys(dict)) {
|
|
549
|
+
if (collapsedKeys.has(k))
|
|
550
|
+
continue;
|
|
551
|
+
if (dynamicEntries[k]) {
|
|
552
|
+
result[k] = dynamicEntries[k];
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
result[k] = dict[k];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
for (const k of Object.keys(dynamicEntries)) {
|
|
559
|
+
if (!result[k]) {
|
|
560
|
+
result[k] = dynamicEntries[k];
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
function writeRegistry(filePath, registryObj, overrideMode, usedPageKeys) {
|
|
566
|
+
try {
|
|
567
|
+
const keyReplacements = {};
|
|
568
|
+
const codegenKeys = Object.keys(registryObj).filter((k) => {
|
|
569
|
+
const pageConfig = registryObj[k];
|
|
570
|
+
const usedSet = usedPageKeys ? (usedPageKeys instanceof Set ? usedPageKeys : new Set(usedPageKeys)) : new Set();
|
|
571
|
+
const isUsed = usedSet.has(k);
|
|
572
|
+
if (pageConfig && !isUsed) {
|
|
573
|
+
let hasLocators = false;
|
|
574
|
+
for (const sk of Object.keys(pageConfig)) {
|
|
575
|
+
if (sk !== 'url' && pageConfig[sk] && Object.keys(pageConfig[sk]).length > 0) {
|
|
576
|
+
hasLocators = true;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (!hasLocators)
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
const isDefaultCodegen = k.startsWith('codegen_') || k === 'codegenPage';
|
|
584
|
+
if (isUsed) {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
if (overrideMode) {
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
return isDefaultCodegen;
|
|
591
|
+
});
|
|
592
|
+
for (const codegenKey of codegenKeys) {
|
|
593
|
+
const pageConfig = registryObj[codegenKey];
|
|
594
|
+
if (pageConfig) {
|
|
595
|
+
if (pageConfig.testId) {
|
|
596
|
+
pageConfig.testId = collapsePageDict(pageConfig.testId, true, keyReplacements);
|
|
597
|
+
}
|
|
598
|
+
if (pageConfig.testIds) {
|
|
599
|
+
pageConfig.testIds = collapsePageDict(pageConfig.testIds, true, keyReplacements);
|
|
600
|
+
}
|
|
601
|
+
if (pageConfig.selector) {
|
|
602
|
+
pageConfig.selector = collapsePageDict(pageConfig.selector, false, keyReplacements);
|
|
603
|
+
}
|
|
604
|
+
if (pageConfig.selectors) {
|
|
605
|
+
pageConfig.selectors = collapsePageDict(pageConfig.selectors, false, keyReplacements);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
610
|
+
const serialize = (obj, indent = 2, isTestIdsOrSelectors = false) => {
|
|
611
|
+
const spaces = ' '.repeat(indent);
|
|
612
|
+
if (obj === null)
|
|
613
|
+
return 'null';
|
|
614
|
+
if (obj === undefined)
|
|
615
|
+
return 'undefined';
|
|
616
|
+
if (typeof obj === 'string') {
|
|
617
|
+
return `'${obj.replace(/'/g, "\\'")}'`;
|
|
618
|
+
}
|
|
619
|
+
if (typeof obj === 'number' || typeof obj === 'boolean') {
|
|
620
|
+
return String(obj);
|
|
621
|
+
}
|
|
622
|
+
if (Array.isArray(obj)) {
|
|
623
|
+
return '[' + obj.map((x) => serialize(x, indent, false)).join(', ') + ']';
|
|
624
|
+
}
|
|
625
|
+
if (typeof obj === 'object') {
|
|
626
|
+
let keys = Object.keys(obj);
|
|
627
|
+
if (isTestIdsOrSelectors) {
|
|
628
|
+
keys = keys.sort((a, b) => a.localeCompare(b));
|
|
629
|
+
}
|
|
630
|
+
else if (indent === 4) {
|
|
631
|
+
const order = [
|
|
632
|
+
'url', 'testId', 'testIds', 'selector', 'selectors',
|
|
633
|
+
'text', 'label', 'title', 'placeholder', 'altText',
|
|
634
|
+
'button', 'checkbox', 'radio', 'heading', 'link', 'dialog',
|
|
635
|
+
'textbox', 'searchbox', 'combobox', 'list', 'listbox',
|
|
636
|
+
'menu', 'menuitem', 'option', 'row', 'cell', 'grid', 'gridcell',
|
|
637
|
+
'tab', 'tabpanel', 'switch', 'progressbar', 'status', 'tooltip',
|
|
638
|
+
'tree', 'treeitem', 'banner', 'navigation', 'article', 'main', 'form', 'region'
|
|
639
|
+
];
|
|
640
|
+
keys = keys.sort((a, b) => {
|
|
641
|
+
const idxA = order.indexOf(a);
|
|
642
|
+
const idxB = order.indexOf(b);
|
|
643
|
+
if (idxA !== -1 && idxB !== -1)
|
|
644
|
+
return idxA - idxB;
|
|
645
|
+
if (idxA !== -1)
|
|
646
|
+
return -1;
|
|
647
|
+
if (idxB !== -1)
|
|
648
|
+
return 1;
|
|
649
|
+
return a.localeCompare(b);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
if (keys.length === 0)
|
|
653
|
+
return '{}';
|
|
654
|
+
let res = '{\n';
|
|
655
|
+
keys.forEach((key, index) => {
|
|
656
|
+
const formattedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key.replace(/'/g, "\\'")}'`;
|
|
657
|
+
const nextIsTestIdsOrSelectors = isTestIdsOrSelectors || key === 'testId' || key === 'testIds' || key === 'selector' || key === 'selectors';
|
|
658
|
+
res += `${spaces} ${formattedKey}: ${serialize(obj[key], indent + 2, nextIsTestIdsOrSelectors)}${index < keys.length - 1 ? ',\n' : '\n'}`;
|
|
659
|
+
});
|
|
660
|
+
res += `${spaces}}`;
|
|
661
|
+
return res;
|
|
662
|
+
}
|
|
663
|
+
return 'undefined';
|
|
664
|
+
};
|
|
665
|
+
for (const codegenKey of codegenKeys) {
|
|
666
|
+
const escapedKey = codegenKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
667
|
+
const keyPattern = new RegExp(`(['"\`]?${escapedKey}['"\`]?\\s*:\\s*\\{)`);
|
|
668
|
+
const match = content.match(keyPattern);
|
|
669
|
+
if (match && match.index !== undefined) {
|
|
670
|
+
const startIdx = match.index + match[0].length - 1;
|
|
671
|
+
let braceCount = 0;
|
|
672
|
+
let endIdx = -1;
|
|
673
|
+
for (let i = startIdx; i < content.length; i++) {
|
|
674
|
+
if (content[i] === '{')
|
|
675
|
+
braceCount++;
|
|
676
|
+
else if (content[i] === '}') {
|
|
677
|
+
braceCount--;
|
|
678
|
+
if (braceCount === 0) {
|
|
679
|
+
endIdx = i + 1;
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (endIdx !== -1) {
|
|
685
|
+
const updatedObjectText = serialize(registryObj[codegenKey], 2);
|
|
686
|
+
content = content.substring(0, startIdx) + updatedObjectText + content.substring(endIdx);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
const bounds = findRegistryCallBounds(content);
|
|
691
|
+
if (bounds) {
|
|
692
|
+
const lastBraceIdx = content.lastIndexOf('}', bounds.endIdx);
|
|
693
|
+
if (lastBraceIdx !== -1) {
|
|
694
|
+
const formattedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(codegenKey)
|
|
695
|
+
? codegenKey
|
|
696
|
+
: `'${codegenKey}'`;
|
|
697
|
+
const serializedPage = ` ${formattedKey}: ${serialize(registryObj[codegenKey], 2)}`;
|
|
698
|
+
let lastCharIdx = lastBraceIdx - 1;
|
|
699
|
+
while (lastCharIdx > bounds.startIdx && /\s/.test(content[lastCharIdx])) {
|
|
700
|
+
lastCharIdx--;
|
|
701
|
+
}
|
|
702
|
+
if (content[lastCharIdx] === '}' && content[lastCharIdx + 1] !== ',') {
|
|
703
|
+
content = content.substring(0, lastCharIdx + 1) + ',\n' + serializedPage + '\n' + content.substring(lastBraceIdx);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
const beforeBrace = content.substring(bounds.startIdx, lastBraceIdx).trim();
|
|
707
|
+
const needsComma = beforeBrace.length > 0 && !beforeBrace.endsWith(',') && !beforeBrace.endsWith('{');
|
|
708
|
+
const insertText = (needsComma ? ',\n' : '\n') + serializedPage + '\n';
|
|
709
|
+
content = content.substring(0, lastBraceIdx) + insertText + content.substring(lastBraceIdx);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
716
|
+
if (Object.keys(keyReplacements).length > 0) {
|
|
717
|
+
const testsDir = path.join(path.dirname(filePath), '../tests');
|
|
718
|
+
if (fs.existsSync(testsDir)) {
|
|
719
|
+
const files = fs.readdirSync(testsDir);
|
|
720
|
+
for (const file of files) {
|
|
721
|
+
if (file.endsWith('.spec.ts') || file.endsWith('.test.ts')) {
|
|
722
|
+
const specPath = path.join(testsDir, file);
|
|
723
|
+
let specContent = fs.readFileSync(specPath, 'utf8');
|
|
724
|
+
let modified = false;
|
|
725
|
+
for (const oldKey of Object.keys(keyReplacements)) {
|
|
726
|
+
const newKey = keyReplacements[oldKey];
|
|
727
|
+
const regex = new RegExp(`(['"\`])${oldKey}(['"\`])`, 'g');
|
|
728
|
+
const before = specContent;
|
|
729
|
+
specContent = specContent.replace(regex, `$1${newKey}$2`);
|
|
730
|
+
if (specContent !== before)
|
|
731
|
+
modified = true;
|
|
732
|
+
}
|
|
733
|
+
if (modified) {
|
|
734
|
+
fs.writeFileSync(specPath, specContent, 'utf8');
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return keyReplacements;
|
|
741
|
+
}
|
|
742
|
+
catch (e) {
|
|
743
|
+
console.error('Error writing registry.ts:', e);
|
|
744
|
+
}
|
|
745
|
+
return {};
|
|
746
|
+
}
|
|
747
|
+
function findPageKey(url, title, registryObj, overrideMode) {
|
|
748
|
+
let pathname = '';
|
|
749
|
+
let hashPath = '';
|
|
750
|
+
try {
|
|
751
|
+
const u = new URL(url);
|
|
752
|
+
pathname = u.pathname;
|
|
753
|
+
if (u.hash && u.hash.startsWith('#/')) {
|
|
754
|
+
hashPath = u.hash.slice(1);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
catch (e) {
|
|
758
|
+
pathname = url || '';
|
|
759
|
+
}
|
|
760
|
+
const cleanPath = (p) => {
|
|
761
|
+
let result = p.split('?')[0].split('#')[0];
|
|
762
|
+
if (result.startsWith('/'))
|
|
763
|
+
result = result.substring(1);
|
|
764
|
+
if (result.endsWith('/'))
|
|
765
|
+
result = result.slice(0, -1);
|
|
766
|
+
return result;
|
|
767
|
+
};
|
|
768
|
+
const effectivePath = (hashPath && hashPath !== '/' && hashPath !== '')
|
|
769
|
+
? cleanPath(hashPath)
|
|
770
|
+
: cleanPath(pathname);
|
|
771
|
+
for (const key of Object.keys(registryObj)) {
|
|
772
|
+
const pageConfig = registryObj[key];
|
|
773
|
+
if (pageConfig && pageConfig.url) {
|
|
774
|
+
let configUrl = pageConfig.url;
|
|
775
|
+
if (configUrl.startsWith('/'))
|
|
776
|
+
configUrl = configUrl.substring(1);
|
|
777
|
+
if (configUrl.endsWith('/'))
|
|
778
|
+
configUrl = configUrl.slice(0, -1);
|
|
779
|
+
if (configUrl === effectivePath) {
|
|
780
|
+
const isCodegen = key.startsWith('codegen_') || key === 'codegenPage';
|
|
781
|
+
if (!overrideMode && !isCodegen)
|
|
782
|
+
continue;
|
|
783
|
+
if (!effectivePath && isCodegen)
|
|
784
|
+
continue;
|
|
785
|
+
return key;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
let keyBase = '';
|
|
790
|
+
if (effectivePath) {
|
|
791
|
+
const segments = effectivePath.split('/').filter(Boolean);
|
|
792
|
+
keyBase = segments
|
|
793
|
+
.map((seg, idx) => {
|
|
794
|
+
const parts = seg.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
795
|
+
if (parts.length === 0)
|
|
796
|
+
return '';
|
|
797
|
+
return parts
|
|
798
|
+
.map((part, partIdx) => {
|
|
799
|
+
if (idx === 0 && partIdx === 0) {
|
|
800
|
+
return part.charAt(0).toLowerCase() + part.slice(1);
|
|
801
|
+
}
|
|
802
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
803
|
+
})
|
|
804
|
+
.join('');
|
|
805
|
+
})
|
|
806
|
+
.join('');
|
|
807
|
+
}
|
|
808
|
+
if (!keyBase && title) {
|
|
809
|
+
const firstSegment = title.split(/[|\u2014\-:]/)[0].trim();
|
|
810
|
+
const cleanTitle = (firstSegment || title).replace(/[^a-zA-Z0-9\s]/g, '').trim();
|
|
811
|
+
const words = cleanTitle.split(/\s+/).filter(Boolean);
|
|
812
|
+
keyBase = words
|
|
813
|
+
.map((w, idx) => {
|
|
814
|
+
const clean = w.replace(/[^a-zA-Z0-9]/g, '');
|
|
815
|
+
if (idx === 0)
|
|
816
|
+
return clean.charAt(0).toLowerCase() + clean.slice(1);
|
|
817
|
+
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
818
|
+
})
|
|
819
|
+
.join('');
|
|
820
|
+
}
|
|
821
|
+
if (!keyBase) {
|
|
822
|
+
keyBase = 'home';
|
|
823
|
+
}
|
|
824
|
+
if (overrideMode) {
|
|
825
|
+
return keyBase;
|
|
826
|
+
}
|
|
827
|
+
return `codegen_${keyBase}`;
|
|
828
|
+
}
|
|
829
|
+
function toCamelCase(str) {
|
|
830
|
+
return (0, key_utils_1.toRegistryKey)(str);
|
|
831
|
+
}
|
|
832
|
+
function normalizePageKey(key) {
|
|
833
|
+
if (key.startsWith('worker') && key.length > 6) {
|
|
834
|
+
return key.slice(6).charAt(0).toLowerCase() + key.slice(7);
|
|
835
|
+
}
|
|
836
|
+
return key;
|
|
837
|
+
}
|
|
838
|
+
function findMatchInDict(val, strategyType, pageKey, registryObj, overrideMode) {
|
|
839
|
+
const getExpandedKey = (dynamicKey, placeholder, value) => {
|
|
840
|
+
const isPurePlaceholder = dynamicKey === `{${placeholder}}`;
|
|
841
|
+
let capValue = '';
|
|
842
|
+
if (isPurePlaceholder) {
|
|
843
|
+
capValue = value;
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
const words = value.split(/[^a-zA-Z0-9]/).filter(Boolean);
|
|
847
|
+
capValue = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
848
|
+
}
|
|
849
|
+
return dynamicKey.replace(`{${placeholder}}`, capValue);
|
|
850
|
+
};
|
|
851
|
+
let targetPageKey = pageKey;
|
|
852
|
+
for (const k of Object.keys(registryObj)) {
|
|
853
|
+
if (normalizePageKey(k) === normalizePageKey(pageKey)) {
|
|
854
|
+
targetPageKey = k;
|
|
855
|
+
break;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const pageConfig = registryObj[targetPageKey] || {};
|
|
859
|
+
const searchConfig = (config, pk) => {
|
|
860
|
+
if (strategyType === 'testId') {
|
|
861
|
+
const dict = config.testId || config.testIds || {};
|
|
862
|
+
for (const k of Object.keys(dict)) {
|
|
863
|
+
const entry = dict[k];
|
|
864
|
+
if (typeof entry === 'object' && entry !== null) {
|
|
865
|
+
const { matches, matchedValue, placeholderName } = matchDynamicEntry(val, entry, true);
|
|
866
|
+
if (matches && matchedValue && placeholderName) {
|
|
867
|
+
const arr = entry[placeholderName] || [];
|
|
868
|
+
if (!arr.includes(matchedValue)) {
|
|
869
|
+
entry[placeholderName] = [...arr, matchedValue];
|
|
870
|
+
}
|
|
871
|
+
const expandedKey = getExpandedKey(k, placeholderName, matchedValue);
|
|
872
|
+
return { pageKey: pk, elementKey: expandedKey, val, type: 'testId', isNew: false };
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
else if (entry === val) {
|
|
876
|
+
return { pageKey: pk, elementKey: k, val, type: 'testId', isNew: false };
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
else if (strategyType === 'selector') {
|
|
881
|
+
const dict = config.selector || config.selectors || {};
|
|
882
|
+
for (const k of Object.keys(dict)) {
|
|
883
|
+
const entry = dict[k];
|
|
884
|
+
if (typeof entry === 'object' && entry !== null) {
|
|
885
|
+
const { matches, matchedValue, placeholderName } = matchDynamicEntry(val, entry, false);
|
|
886
|
+
if (matches && matchedValue && placeholderName) {
|
|
887
|
+
const arr = entry[placeholderName] || [];
|
|
888
|
+
if (!arr.includes(matchedValue)) {
|
|
889
|
+
entry[placeholderName] = [...arr, matchedValue];
|
|
890
|
+
}
|
|
891
|
+
const expandedKey = getExpandedKey(k, placeholderName, matchedValue);
|
|
892
|
+
return { pageKey: pk, elementKey: expandedKey, val, type: 'selector', isNew: false };
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
else if (entry === val) {
|
|
896
|
+
return { pageKey: pk, elementKey: k, val, type: 'selector', isNew: false };
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
const arr = config[strategyType];
|
|
902
|
+
if (Array.isArray(arr)) {
|
|
903
|
+
if (arr.includes(val)) {
|
|
904
|
+
const elementKey = val;
|
|
905
|
+
return { pageKey: pk, elementKey, val, type: strategyType, isNew: false };
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return null;
|
|
910
|
+
};
|
|
911
|
+
const targetMatch = searchConfig(pageConfig, targetPageKey);
|
|
912
|
+
if (targetMatch)
|
|
913
|
+
return targetMatch;
|
|
914
|
+
for (const pk of Object.keys(registryObj)) {
|
|
915
|
+
if (normalizePageKey(pk) === normalizePageKey(pageKey))
|
|
916
|
+
continue;
|
|
917
|
+
const isCurrentCodegen = pageKey.startsWith('codegen_') || pageKey === 'codegenPage';
|
|
918
|
+
const isOtherCodegen = pk.startsWith('codegen_') || pk === 'codegenPage';
|
|
919
|
+
if (isCurrentCodegen && isOtherCodegen)
|
|
920
|
+
continue;
|
|
921
|
+
const config = registryObj[pk] || {};
|
|
922
|
+
const match = searchConfig(config, pk);
|
|
923
|
+
if (match)
|
|
924
|
+
return match;
|
|
925
|
+
}
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
function findElementKey(selector, pageKey, registryObj, overrideMode, extra) {
|
|
929
|
+
let { type, val } = getSelectorValue(selector);
|
|
930
|
+
if (extra?.overrideType) {
|
|
931
|
+
type = extra.overrideType;
|
|
932
|
+
}
|
|
933
|
+
// 1. Try matching with the selector itself first
|
|
934
|
+
const selectorMatch = findMatchInDict(val, type, pageKey, registryObj, overrideMode);
|
|
935
|
+
if (selectorMatch)
|
|
936
|
+
return selectorMatch;
|
|
937
|
+
// 1.5. Check if the value exists in ANY other strategy array/object under the page config to avoid duplicates and recreation
|
|
938
|
+
const pageConfig = registryObj[pageKey] || {};
|
|
939
|
+
for (const k of Object.keys(pageConfig)) {
|
|
940
|
+
if (k === 'url')
|
|
941
|
+
continue;
|
|
942
|
+
const match = findMatchInDict(val, k, pageKey, registryObj, overrideMode);
|
|
943
|
+
if (match) {
|
|
944
|
+
return match;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
// 2. Try matching targetTestId (if any) against testIds in registry
|
|
948
|
+
if (extra?.targetTestId) {
|
|
949
|
+
const testIdMatch = findMatchInDict(extra.targetTestId, 'testId', pageKey, registryObj, overrideMode);
|
|
950
|
+
if (testIdMatch)
|
|
951
|
+
return testIdMatch;
|
|
952
|
+
}
|
|
953
|
+
// 3. Try matching targetId (if any) against selectors/testIds
|
|
954
|
+
if (extra?.targetId) {
|
|
955
|
+
const idSelectorMatch = findMatchInDict(extra.targetId, 'selector', pageKey, registryObj, overrideMode);
|
|
956
|
+
if (idSelectorMatch)
|
|
957
|
+
return idSelectorMatch;
|
|
958
|
+
const idTestIdMatch = findMatchInDict(extra.targetId, 'testId', pageKey, registryObj, overrideMode);
|
|
959
|
+
if (idTestIdMatch)
|
|
960
|
+
return idTestIdMatch;
|
|
961
|
+
}
|
|
962
|
+
// 4. Try matching targetParentId (if any) against selectors/testIds
|
|
963
|
+
if (extra?.targetParentId) {
|
|
964
|
+
const pIdSelectorMatch = findMatchInDict(extra.targetParentId, 'selector', pageKey, registryObj, overrideMode);
|
|
965
|
+
if (pIdSelectorMatch)
|
|
966
|
+
return pIdSelectorMatch;
|
|
967
|
+
}
|
|
968
|
+
// Already declared above: pageConfig
|
|
969
|
+
let baseKey = (type === 'testId' || type === 'selector') ? toCamelCase(val) : val;
|
|
970
|
+
if (!baseKey)
|
|
971
|
+
baseKey = 'element';
|
|
972
|
+
let finalKey = baseKey;
|
|
973
|
+
let counter = 1;
|
|
974
|
+
const existingKeys = new Set();
|
|
975
|
+
for (const key of Object.keys(pageConfig)) {
|
|
976
|
+
const entry = pageConfig[key];
|
|
977
|
+
if (Array.isArray(entry)) {
|
|
978
|
+
entry.forEach(v => existingKeys.add(v));
|
|
979
|
+
}
|
|
980
|
+
else if (typeof entry === 'object' && entry !== null) {
|
|
981
|
+
Object.keys(entry).forEach(k => existingKeys.add(k));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
while (existingKeys.has(finalKey)) {
|
|
985
|
+
finalKey = baseKey + counter;
|
|
986
|
+
counter++;
|
|
987
|
+
}
|
|
988
|
+
return { pageKey, elementKey: finalKey, val, type, isNew: true };
|
|
989
|
+
}
|
|
990
|
+
function formatActionCall(pageKey, elementKey, action, isTextSelector = false, textValue) {
|
|
991
|
+
const getLocatorExpression = () => {
|
|
992
|
+
if (isTextSelector && textValue) {
|
|
993
|
+
let expr = `${pageKey}.page.getByText('${textValue.replace(/'/g, "\\'")}')`;
|
|
994
|
+
if (action.nth !== undefined && action.nth !== 0) {
|
|
995
|
+
expr += `.nth(${action.nth})`;
|
|
996
|
+
}
|
|
997
|
+
return expr;
|
|
998
|
+
}
|
|
999
|
+
return null;
|
|
1000
|
+
};
|
|
1001
|
+
// Escape single quotes in elementKey so generated code like click('I\'m Feeling Lucky') is valid JS
|
|
1002
|
+
const safeKey = elementKey.replace(/'/g, "\\'");
|
|
1003
|
+
switch (action.name) {
|
|
1004
|
+
case 'click': {
|
|
1005
|
+
let method = 'click';
|
|
1006
|
+
if (action.clickCount === 2)
|
|
1007
|
+
method = 'dblclick';
|
|
1008
|
+
let opts = [];
|
|
1009
|
+
if (action.modifiers && action.modifiers.length) {
|
|
1010
|
+
opts.push(`modifiers: [${action.modifiers.map((m) => `'${m}'`).join(', ')}]`);
|
|
1011
|
+
}
|
|
1012
|
+
const locatorExpr = getLocatorExpression();
|
|
1013
|
+
if (locatorExpr) {
|
|
1014
|
+
const optsStr = opts.length ? `{ ${opts.join(', ')} }` : '';
|
|
1015
|
+
return ` await ${locatorExpr}.${method}(${optsStr});`;
|
|
1016
|
+
}
|
|
1017
|
+
if (action.nth !== undefined && action.nth !== 0) {
|
|
1018
|
+
opts.push(`nth: ${action.nth}`);
|
|
1019
|
+
}
|
|
1020
|
+
const optsStr = opts.length ? `, { ${opts.join(', ')} }` : '';
|
|
1021
|
+
return ` await ${pageKey}.${method}('${safeKey}'${optsStr});`;
|
|
1022
|
+
}
|
|
1023
|
+
case 'hover': {
|
|
1024
|
+
const locatorExpr = getLocatorExpression();
|
|
1025
|
+
if (locatorExpr) {
|
|
1026
|
+
return ` await ${locatorExpr}.hover();`;
|
|
1027
|
+
}
|
|
1028
|
+
const optsStr = action.nth !== undefined && action.nth !== 0 ? `, { nth: ${action.nth} }` : '';
|
|
1029
|
+
return ` await ${pageKey}.hover('${safeKey}'${optsStr});`;
|
|
1030
|
+
}
|
|
1031
|
+
case 'check': {
|
|
1032
|
+
const locatorExpr = getLocatorExpression();
|
|
1033
|
+
if (locatorExpr) {
|
|
1034
|
+
return ` await ${locatorExpr}.check();`;
|
|
1035
|
+
}
|
|
1036
|
+
const optsStr = action.nth !== undefined && action.nth !== 0 ? `, { nth: ${action.nth} }` : '';
|
|
1037
|
+
return ` await ${pageKey}.check('${safeKey}'${optsStr});`;
|
|
1038
|
+
}
|
|
1039
|
+
case 'uncheck': {
|
|
1040
|
+
const locatorExpr = getLocatorExpression();
|
|
1041
|
+
if (locatorExpr) {
|
|
1042
|
+
return ` await ${locatorExpr}.uncheck();`;
|
|
1043
|
+
}
|
|
1044
|
+
const optsStr = action.nth !== undefined && action.nth !== 0 ? `, { nth: ${action.nth} }` : '';
|
|
1045
|
+
return ` await ${pageKey}.uncheck('${safeKey}'${optsStr});`;
|
|
1046
|
+
}
|
|
1047
|
+
case 'fill': {
|
|
1048
|
+
const locatorExpr = getLocatorExpression();
|
|
1049
|
+
if (locatorExpr) {
|
|
1050
|
+
return ` await ${locatorExpr}.fill('${action.text.replace(/'/g, "\\'")}');`;
|
|
1051
|
+
}
|
|
1052
|
+
const optsStr = action.nth !== undefined && action.nth !== 0 ? `, { nth: ${action.nth} }` : '';
|
|
1053
|
+
return ` await ${pageKey}.fill('${safeKey}', '${action.text.replace(/'/g, "\\'")}'${optsStr});`;
|
|
1054
|
+
}
|
|
1055
|
+
case 'setInputFiles': {
|
|
1056
|
+
const locatorExpr = getLocatorExpression();
|
|
1057
|
+
if (locatorExpr) {
|
|
1058
|
+
return ` await ${locatorExpr}.setInputFiles(${JSON.stringify(action.files)});`;
|
|
1059
|
+
}
|
|
1060
|
+
const optsStr = action.nth !== undefined && action.nth !== 0 ? `, { nth: ${action.nth} }` : '';
|
|
1061
|
+
return ` await ${pageKey}.setInputFiles('${safeKey}', ${JSON.stringify(action.files)}${optsStr});`;
|
|
1062
|
+
}
|
|
1063
|
+
case 'press': {
|
|
1064
|
+
const modifiers = Array.isArray(action.modifiers) ? action.modifiers : [];
|
|
1065
|
+
const shortcut = [...modifiers, action.key].join('+');
|
|
1066
|
+
const locatorExpr = getLocatorExpression();
|
|
1067
|
+
if (locatorExpr) {
|
|
1068
|
+
return ` await ${locatorExpr}.press('${shortcut}');`;
|
|
1069
|
+
}
|
|
1070
|
+
const optsStr = action.nth !== undefined && action.nth !== 0 ? `, { nth: ${action.nth} }` : '';
|
|
1071
|
+
return ` await ${pageKey}.press('${safeKey}', '${shortcut}'${optsStr});`;
|
|
1072
|
+
}
|
|
1073
|
+
case 'navigate':
|
|
1074
|
+
return ` await ${pageKey}.goto();`;
|
|
1075
|
+
case 'select': {
|
|
1076
|
+
const locatorExpr = getLocatorExpression();
|
|
1077
|
+
if (locatorExpr) {
|
|
1078
|
+
return ` await ${locatorExpr}.selectOption(${JSON.stringify(action.options)});`;
|
|
1079
|
+
}
|
|
1080
|
+
const optsStr = action.nth !== undefined && action.nth !== 0 ? `, { nth: ${action.nth} }` : '';
|
|
1081
|
+
return ` await ${pageKey}.selectOption('${safeKey}', ${JSON.stringify(action.options)}${optsStr});`;
|
|
1082
|
+
}
|
|
1083
|
+
case 'assertText': {
|
|
1084
|
+
const locatorExpr = getLocatorExpression();
|
|
1085
|
+
if (locatorExpr) {
|
|
1086
|
+
return ` await expect(${locatorExpr}).${action.substring ? 'toContainText' : 'toHaveText'}('${action.text.replace(/'/g, "\\'")}');`;
|
|
1087
|
+
}
|
|
1088
|
+
const opts = [];
|
|
1089
|
+
if (action.nth !== undefined && action.nth !== 0)
|
|
1090
|
+
opts.push(`nth: ${action.nth}`);
|
|
1091
|
+
const optsStr = opts.length ? `, { ${opts.join(', ')} }` : '';
|
|
1092
|
+
return ` await ${pageKey}.verify('${safeKey}'${optsStr}).${action.substring ? 'toContainText' : 'toHaveText'}('${action.text.replace(/'/g, "\\'")}');`;
|
|
1093
|
+
}
|
|
1094
|
+
case 'assertChecked': {
|
|
1095
|
+
const locatorExpr = getLocatorExpression();
|
|
1096
|
+
if (locatorExpr) {
|
|
1097
|
+
return ` await expect(${locatorExpr})${action.checked ? '' : '.not'}.toBeChecked();`;
|
|
1098
|
+
}
|
|
1099
|
+
const opts = [];
|
|
1100
|
+
if (action.nth !== undefined && action.nth !== 0)
|
|
1101
|
+
opts.push(`nth: ${action.nth}`);
|
|
1102
|
+
const optsStr = opts.length ? `, { ${opts.join(', ')} }` : '';
|
|
1103
|
+
return ` await ${pageKey}.verify('${safeKey}'${optsStr})${action.checked ? '' : '.not'}.toBeChecked();`;
|
|
1104
|
+
}
|
|
1105
|
+
case 'assertVisible': {
|
|
1106
|
+
const locatorExpr = getLocatorExpression();
|
|
1107
|
+
if (locatorExpr) {
|
|
1108
|
+
return ` await expect(${locatorExpr}).toBeVisible();`;
|
|
1109
|
+
}
|
|
1110
|
+
const opts = [];
|
|
1111
|
+
if (action.nth !== undefined && action.nth !== 0)
|
|
1112
|
+
opts.push(`nth: ${action.nth}`);
|
|
1113
|
+
const optsStr = opts.length ? `, { ${opts.join(', ')} }` : '';
|
|
1114
|
+
return ` await ${pageKey}.verify('${safeKey}'${optsStr});`;
|
|
1115
|
+
}
|
|
1116
|
+
case 'assertValue': {
|
|
1117
|
+
const locatorExpr = getLocatorExpression();
|
|
1118
|
+
if (locatorExpr) {
|
|
1119
|
+
const assertion = action.value ? `toHaveValue('${action.value.replace(/'/g, "\\'")}')` : `toBeEmpty()`;
|
|
1120
|
+
return ` await expect(${locatorExpr}).${assertion};`;
|
|
1121
|
+
}
|
|
1122
|
+
const opts = [];
|
|
1123
|
+
if (action.nth !== undefined && action.nth !== 0)
|
|
1124
|
+
opts.push(`nth: ${action.nth}`);
|
|
1125
|
+
const optsStr = opts.length ? `, { ${opts.join(', ')} }` : '';
|
|
1126
|
+
const assertion = action.value ? `toHaveValue('${action.value.replace(/'/g, "\\'")}')` : `toBeEmpty()`;
|
|
1127
|
+
return ` await ${pageKey}.verify('${safeKey}'${optsStr}).${assertion};`;
|
|
1128
|
+
}
|
|
1129
|
+
case 'assertSnapshot': {
|
|
1130
|
+
const locatorExpr = getLocatorExpression();
|
|
1131
|
+
if (locatorExpr) {
|
|
1132
|
+
return ` await expect(${locatorExpr}).toMatchAriaSnapshot(\`\n${action.ariaSnapshot}\`);`;
|
|
1133
|
+
}
|
|
1134
|
+
const opts = [];
|
|
1135
|
+
if (action.nth !== undefined && action.nth !== 0)
|
|
1136
|
+
opts.push(`nth: ${action.nth}`);
|
|
1137
|
+
const optsStr = opts.length ? `, { ${opts.join(', ')} }` : '';
|
|
1138
|
+
return ` await ${pageKey}.verify('${safeKey}'${optsStr}).toMatchAriaSnapshot(\`\n${action.ariaSnapshot}\`);`;
|
|
1139
|
+
}
|
|
1140
|
+
default:
|
|
1141
|
+
return ` // Unsupported action: ${action.name} on ${elementKey}`;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
__exportStar(require("./config"), exports);
|
|
1145
|
+
__exportStar(require("./generator/index"), exports);
|
|
1146
|
+
__exportStar(require("./types"), exports);
|
|
1147
|
+
__exportStar(require("./scorer"), exports);
|