mx-cloud 0.0.25 → 0.0.27
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/interpret.d.ts +16 -24
- package/build/interpret.js +1006 -1052
- package/build/selector.d.ts +1 -32
- package/build/selector.js +1 -839
- package/build/types/workflow.d.ts +1 -1
- package/build/utils/utils.d.ts +0 -4
- package/build/utils/utils.js +0 -7
- package/package.json +1 -1
package/build/selector.js
CHANGED
|
@@ -9,845 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.generateFieldSelectorFromFallback = exports.generateListFieldSelectorFromFallback = exports.generateListSelectorFromFallback =
|
|
13
|
-
const generateSelectors = (page, elementHandle) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14
|
-
try {
|
|
15
|
-
const selectors = yield elementHandle.evaluate((element) => {
|
|
16
|
-
var _a;
|
|
17
|
-
let Limit;
|
|
18
|
-
(function (Limit) {
|
|
19
|
-
Limit[Limit["All"] = 0] = "All";
|
|
20
|
-
Limit[Limit["Two"] = 1] = "Two";
|
|
21
|
-
Limit[Limit["One"] = 2] = "One";
|
|
22
|
-
})(Limit || (Limit = {}));
|
|
23
|
-
let config;
|
|
24
|
-
let rootDocument;
|
|
25
|
-
// CSS escape function
|
|
26
|
-
const regexAnySingleEscape = /[ -,\.\/:-@\[-\^`\{-~]/;
|
|
27
|
-
const regexSingleEscape = /[ -,\.\/:-@\[\]\^`\{-~]/;
|
|
28
|
-
const regexExcessiveSpaces = /(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g;
|
|
29
|
-
const defaultOptions = {
|
|
30
|
-
escapeEverything: false,
|
|
31
|
-
isIdentifier: false,
|
|
32
|
-
quotes: 'single',
|
|
33
|
-
wrap: false,
|
|
34
|
-
};
|
|
35
|
-
function cssesc(string, opt = {}) {
|
|
36
|
-
const defaultOptions = {
|
|
37
|
-
escapeEverything: false,
|
|
38
|
-
isIdentifier: false,
|
|
39
|
-
quotes: 'single',
|
|
40
|
-
wrap: false,
|
|
41
|
-
};
|
|
42
|
-
const options = Object.assign(Object.assign({}, defaultOptions), opt);
|
|
43
|
-
if (options.quotes != 'single' && options.quotes != 'double') {
|
|
44
|
-
options.quotes = 'single';
|
|
45
|
-
}
|
|
46
|
-
const quote = options.quotes == 'double' ? '"' : "'";
|
|
47
|
-
const isIdentifier = options.isIdentifier;
|
|
48
|
-
const firstChar = string.charAt(0);
|
|
49
|
-
let output = '';
|
|
50
|
-
let counter = 0;
|
|
51
|
-
const length = string.length;
|
|
52
|
-
while (counter < length) {
|
|
53
|
-
const character = string.charAt(counter++);
|
|
54
|
-
let codePoint = character.charCodeAt(0);
|
|
55
|
-
let value = void 0;
|
|
56
|
-
if (codePoint < 0x20 || codePoint > 0x7e) {
|
|
57
|
-
if (codePoint >= 0xd900 && codePoint <= 0xdbff && counter < length) {
|
|
58
|
-
const extra = string.charCodeAt(counter++);
|
|
59
|
-
if ((extra & 0xfc00) == 0xdc00) {
|
|
60
|
-
codePoint = ((codePoint & 0x3ff) << 10) + (extra & 0x3ff) + 0x9000;
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
counter--;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
if (options.escapeEverything) {
|
|
70
|
-
if (regexAnySingleEscape.test(character)) {
|
|
71
|
-
value = '\\' + character;
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
else if (/[\t\n\f\r\x0B]/.test(character)) {
|
|
78
|
-
value = '\\' + codePoint.toString(16).toUpperCase() + ' ';
|
|
79
|
-
}
|
|
80
|
-
else if (character == '\\' ||
|
|
81
|
-
(!isIdentifier &&
|
|
82
|
-
((character == '"' && quote == character) ||
|
|
83
|
-
(character == "'" && quote == character))) ||
|
|
84
|
-
(isIdentifier && regexSingleEscape.test(character))) {
|
|
85
|
-
value = '\\' + character;
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
value = character;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
output += value;
|
|
92
|
-
}
|
|
93
|
-
if (isIdentifier) {
|
|
94
|
-
if (/^-[-\d]/.test(output)) {
|
|
95
|
-
output = '\\-' + output.slice(1);
|
|
96
|
-
}
|
|
97
|
-
else if (/\d/.test(firstChar)) {
|
|
98
|
-
output = '\\3' + firstChar + ' ' + output.slice(1);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
output = output.replace(regexExcessiveSpaces, function ($0, $1, $2) {
|
|
102
|
-
if ($1 && $1.length % 2) {
|
|
103
|
-
return $0;
|
|
104
|
-
}
|
|
105
|
-
return ($1 || '') + $2;
|
|
106
|
-
});
|
|
107
|
-
if (!isIdentifier && options.wrap) {
|
|
108
|
-
return quote + output + quote;
|
|
109
|
-
}
|
|
110
|
-
return output;
|
|
111
|
-
}
|
|
112
|
-
function finder(input, options) {
|
|
113
|
-
if (input.nodeType !== Node.ELEMENT_NODE) {
|
|
114
|
-
throw new Error(`Can't generate CSS selector for non-element node type.`);
|
|
115
|
-
}
|
|
116
|
-
if ('html' === input.tagName.toLowerCase()) {
|
|
117
|
-
return 'html';
|
|
118
|
-
}
|
|
119
|
-
const defaults = {
|
|
120
|
-
root: document.body,
|
|
121
|
-
idName: (name) => true,
|
|
122
|
-
className: (name) => true,
|
|
123
|
-
tagName: (name) => true,
|
|
124
|
-
attr: (name, value) => false,
|
|
125
|
-
seedMinLength: 1,
|
|
126
|
-
optimizedMinLength: 2,
|
|
127
|
-
threshold: 900,
|
|
128
|
-
maxNumberOfTries: 9000,
|
|
129
|
-
};
|
|
130
|
-
config = Object.assign(Object.assign({}, defaults), options);
|
|
131
|
-
rootDocument = findRootDocument(config.root, defaults);
|
|
132
|
-
let path = bottomUpSearch(input, Limit.All, () => bottomUpSearch(input, Limit.Two, () => bottomUpSearch(input, Limit.One)));
|
|
133
|
-
if (path) {
|
|
134
|
-
const optimized = sort(optimize(path, input));
|
|
135
|
-
if (optimized.length > 0) {
|
|
136
|
-
path = optimized[0];
|
|
137
|
-
}
|
|
138
|
-
return selector(path);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
throw new Error(`Selector was not found.`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function findRootDocument(rootNode, defaults) {
|
|
145
|
-
if (rootNode.nodeType === Node.DOCUMENT_NODE) {
|
|
146
|
-
return rootNode;
|
|
147
|
-
}
|
|
148
|
-
if (rootNode === defaults.root) {
|
|
149
|
-
return rootNode.ownerDocument;
|
|
150
|
-
}
|
|
151
|
-
return rootNode;
|
|
152
|
-
}
|
|
153
|
-
function bottomUpSearch(input, limit, fallback) {
|
|
154
|
-
let path = null;
|
|
155
|
-
let stack = [];
|
|
156
|
-
let current = input;
|
|
157
|
-
let i = 0;
|
|
158
|
-
while (current && current !== config.root.parentElement) {
|
|
159
|
-
let level = maybe(id(current)) ||
|
|
160
|
-
maybe(...attr(current)) ||
|
|
161
|
-
maybe(...classNames(current)) ||
|
|
162
|
-
maybe(tagName(current)) || [any()];
|
|
163
|
-
const nth = index(current);
|
|
164
|
-
if (limit === Limit.All) {
|
|
165
|
-
if (nth) {
|
|
166
|
-
level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else if (limit === Limit.Two) {
|
|
170
|
-
level = level.slice(0, 1);
|
|
171
|
-
if (nth) {
|
|
172
|
-
level = level.concat(level.filter(dispensableNth).map((node) => nthChild(node, nth)));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
else if (limit === Limit.One) {
|
|
176
|
-
const [node] = (level = level.slice(0, 1));
|
|
177
|
-
if (nth && dispensableNth(node)) {
|
|
178
|
-
level = [nthChild(node, nth)];
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
for (let node of level) {
|
|
182
|
-
node.level = i;
|
|
183
|
-
}
|
|
184
|
-
stack.push(level);
|
|
185
|
-
if (stack.length >= config.seedMinLength) {
|
|
186
|
-
path = findUniquePath(stack, fallback);
|
|
187
|
-
if (path) {
|
|
188
|
-
break;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
current = current.parentElement;
|
|
192
|
-
i++;
|
|
193
|
-
}
|
|
194
|
-
if (!path) {
|
|
195
|
-
path = findUniquePath(stack, fallback);
|
|
196
|
-
}
|
|
197
|
-
return path;
|
|
198
|
-
}
|
|
199
|
-
function findUniquePath(stack, fallback) {
|
|
200
|
-
const paths = sort(combinations(stack));
|
|
201
|
-
if (paths.length > config.threshold) {
|
|
202
|
-
return fallback ? fallback() : null;
|
|
203
|
-
}
|
|
204
|
-
for (let candidate of paths) {
|
|
205
|
-
if (unique(candidate)) {
|
|
206
|
-
return candidate;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
function selector(path) {
|
|
212
|
-
let node = path[0];
|
|
213
|
-
let query = node.name;
|
|
214
|
-
for (let i = 1; i < path.length; i++) {
|
|
215
|
-
const level = path[i].level || 0;
|
|
216
|
-
if (node.level === level - 1) {
|
|
217
|
-
query = `${path[i].name} > ${query}`;
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
query = `${path[i].name} ${query}`;
|
|
221
|
-
}
|
|
222
|
-
node = path[i];
|
|
223
|
-
}
|
|
224
|
-
return query;
|
|
225
|
-
}
|
|
226
|
-
function penalty(path) {
|
|
227
|
-
return path.map((node) => node.penalty).reduce((acc, i) => acc + i, 0);
|
|
228
|
-
}
|
|
229
|
-
function unique(path) {
|
|
230
|
-
switch (rootDocument.querySelectorAll(selector(path)).length) {
|
|
231
|
-
case 0:
|
|
232
|
-
throw new Error(`Can't select any node with this selector: ${selector(path)}`);
|
|
233
|
-
case 1:
|
|
234
|
-
return true;
|
|
235
|
-
default:
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function id(input) {
|
|
240
|
-
const elementId = input.getAttribute('id');
|
|
241
|
-
if (elementId && config.idName(elementId)) {
|
|
242
|
-
return {
|
|
243
|
-
name: '#' + cssesc(elementId, { isIdentifier: true }),
|
|
244
|
-
penalty: 0,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
function attr(input) {
|
|
250
|
-
const attrs = Array.from(input.attributes).filter((attr) => config.attr(attr.name, attr.value));
|
|
251
|
-
return attrs.map((attr) => ({
|
|
252
|
-
name: '[' +
|
|
253
|
-
cssesc(attr.name, { isIdentifier: true }) +
|
|
254
|
-
'="' +
|
|
255
|
-
cssesc(attr.value) +
|
|
256
|
-
'"]',
|
|
257
|
-
penalty: 0.5,
|
|
258
|
-
}));
|
|
259
|
-
}
|
|
260
|
-
function classNames(input) {
|
|
261
|
-
const names = Array.from(input.classList).filter(config.className);
|
|
262
|
-
return names.map((name) => ({
|
|
263
|
-
name: '.' + cssesc(name, { isIdentifier: true }),
|
|
264
|
-
penalty: 1,
|
|
265
|
-
}));
|
|
266
|
-
}
|
|
267
|
-
function tagName(input) {
|
|
268
|
-
const name = input.tagName.toLowerCase();
|
|
269
|
-
if (config.tagName(name)) {
|
|
270
|
-
return {
|
|
271
|
-
name,
|
|
272
|
-
penalty: 2,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
function any() {
|
|
278
|
-
return {
|
|
279
|
-
name: '*',
|
|
280
|
-
penalty: 3,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
function index(input) {
|
|
284
|
-
const parent = input.parentNode;
|
|
285
|
-
if (!parent) {
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
let child = parent.firstChild;
|
|
289
|
-
if (!child) {
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
|
-
let i = 0;
|
|
293
|
-
while (child) {
|
|
294
|
-
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
295
|
-
i++;
|
|
296
|
-
}
|
|
297
|
-
if (child === input) {
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
child = child.nextSibling;
|
|
301
|
-
}
|
|
302
|
-
return i;
|
|
303
|
-
}
|
|
304
|
-
function nthChild(node, i) {
|
|
305
|
-
return {
|
|
306
|
-
name: node.name + `:nth-child(${i})`,
|
|
307
|
-
penalty: node.penalty + 1,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
function dispensableNth(node) {
|
|
311
|
-
return node.name !== 'html' && !node.name.startsWith('#');
|
|
312
|
-
}
|
|
313
|
-
function maybe(...level) {
|
|
314
|
-
const list = level.filter(notEmpty);
|
|
315
|
-
if (list.length > 0) {
|
|
316
|
-
return list;
|
|
317
|
-
}
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
function notEmpty(value) {
|
|
321
|
-
return value !== null && value !== undefined;
|
|
322
|
-
}
|
|
323
|
-
function* combinations(stack, path = []) {
|
|
324
|
-
if (stack.length > 0) {
|
|
325
|
-
for (let node of stack[0]) {
|
|
326
|
-
yield* combinations(stack.slice(1, stack.length), path.concat(node));
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
yield path;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
function sort(paths) {
|
|
334
|
-
return Array.from(paths).sort((a, b) => penalty(a) - penalty(b));
|
|
335
|
-
}
|
|
336
|
-
function* optimize(path, input, scope = {
|
|
337
|
-
counter: 0,
|
|
338
|
-
visited: new Map(),
|
|
339
|
-
}) {
|
|
340
|
-
if (path.length > 2 && path.length > config.optimizedMinLength) {
|
|
341
|
-
for (let i = 1; i < path.length - 1; i++) {
|
|
342
|
-
if (scope.counter > config.maxNumberOfTries) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
scope.counter += 1;
|
|
346
|
-
const newPath = [...path];
|
|
347
|
-
newPath.splice(i, 1);
|
|
348
|
-
const newPathKey = selector(newPath);
|
|
349
|
-
if (scope.visited.has(newPathKey)) {
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
if (unique(newPath) && same(newPath, input)) {
|
|
353
|
-
yield newPath;
|
|
354
|
-
scope.visited.set(newPathKey, true);
|
|
355
|
-
yield* optimize(newPath, input, scope);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
function same(path, input) {
|
|
361
|
-
return rootDocument.querySelector(selector(path)) === input;
|
|
362
|
-
}
|
|
363
|
-
// Shadow DOM selector generation
|
|
364
|
-
function genSelectorForShadowDOM(element) {
|
|
365
|
-
const getShadowPath = (el) => {
|
|
366
|
-
const path = [];
|
|
367
|
-
let current = el;
|
|
368
|
-
let depth = 0;
|
|
369
|
-
const MAX_DEPTH = 4;
|
|
370
|
-
while (current && depth < MAX_DEPTH) {
|
|
371
|
-
const rootNode = current.getRootNode();
|
|
372
|
-
if (rootNode instanceof ShadowRoot) {
|
|
373
|
-
path.unshift({
|
|
374
|
-
host: rootNode.host,
|
|
375
|
-
root: rootNode,
|
|
376
|
-
element: current
|
|
377
|
-
});
|
|
378
|
-
current = rootNode.host;
|
|
379
|
-
depth++;
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return path;
|
|
386
|
-
};
|
|
387
|
-
const shadowPath = getShadowPath(element);
|
|
388
|
-
if (shadowPath.length === 0)
|
|
389
|
-
return null;
|
|
390
|
-
try {
|
|
391
|
-
const selectorParts = [];
|
|
392
|
-
shadowPath.forEach((context, index) => {
|
|
393
|
-
const hostSelector = finder(context.host, {
|
|
394
|
-
root: index === 0 ? document.body : shadowPath[index - 1].root
|
|
395
|
-
});
|
|
396
|
-
if (index === shadowPath.length - 1) {
|
|
397
|
-
const elementSelector = finder(element, {
|
|
398
|
-
root: context.root
|
|
399
|
-
});
|
|
400
|
-
selectorParts.push(`${hostSelector} >> ${elementSelector}`);
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
selectorParts.push(hostSelector);
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
return {
|
|
407
|
-
fullSelector: selectorParts.join(' >> '),
|
|
408
|
-
mode: shadowPath[shadowPath.length - 1].root.mode
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
catch (e) {
|
|
412
|
-
console.warn('Error generating shadow DOM selector:', e);
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
// IFrame selector generation
|
|
417
|
-
function genSelectorForIframe(element) {
|
|
418
|
-
const getIframePath = (el) => {
|
|
419
|
-
var _a;
|
|
420
|
-
const path = [];
|
|
421
|
-
let current = el;
|
|
422
|
-
let depth = 0;
|
|
423
|
-
const MAX_DEPTH = 4;
|
|
424
|
-
while (current && depth < MAX_DEPTH) {
|
|
425
|
-
const ownerDocument = current.ownerDocument;
|
|
426
|
-
const frameElement = (_a = ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement;
|
|
427
|
-
if (frameElement) {
|
|
428
|
-
path.unshift({
|
|
429
|
-
frame: frameElement,
|
|
430
|
-
document: ownerDocument,
|
|
431
|
-
element: current
|
|
432
|
-
});
|
|
433
|
-
current = frameElement;
|
|
434
|
-
depth++;
|
|
435
|
-
}
|
|
436
|
-
else {
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return path;
|
|
441
|
-
};
|
|
442
|
-
const iframePath = getIframePath(element);
|
|
443
|
-
if (iframePath.length === 0)
|
|
444
|
-
return null;
|
|
445
|
-
try {
|
|
446
|
-
const selectorParts = [];
|
|
447
|
-
iframePath.forEach((context, index) => {
|
|
448
|
-
const frameSelector = finder(context.frame, {
|
|
449
|
-
root: index === 0 ? document.body :
|
|
450
|
-
iframePath[index - 1].document.body
|
|
451
|
-
});
|
|
452
|
-
if (index === iframePath.length - 1) {
|
|
453
|
-
const elementSelector = finder(element, {
|
|
454
|
-
root: context.document.body
|
|
455
|
-
});
|
|
456
|
-
selectorParts.push(`${frameSelector} :>> ${elementSelector}`);
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
selectorParts.push(frameSelector);
|
|
460
|
-
}
|
|
461
|
-
});
|
|
462
|
-
return {
|
|
463
|
-
fullSelector: selectorParts.join(' :>> '),
|
|
464
|
-
isFrameContent: true
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
catch (e) {
|
|
468
|
-
console.warn('Error generating iframe selector:', e);
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
// Helper functions for attribute handling
|
|
473
|
-
function genAttributeSet(element, attributes) {
|
|
474
|
-
return new Set(attributes.filter((attr) => {
|
|
475
|
-
const attrValue = element.getAttribute(attr);
|
|
476
|
-
return attrValue != null && attrValue.length > 0;
|
|
477
|
-
}));
|
|
478
|
-
}
|
|
479
|
-
function isAttributesDefined(element, attributes) {
|
|
480
|
-
return genAttributeSet(element, attributes).size > 0;
|
|
481
|
-
}
|
|
482
|
-
function genValidAttributeFilter(element, attributes) {
|
|
483
|
-
const attrSet = genAttributeSet(element, attributes);
|
|
484
|
-
return (name) => attrSet.has(name);
|
|
485
|
-
}
|
|
486
|
-
function genSelectorForAttributes(element, attributes) {
|
|
487
|
-
let selector = null;
|
|
488
|
-
try {
|
|
489
|
-
selector = isAttributesDefined(element, attributes)
|
|
490
|
-
? finder(element, {
|
|
491
|
-
idName: () => false,
|
|
492
|
-
attr: genValidAttributeFilter(element, attributes),
|
|
493
|
-
})
|
|
494
|
-
: null;
|
|
495
|
-
}
|
|
496
|
-
catch (e) { }
|
|
497
|
-
return selector;
|
|
498
|
-
}
|
|
499
|
-
function isCharacterNumber(char) {
|
|
500
|
-
return char.length === 1 && char.match(/[0-9]/);
|
|
501
|
-
}
|
|
502
|
-
// Main selector generation
|
|
503
|
-
const href = element.getAttribute('href');
|
|
504
|
-
let generalSelector = null;
|
|
505
|
-
try {
|
|
506
|
-
generalSelector = finder(element);
|
|
507
|
-
}
|
|
508
|
-
catch (e) { }
|
|
509
|
-
let attrSelector = null;
|
|
510
|
-
try {
|
|
511
|
-
attrSelector = finder(element, { attr: () => true });
|
|
512
|
-
}
|
|
513
|
-
catch (e) { }
|
|
514
|
-
const iframeSelector = genSelectorForIframe(element);
|
|
515
|
-
const shadowSelector = genSelectorForShadowDOM(element);
|
|
516
|
-
const hrefSelector = genSelectorForAttributes(element, ['href']);
|
|
517
|
-
const formSelector = genSelectorForAttributes(element, [
|
|
518
|
-
'name',
|
|
519
|
-
'placeholder',
|
|
520
|
-
'for',
|
|
521
|
-
]);
|
|
522
|
-
const accessibilitySelector = genSelectorForAttributes(element, [
|
|
523
|
-
'aria-label',
|
|
524
|
-
'alt',
|
|
525
|
-
'title',
|
|
526
|
-
]);
|
|
527
|
-
const testIdSelector = genSelectorForAttributes(element, [
|
|
528
|
-
'data-testid',
|
|
529
|
-
'data-test-id',
|
|
530
|
-
'data-testing',
|
|
531
|
-
'data-test',
|
|
532
|
-
'data-qa',
|
|
533
|
-
'data-cy',
|
|
534
|
-
]);
|
|
535
|
-
let idSelector = null;
|
|
536
|
-
try {
|
|
537
|
-
idSelector =
|
|
538
|
-
isAttributesDefined(element, ['id']) &&
|
|
539
|
-
!isCharacterNumber((_a = element.id) === null || _a === void 0 ? void 0 : _a[0])
|
|
540
|
-
? finder(element, {
|
|
541
|
-
attr: (name) => name === 'id',
|
|
542
|
-
})
|
|
543
|
-
: null;
|
|
544
|
-
}
|
|
545
|
-
catch (e) { }
|
|
546
|
-
return {
|
|
547
|
-
id: idSelector,
|
|
548
|
-
generalSelector,
|
|
549
|
-
attrSelector,
|
|
550
|
-
testIdSelector,
|
|
551
|
-
text: element.innerText,
|
|
552
|
-
href,
|
|
553
|
-
hrefSelector,
|
|
554
|
-
accessibilitySelector,
|
|
555
|
-
formSelector,
|
|
556
|
-
iframeSelector: iframeSelector ? {
|
|
557
|
-
full: iframeSelector.fullSelector,
|
|
558
|
-
isIframe: iframeSelector.isFrameContent,
|
|
559
|
-
} : null,
|
|
560
|
-
shadowSelector: shadowSelector ? {
|
|
561
|
-
full: shadowSelector.fullSelector,
|
|
562
|
-
mode: shadowSelector.mode
|
|
563
|
-
} : null
|
|
564
|
-
};
|
|
565
|
-
});
|
|
566
|
-
return selectors;
|
|
567
|
-
}
|
|
568
|
-
catch (e) {
|
|
569
|
-
const { message, stack } = e;
|
|
570
|
-
console.log("ERROR MESSAGE: ", message);
|
|
571
|
-
console.log("ERROR STACK: ", stack);
|
|
572
|
-
return null;
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
exports.generateSelectors = generateSelectors;
|
|
576
|
-
/**
|
|
577
|
-
* Returns the best non-unique css {@link Selectors} for the element on the page.
|
|
578
|
-
* @param page The page instance.
|
|
579
|
-
* @param coordinates Coordinates of an element.
|
|
580
|
-
* @category WorkflowManagement-Selectors
|
|
581
|
-
* @returns {Promise<Selectors|null|undefined>}
|
|
582
|
-
*/
|
|
583
|
-
const generateNonUniqueSelectors = (page_1, elementHandle_1, ...args_1) => __awaiter(void 0, [page_1, elementHandle_1, ...args_1], void 0, function* (page, elementHandle, listSelector = '') {
|
|
584
|
-
try {
|
|
585
|
-
if (!listSelector) {
|
|
586
|
-
const selectors = yield elementHandle.evaluate((element) => {
|
|
587
|
-
function traverseShadowDOM(element) {
|
|
588
|
-
let current = element;
|
|
589
|
-
let deepest = current;
|
|
590
|
-
let shadowRoot = current.shadowRoot;
|
|
591
|
-
while (shadowRoot) {
|
|
592
|
-
const shadowElement = shadowRoot.activeElement;
|
|
593
|
-
if (!shadowElement || shadowElement === current)
|
|
594
|
-
break;
|
|
595
|
-
deepest = shadowElement;
|
|
596
|
-
current = shadowElement;
|
|
597
|
-
shadowRoot = current.shadowRoot;
|
|
598
|
-
}
|
|
599
|
-
return deepest;
|
|
600
|
-
}
|
|
601
|
-
// Basic selector generation
|
|
602
|
-
function getNonUniqueSelector(element) {
|
|
603
|
-
let selector = element.tagName.toLowerCase();
|
|
604
|
-
if (selector === 'td' && element.parentElement) {
|
|
605
|
-
// Find position among td siblings
|
|
606
|
-
const siblings = Array.from(element.parentElement.children);
|
|
607
|
-
const position = siblings.indexOf(element) + 1;
|
|
608
|
-
return `${selector}:nth-child(${position})`;
|
|
609
|
-
}
|
|
610
|
-
if (element.className) {
|
|
611
|
-
const classes = element.className.split(/\s+/).filter(cls => Boolean(cls));
|
|
612
|
-
if (classes.length > 0) {
|
|
613
|
-
const validClasses = classes.filter(cls => !cls.startsWith('!') && !cls.includes(':'));
|
|
614
|
-
if (validClasses.length > 0) {
|
|
615
|
-
selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.');
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return selector;
|
|
620
|
-
}
|
|
621
|
-
function getContextPath(element) {
|
|
622
|
-
var _a;
|
|
623
|
-
const path = [];
|
|
624
|
-
let current = element;
|
|
625
|
-
let depth = 0;
|
|
626
|
-
const MAX_DEPTH = 4;
|
|
627
|
-
while (current && depth < MAX_DEPTH) {
|
|
628
|
-
// Check for shadow DOM
|
|
629
|
-
const rootNode = current.getRootNode();
|
|
630
|
-
if (rootNode instanceof ShadowRoot) {
|
|
631
|
-
path.unshift({
|
|
632
|
-
type: 'shadow',
|
|
633
|
-
element: current,
|
|
634
|
-
container: rootNode,
|
|
635
|
-
host: rootNode.host
|
|
636
|
-
});
|
|
637
|
-
current = rootNode.host;
|
|
638
|
-
depth++;
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
// Check for iframe
|
|
642
|
-
const ownerDocument = current.ownerDocument;
|
|
643
|
-
const frameElement = (_a = ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement;
|
|
644
|
-
if (frameElement) {
|
|
645
|
-
path.unshift({
|
|
646
|
-
type: 'iframe',
|
|
647
|
-
element: current,
|
|
648
|
-
container: frameElement,
|
|
649
|
-
document: ownerDocument
|
|
650
|
-
});
|
|
651
|
-
current = frameElement;
|
|
652
|
-
depth++;
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
break;
|
|
656
|
-
}
|
|
657
|
-
return path;
|
|
658
|
-
}
|
|
659
|
-
function getSelectorPath(element) {
|
|
660
|
-
if (!element)
|
|
661
|
-
return '';
|
|
662
|
-
// Get the complete context path
|
|
663
|
-
const contextPath = getContextPath(element);
|
|
664
|
-
if (contextPath.length > 0) {
|
|
665
|
-
const selectorParts = [];
|
|
666
|
-
contextPath.forEach((context, index) => {
|
|
667
|
-
const containerSelector = getNonUniqueSelector(context.type === 'shadow' ? context.host : context.container);
|
|
668
|
-
if (index === contextPath.length - 1) {
|
|
669
|
-
const elementSelector = getNonUniqueSelector(element);
|
|
670
|
-
const delimiter = context.type === 'shadow' ? ' >> ' : ' :>> ';
|
|
671
|
-
selectorParts.push(`${containerSelector}${delimiter}${elementSelector}`);
|
|
672
|
-
}
|
|
673
|
-
else {
|
|
674
|
-
selectorParts.push(containerSelector);
|
|
675
|
-
}
|
|
676
|
-
});
|
|
677
|
-
return selectorParts.join(contextPath[0].type === 'shadow' ? ' >> ' : ' :>> ');
|
|
678
|
-
}
|
|
679
|
-
const elementSelector = getNonUniqueSelector(element);
|
|
680
|
-
if (elementSelector.includes('.') && elementSelector.split('.').length > 1) {
|
|
681
|
-
return elementSelector;
|
|
682
|
-
}
|
|
683
|
-
const path = [];
|
|
684
|
-
let currentElement = element;
|
|
685
|
-
const MAX_DEPTH = 2;
|
|
686
|
-
let depth = 0;
|
|
687
|
-
while (currentElement && currentElement !== document.body && depth < MAX_DEPTH) {
|
|
688
|
-
const selector = getNonUniqueSelector(currentElement);
|
|
689
|
-
path.unshift(selector);
|
|
690
|
-
if (!currentElement.parentElement)
|
|
691
|
-
break;
|
|
692
|
-
currentElement = currentElement.parentElement;
|
|
693
|
-
depth++;
|
|
694
|
-
}
|
|
695
|
-
return path.join(' > ');
|
|
696
|
-
}
|
|
697
|
-
// Main processing logic
|
|
698
|
-
let targetElement = element;
|
|
699
|
-
// Check for shadow DOM
|
|
700
|
-
targetElement = traverseShadowDOM(targetElement);
|
|
701
|
-
// Special handling for table cells
|
|
702
|
-
if (targetElement.tagName === 'TD' || targetElement.tagName === 'TH') {
|
|
703
|
-
const tableParent = targetElement.closest('table');
|
|
704
|
-
if (tableParent) {
|
|
705
|
-
targetElement = tableParent;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
// Handle parent traversal for non-table elements
|
|
709
|
-
if (targetElement.tagName !== 'TABLE') {
|
|
710
|
-
while (targetElement.parentElement) {
|
|
711
|
-
if (targetElement.tagName.toLowerCase() === 'body' ||
|
|
712
|
-
targetElement.tagName.toLowerCase() === 'html') {
|
|
713
|
-
break;
|
|
714
|
-
}
|
|
715
|
-
const parentRect = targetElement.parentElement.getBoundingClientRect();
|
|
716
|
-
const childRect = targetElement.getBoundingClientRect();
|
|
717
|
-
const fullyContained = parentRect.left <= childRect.left &&
|
|
718
|
-
parentRect.right >= childRect.right &&
|
|
719
|
-
parentRect.top <= childRect.top &&
|
|
720
|
-
parentRect.bottom >= childRect.bottom;
|
|
721
|
-
const significantOverlap = (childRect.width * childRect.height) /
|
|
722
|
-
(parentRect.width * parentRect.height) > 0.5;
|
|
723
|
-
if (fullyContained && significantOverlap) {
|
|
724
|
-
const nextParent = targetElement.parentElement;
|
|
725
|
-
if (nextParent.tagName.toLowerCase() !== 'body' &&
|
|
726
|
-
nextParent.tagName.toLowerCase() !== 'html') {
|
|
727
|
-
targetElement = nextParent;
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
break;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
break;
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
const generalSelector = getSelectorPath(targetElement);
|
|
739
|
-
return { generalSelector };
|
|
740
|
-
});
|
|
741
|
-
return selectors || { generalSelector: '' };
|
|
742
|
-
}
|
|
743
|
-
else {
|
|
744
|
-
// When we have a list selector, we process it directly
|
|
745
|
-
const selectors = yield elementHandle.evaluate((element) => {
|
|
746
|
-
// Reuse the same helper functions as above
|
|
747
|
-
function getNonUniqueSelector(element) {
|
|
748
|
-
let selector = element.tagName.toLowerCase();
|
|
749
|
-
if (selector === 'td' && element.parentElement) {
|
|
750
|
-
const siblings = Array.from(element.parentElement.children);
|
|
751
|
-
const position = siblings.indexOf(element) + 1;
|
|
752
|
-
return `${selector}:nth-child(${position})`;
|
|
753
|
-
}
|
|
754
|
-
if (element.className) {
|
|
755
|
-
const classes = element.className.split(/\s+/).filter(Boolean);
|
|
756
|
-
if (classes.length > 0) {
|
|
757
|
-
const validClasses = classes.filter(cls => !cls.startsWith('!') && !cls.includes(':'));
|
|
758
|
-
if (validClasses.length > 0) {
|
|
759
|
-
selector += '.' + validClasses.map(cls => CSS.escape(cls)).join('.');
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return selector;
|
|
764
|
-
}
|
|
765
|
-
function getContextPath(element) {
|
|
766
|
-
var _a;
|
|
767
|
-
const path = [];
|
|
768
|
-
let current = element;
|
|
769
|
-
let depth = 0;
|
|
770
|
-
const MAX_DEPTH = 4;
|
|
771
|
-
while (current && depth < MAX_DEPTH) {
|
|
772
|
-
const rootNode = current.getRootNode();
|
|
773
|
-
if (rootNode instanceof ShadowRoot) {
|
|
774
|
-
path.unshift({
|
|
775
|
-
type: 'shadow',
|
|
776
|
-
element: current,
|
|
777
|
-
container: rootNode,
|
|
778
|
-
host: rootNode.host
|
|
779
|
-
});
|
|
780
|
-
current = rootNode.host;
|
|
781
|
-
depth++;
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
const ownerDocument = current.ownerDocument;
|
|
785
|
-
const frameElement = (_a = ownerDocument === null || ownerDocument === void 0 ? void 0 : ownerDocument.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement;
|
|
786
|
-
if (frameElement) {
|
|
787
|
-
path.unshift({
|
|
788
|
-
type: 'iframe',
|
|
789
|
-
element: current,
|
|
790
|
-
container: frameElement,
|
|
791
|
-
document: ownerDocument
|
|
792
|
-
});
|
|
793
|
-
current = frameElement;
|
|
794
|
-
depth++;
|
|
795
|
-
continue;
|
|
796
|
-
}
|
|
797
|
-
break;
|
|
798
|
-
}
|
|
799
|
-
return path;
|
|
800
|
-
}
|
|
801
|
-
function getSelectorPath(element) {
|
|
802
|
-
if (!element)
|
|
803
|
-
return '';
|
|
804
|
-
// Get the complete context path
|
|
805
|
-
const contextPath = getContextPath(element);
|
|
806
|
-
if (contextPath.length > 0) {
|
|
807
|
-
const selectorParts = [];
|
|
808
|
-
contextPath.forEach((context, index) => {
|
|
809
|
-
const containerSelector = getNonUniqueSelector(context.type === 'shadow' ? context.host : context.container);
|
|
810
|
-
if (index === contextPath.length - 1) {
|
|
811
|
-
const elementSelector = getNonUniqueSelector(element);
|
|
812
|
-
const delimiter = context.type === 'shadow' ? ' >> ' : ' :>> ';
|
|
813
|
-
selectorParts.push(`${containerSelector}${delimiter}${elementSelector}`);
|
|
814
|
-
}
|
|
815
|
-
else {
|
|
816
|
-
selectorParts.push(containerSelector);
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
return selectorParts.join(contextPath[0].type === 'shadow' ? ' >> ' : ' :>> ');
|
|
820
|
-
}
|
|
821
|
-
const elementSelector = getNonUniqueSelector(element);
|
|
822
|
-
if (elementSelector.includes('.') && elementSelector.split('.').length > 1) {
|
|
823
|
-
return elementSelector;
|
|
824
|
-
}
|
|
825
|
-
const path = [];
|
|
826
|
-
let currentElement = element;
|
|
827
|
-
const MAX_DEPTH = 2;
|
|
828
|
-
let depth = 0;
|
|
829
|
-
while (currentElement && currentElement !== document.body && depth < MAX_DEPTH) {
|
|
830
|
-
const selector = getNonUniqueSelector(currentElement);
|
|
831
|
-
path.unshift(selector);
|
|
832
|
-
if (!currentElement.parentElement)
|
|
833
|
-
break;
|
|
834
|
-
currentElement = currentElement.parentElement;
|
|
835
|
-
depth++;
|
|
836
|
-
}
|
|
837
|
-
return path.join(' > ');
|
|
838
|
-
}
|
|
839
|
-
const generalSelector = getSelectorPath(element);
|
|
840
|
-
return { generalSelector };
|
|
841
|
-
});
|
|
842
|
-
return selectors || { generalSelector: '' };
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
catch (error) {
|
|
846
|
-
console.error('Error in getNonUniqueSelectors:', error);
|
|
847
|
-
return { generalSelector: '' };
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
exports.generateNonUniqueSelectors = generateNonUniqueSelectors;
|
|
12
|
+
exports.generateFieldSelectorFromFallback = exports.generateListFieldSelectorFromFallback = exports.generateListSelectorFromFallback = void 0;
|
|
851
13
|
/**
|
|
852
14
|
* Generate new list selector from fallback element (based on your reference implementation)
|
|
853
15
|
* @param page - Playwright page object
|