cdpbrowser 0.1.0__py3-none-any.whl

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.
cdpbrowser/bridge.js ADDED
@@ -0,0 +1,909 @@
1
+ /*
2
+ * cdpbrowser page bridge.
3
+ *
4
+ * Injected automatically into every new document via
5
+ * Page.addScriptToEvaluateOnNewDocument; global pollution is limited to the
6
+ * single window.__cdpb property. Every function runs synchronously and
7
+ * returns only JSON-serializable values, so Runtime.evaluate
8
+ * (returnByValue=true) results come back into Python as-is.
9
+ *
10
+ * The locator argument matches the shape of locators.py's to_bridge_arg()
11
+ * output, {"s": "css"|"id"|"t"|"x", "v": string}:
12
+ * css — document.querySelector
13
+ * id — exact match on the data-testid attribute (compared by iteration
14
+ * to avoid value-escaping issues)
15
+ * t — text matching: on whitespace-normalized textContent, exact match
16
+ * first, then a case-insensitive substring fallback. With nesting
17
+ * (button>span), prefers the element with the shortest text (the
18
+ * leaf-most one).
19
+ * x — XPath via document.evaluate FIRST_ORDERED_NODE
20
+ * r — role matching: explicit role attribute first, then the implicit
21
+ * role map, case-insensitive exact match, first in document order.
22
+ * The value splits into role/name at the first space, as in
23
+ * "button" or "button Save" (name is a case-insensitive exact match
24
+ * against the accessible name).
25
+ * l — label matching: case-insensitive exact match against the
26
+ * accessible name (simplified accname), first in document order.
27
+ *
28
+ * The accessible name is a **simplified accname** — aria-label →
29
+ * aria-labelledby → label[for] → wrapping label → name-from-content
30
+ * (button/link only) → placeholder → img alt → title, in that order.
31
+ * Boundaries: aria-describedby, recursion exclusion for purely decorative
32
+ * elements, and name-from-content extensions (headings etc.) are not
33
+ * implemented (the full two-pass accname algorithm is out of scope).
34
+ *
35
+ * Atomic primitives (click/setValue) run the find→visible→enabled check in
36
+ * a single step and return {"ok": true} or
37
+ * {"ok": false, "reason": ...[, "describe"]}. reason is "not-found" |
38
+ * "not-visible" | "not-enabled"; describe is failure diagnostics attached
39
+ * only when the element exists.
40
+ */
41
+ (function () {
42
+ "use strict";
43
+
44
+ var TESTID_ATTR = "data-testid";
45
+
46
+ // Non-display elements excluded from text matching.
47
+ var SKIP_TAGS = {
48
+ SCRIPT: true,
49
+ STYLE: true,
50
+ HEAD: true,
51
+ TITLE: true,
52
+ META: true,
53
+ LINK: true,
54
+ NOSCRIPT: true,
55
+ TEMPLATE: true,
56
+ };
57
+
58
+ function normText(value) {
59
+ return String(value === null || value === undefined ? "" : value)
60
+ .replace(/\s+/g, " ")
61
+ .trim();
62
+ }
63
+
64
+ // Direct text: collect only the element's own text nodes and normalize (for failure diagnostics).
65
+ function directText(el) {
66
+ var text = "";
67
+ for (var node = el.firstChild; node; node = node.nextSibling) {
68
+ if (node.nodeType === 3) {
69
+ text += node.nodeValue;
70
+ }
71
+ }
72
+ return normText(text);
73
+ }
74
+
75
+ function allElements() {
76
+ var root = document.body || document.documentElement;
77
+ if (!root || !root.querySelectorAll) {
78
+ return [];
79
+ }
80
+ return root.querySelectorAll("*");
81
+ }
82
+
83
+ // deep: modifier — pierce open shadow DOM (opt-in).
84
+ // Scans the document root plus every open shadowRoot found recursively.
85
+ // closed shadowRoots are unreachable in the first place (boundary).
86
+ function deepRoots() {
87
+ var roots = [document];
88
+ var queue = [document];
89
+ while (queue.length) {
90
+ var current = queue.shift();
91
+ var elements;
92
+ try {
93
+ elements = current.querySelectorAll("*");
94
+ } catch (err) {
95
+ continue;
96
+ }
97
+ for (var i = 0; i < elements.length; i++) {
98
+ var sr = elements[i].shadowRoot;
99
+ if (sr) {
100
+ roots.push(sr);
101
+ queue.push(sr);
102
+ }
103
+ }
104
+ }
105
+ return roots;
106
+ }
107
+
108
+ function deepElements() {
109
+ var roots = deepRoots();
110
+ var pool = [];
111
+ for (var i = 0; i < roots.length; i++) {
112
+ var body = roots[i].body || roots[i];
113
+ var list;
114
+ try {
115
+ list = body.querySelectorAll("*");
116
+ } catch (err) {
117
+ continue;
118
+ }
119
+ for (var j = 0; j < list.length; j++) {
120
+ pool.push(list[j]);
121
+ }
122
+ }
123
+ return pool;
124
+ }
125
+
126
+ function findByTestId(value) {
127
+ var candidates = document.querySelectorAll("[" + TESTID_ATTR + "]");
128
+ for (var i = 0; i < candidates.length; i++) {
129
+ if (candidates[i].getAttribute(TESTID_ATTR) === value) {
130
+ return candidates[i];
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+
136
+ function findByXPath(expression) {
137
+ var result = document.evaluate(
138
+ expression,
139
+ document,
140
+ null,
141
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
142
+ null
143
+ );
144
+ var node = result.singleNodeValue;
145
+ return node && node.nodeType === 1 ? node : null;
146
+ }
147
+
148
+ function findByText(value, pool) {
149
+ var wanted = normText(value);
150
+ if (!wanted) {
151
+ return null;
152
+ }
153
+ var lower = wanted.toLowerCase();
154
+ var elements = pool || allElements();
155
+ var bestExact = null;
156
+ var bestExactLen = 0;
157
+ var bestPartial = null;
158
+ var bestPartialLen = 0;
159
+ for (var i = 0; i < elements.length; i++) {
160
+ var el = elements[i];
161
+ if (SKIP_TAGS[el.tagName]) {
162
+ continue;
163
+ }
164
+ var text = normText(el.textContent);
165
+ if (!text) {
166
+ continue;
167
+ }
168
+ if (text === wanted) {
169
+ // Exact match: prefer the element with the shortest text (closest to
170
+ // the leaf), ties broken by document order first. Stable when parent
171
+ // and child share the same text (button>span).
172
+ if (bestExact === null || text.length < bestExactLen) {
173
+ bestExact = el;
174
+ bestExactLen = text.length;
175
+ }
176
+ } else if (text.toLowerCase().indexOf(lower) !== -1) {
177
+ if (bestPartial === null || text.length < bestPartialLen) {
178
+ bestPartial = el;
179
+ bestPartialLen = text.length;
180
+ }
181
+ }
182
+ }
183
+ return bestExact !== null ? bestExact : bestPartial;
184
+ }
185
+
186
+ // ----------------------------------------------------------------------
187
+ // ARIA — implicit role + simplified accessible name (see boundary note above)
188
+ // ----------------------------------------------------------------------
189
+
190
+ function implicitRole(el) {
191
+ switch (el.tagName) {
192
+ case "A":
193
+ return el.hasAttribute("href") ? "link" : null;
194
+ case "BUTTON":
195
+ return "button";
196
+ case "INPUT": {
197
+ var type = (el.getAttribute("type") || "text").toLowerCase();
198
+ if (type === "checkbox") return "checkbox";
199
+ if (type === "radio") return "radio";
200
+ if (type === "submit" || type === "button") return "button";
201
+ if (type === "hidden") return null;
202
+ return "textbox";
203
+ }
204
+ case "SELECT":
205
+ return "combobox";
206
+ case "TEXTAREA":
207
+ return "textbox";
208
+ case "H1":
209
+ case "H2":
210
+ case "H3":
211
+ case "H4":
212
+ case "H5":
213
+ case "H6":
214
+ return "heading";
215
+ case "UL":
216
+ return "list";
217
+ case "LI":
218
+ return "listitem";
219
+ case "NAV":
220
+ return "navigation";
221
+ case "MAIN":
222
+ return "main";
223
+ case "TABLE":
224
+ return "table";
225
+ case "IMG":
226
+ return "img";
227
+ case "FORM":
228
+ return "form";
229
+ default:
230
+ return null;
231
+ }
232
+ }
233
+
234
+ // Effective role list: explicit role attribute tokens first, then the implicit role.
235
+ function effectiveRoles(el) {
236
+ var roles = [];
237
+ var explicit = el.getAttribute("role");
238
+ if (explicit) {
239
+ var tokens = normText(explicit).toLowerCase().split(" ");
240
+ for (var i = 0; i < tokens.length; i++) {
241
+ if (tokens[i] && roles.indexOf(tokens[i]) === -1) {
242
+ roles.push(tokens[i]);
243
+ }
244
+ }
245
+ }
246
+ var implicit = implicitRole(el);
247
+ if (implicit && roles.indexOf(implicit) === -1) {
248
+ roles.push(implicit);
249
+ }
250
+ return roles;
251
+ }
252
+
253
+ // Tags where name-from-content applies (simplified: button/link only).
254
+ function hasNameFromContent(el) {
255
+ var tag = el.tagName;
256
+ return tag === "BUTTON" || (tag === "A" && el.hasAttribute("href"));
257
+ }
258
+
259
+ function attributeValueSelector(attr, value) {
260
+ // Escape the attribute value string — CSS.escape is for identifiers, unfit for quoted values.
261
+ var escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
262
+ return "[" + attr + '="' + escaped + '"]';
263
+ }
264
+
265
+ function accessibleName(el) {
266
+ var label = normText(el.getAttribute("aria-label"));
267
+ if (label) {
268
+ return label;
269
+ }
270
+ var labelledby = normText(el.getAttribute("aria-labelledby"));
271
+ if (labelledby) {
272
+ var parts = [];
273
+ var ids = labelledby.split(" ");
274
+ for (var i = 0; i < ids.length; i++) {
275
+ var target = document.getElementById(ids[i]);
276
+ if (target) {
277
+ parts.push(target.textContent || "");
278
+ }
279
+ }
280
+ var joined = normText(parts.join(" "));
281
+ if (joined) {
282
+ return joined;
283
+ }
284
+ }
285
+ if (el.id) {
286
+ var forLabel = document.querySelector(
287
+ "label" + attributeValueSelector("for", el.id)
288
+ );
289
+ var forText = forLabel ? normText(forLabel.textContent) : "";
290
+ if (forText) {
291
+ return forText;
292
+ }
293
+ }
294
+ // Wrapping label. Exclude the case where the element itself is a label —
295
+ // closest("label") returns the element itself, so without the exclusion
296
+ // a <label> element would shadow its associated input and findByLabel
297
+ // would return the label instead of the form control.
298
+ var wrapping =
299
+ el.closest && el.tagName !== "LABEL" ? el.closest("label") : null;
300
+ var wrappingText = wrapping ? normText(wrapping.textContent) : "";
301
+ if (wrappingText) {
302
+ return wrappingText;
303
+ }
304
+ if (hasNameFromContent(el)) {
305
+ var content = normText(el.textContent);
306
+ if (content) {
307
+ return content;
308
+ }
309
+ }
310
+ var placeholder = normText(el.getAttribute("placeholder"));
311
+ if (placeholder) {
312
+ return placeholder;
313
+ }
314
+ if (el.tagName === "IMG") {
315
+ var alt = normText(el.getAttribute("alt"));
316
+ if (alt) {
317
+ return alt;
318
+ }
319
+ }
320
+ return normText(el.getAttribute("title"));
321
+ }
322
+
323
+ function matchesName(el, wantedName) {
324
+ return accessibleName(el).toLowerCase() === wantedName.toLowerCase();
325
+ }
326
+
327
+ function findByRole(value, pool) {
328
+ var elements = pool || allElements();
329
+ var spec = normText(value);
330
+ if (!spec) {
331
+ return null;
332
+ }
333
+ var sep = spec.indexOf(" ");
334
+ var wantedRole = (sep === -1 ? spec : spec.slice(0, sep)).toLowerCase();
335
+ var wantedName = sep === -1 ? null : spec.slice(sep + 1);
336
+ for (var i = 0; i < elements.length; i++) {
337
+ var el = elements[i];
338
+ if (SKIP_TAGS[el.tagName]) {
339
+ continue;
340
+ }
341
+ if (effectiveRoles(el).indexOf(wantedRole) === -1) {
342
+ continue;
343
+ }
344
+ if (wantedName !== null && !matchesName(el, wantedName)) {
345
+ continue;
346
+ }
347
+ return el;
348
+ }
349
+ return null;
350
+ }
351
+
352
+ function findByLabel(value, pool) {
353
+ var wanted = normText(value);
354
+ if (!wanted) {
355
+ return null;
356
+ }
357
+ var elements = pool || allElements();
358
+ for (var i = 0; i < elements.length; i++) {
359
+ var el = elements[i];
360
+ if (SKIP_TAGS[el.tagName]) {
361
+ continue;
362
+ }
363
+ var name = accessibleName(el);
364
+ if (name && name.toLowerCase() === wanted.toLowerCase()) {
365
+ return el;
366
+ }
367
+ }
368
+ return null;
369
+ }
370
+
371
+ // Locator resolution. Unknown strategies/empty values are treated as "not
372
+ // found" (null). Invalid CSS/XPath syntax is intentionally rethrown as-is
373
+ // — the browser's SyntaxError must surface as exceptionDetails so it stays
374
+ // diagnosable from Python.
375
+ // ext: strategy — looks up framework components from the registry via Ext
376
+ // JS ComponentQuery and returns the rendered DOM (el.dom). Bypasses DOM
377
+ // selectors, anchoring on the app code's stable component structure
378
+ // (itemId/reference/text configs). Throws an explicit error when Ext is
379
+ // absent rather than failing silently with not-found.
380
+ function extComponents(selector) {
381
+ if (
382
+ typeof window.Ext !== "object" ||
383
+ window.Ext === null ||
384
+ !window.Ext.ComponentQuery ||
385
+ typeof window.Ext.ComponentQuery.query !== "function"
386
+ ) {
387
+ throw new Error(
388
+ "ext: selector requires Ext JS — window.Ext.ComponentQuery not found"
389
+ );
390
+ }
391
+ var matches = window.Ext.ComponentQuery.query(String(selector)) || [];
392
+ var rendered = [];
393
+ for (var i = 0; i < matches.length; i++) {
394
+ var comp = matches[i];
395
+ var dom =
396
+ (comp.el && comp.el.dom) ||
397
+ (comp.element && comp.element.dom) ||
398
+ null; // classic: el.dom / modern: element.dom
399
+ if (dom && dom.nodeType === 1) {
400
+ rendered.push(dom);
401
+ }
402
+ }
403
+ return rendered;
404
+ }
405
+
406
+ function findByExt(value) {
407
+ var all = extComponents(value);
408
+ return all.length ? all[0] : null;
409
+ }
410
+
411
+ function resolve(locator) {
412
+ if (!locator || typeof locator !== "object") {
413
+ return null;
414
+ }
415
+ if (locator.p) {
416
+ return resolveDeep(locator);
417
+ }
418
+ var value = locator.v;
419
+ if (typeof value !== "string") {
420
+ return null;
421
+ }
422
+ switch (locator.s) {
423
+ case "css":
424
+ return document.querySelector(value);
425
+ case "id":
426
+ return findByTestId(value);
427
+ case "t":
428
+ return findByText(value);
429
+ case "x":
430
+ return findByXPath(value);
431
+ case "r":
432
+ return findByRole(value);
433
+ case "l":
434
+ return findByLabel(value);
435
+ case "e":
436
+ return findByExt(value);
437
+ default:
438
+ return null;
439
+ }
440
+ }
441
+
442
+ function isVisible(el) {
443
+ if (!el || typeof el.getBoundingClientRect !== "function") {
444
+ return false;
445
+ }
446
+ var style = window.getComputedStyle(el);
447
+ if (
448
+ style.display === "none" ||
449
+ style.visibility === "hidden" ||
450
+ style.visibility === "collapse"
451
+ ) {
452
+ return false;
453
+ }
454
+ var box = el.getBoundingClientRect();
455
+ return box.width > 0 && box.height > 0;
456
+ }
457
+
458
+ // :disabled covers both form controls and controls under a disabled
459
+ // fieldset. Every other element never matches :disabled, hence enabled=true.
460
+ function isEnabled(el) {
461
+ return !el.matches(":disabled");
462
+ }
463
+
464
+ function state(el) {
465
+ return { visible: isVisible(el), enabled: isEnabled(el) };
466
+ }
467
+
468
+ function status(locator) {
469
+ var el = resolve(locator);
470
+ if (!el) {
471
+ return null;
472
+ }
473
+ var s = state(el);
474
+ return { exists: true, visible: s.visible, enabled: s.enabled };
475
+ }
476
+
477
+ function describe(el) {
478
+ return {
479
+ tag: el.tagName.toLowerCase(),
480
+ text: directText(el),
481
+ testid: el.getAttribute(TESTID_ATTR),
482
+ id: el.id || null,
483
+ classes: Array.prototype.slice.call(el.classList || []),
484
+ };
485
+ }
486
+
487
+ function failure(el, reason) {
488
+ var result = { ok: false, reason: reason };
489
+ if (el) {
490
+ result.describe = describe(el);
491
+ }
492
+ return result;
493
+ }
494
+
495
+ // Atomic precondition check: find→visible→enabled. On failure, returns a result carrying the reason.
496
+ function checkActionable(locator) {
497
+ var el = resolve(locator);
498
+ if (!el) {
499
+ return { el: null, error: failure(null, "not-found") };
500
+ }
501
+ var s = state(el);
502
+ if (!s.visible) {
503
+ return { el: el, error: failure(el, "not-visible") };
504
+ }
505
+ if (!s.enabled) {
506
+ return { el: el, error: failure(el, "not-enabled") };
507
+ }
508
+ return { el: el, error: null };
509
+ }
510
+
511
+ function click(locator) {
512
+ var check = checkActionable(locator);
513
+ if (check.error) {
514
+ return check.error;
515
+ }
516
+ check.el.click();
517
+ return { ok: true };
518
+ }
519
+
520
+ // React-compatible value setting: the prototype native setter must be used
521
+ // so React's value tracking hook (trigger) fires. input/change events are
522
+ // bubbled afterwards.
523
+ function setElementValue(el, value) {
524
+ var stringValue = value === null || value === undefined ? "" : String(value);
525
+ var proto = null;
526
+ if (typeof HTMLInputElement !== "undefined" && el instanceof HTMLInputElement) {
527
+ proto = HTMLInputElement.prototype;
528
+ } else if (
529
+ typeof HTMLTextAreaElement !== "undefined" &&
530
+ el instanceof HTMLTextAreaElement
531
+ ) {
532
+ proto = HTMLTextAreaElement.prototype;
533
+ }
534
+ var applied = false;
535
+ if (proto) {
536
+ var descriptor = Object.getOwnPropertyDescriptor(proto, "value");
537
+ if (descriptor && typeof descriptor.set === "function") {
538
+ descriptor.set.call(el, stringValue);
539
+ applied = true;
540
+ }
541
+ }
542
+ if (!applied) {
543
+ try {
544
+ el.value = stringValue;
545
+ } catch (err) {
546
+ // Element without a value property — still proceed through event dispatch.
547
+ }
548
+ }
549
+ el.dispatchEvent(new Event("input", { bubbles: true }));
550
+ el.dispatchEvent(new Event("change", { bubbles: true }));
551
+ }
552
+
553
+ function setValue(locator, value) {
554
+ var check = checkActionable(locator);
555
+ if (check.error) {
556
+ return check.error;
557
+ }
558
+ setElementValue(check.el, value);
559
+ return { ok: true };
560
+ }
561
+
562
+ function getText(locator) {
563
+ var el = resolve(locator);
564
+ if (!el) {
565
+ return { ok: false, reason: "not-found" };
566
+ }
567
+ var text = el.textContent;
568
+ return { ok: true, text: text === null || text === undefined ? "" : String(text) };
569
+ }
570
+
571
+ function getAttr(locator, name) {
572
+ var el = resolve(locator);
573
+ if (!el) {
574
+ return { ok: false, reason: "not-found" };
575
+ }
576
+ var value = el.getAttribute(String(name));
577
+ return { ok: true, value: value === null ? null : String(value) };
578
+ }
579
+
580
+ // Multi-match across all strategies. For count snapshots only — unlike
581
+ // findOne's tiebreak (shortest text first, etc.), returns the full
582
+ // document-order list as-is.
583
+ // A ShadowRoot is ineligible as a context node for document.evaluate
584
+ // (#document-fragment). Inside a shadow root, the first child element is
585
+ // used as the context and "//x" expressions are rewritten to the relative
586
+ // ".//x". Absolute paths starting with "/" are only meaningful from the
587
+ // document root, so they are skipped inside shadows.
588
+ function evaluateXPathInRoot(expression, root, resultType) {
589
+ var context = root;
590
+ var expr = expression;
591
+ if (root !== document) {
592
+ context = root.firstElementChild;
593
+ if (!context) {
594
+ return null;
595
+ }
596
+ if (expression.indexOf("//") === 0) {
597
+ expr = "." + expression;
598
+ } else if (expression.charAt(0) === "/") {
599
+ return null;
600
+ }
601
+ }
602
+ try {
603
+ return document.evaluate(expr, context, null, resultType, null);
604
+ } catch (err) {
605
+ throw err; // syntax errors propagate as-is (same contract as shallow)
606
+ }
607
+ }
608
+
609
+ // deep: modifier lookup — sweeps the document plus every open shadowRoot
610
+ // in document order. closed roots are unreachable (boundary). CSS/XPath
611
+ // syntax errors are rethrown as-is, same as shallow, to stay diagnosable.
612
+ function resolveDeep(locator) {
613
+ var value = locator.v;
614
+ if (typeof value !== "string") {
615
+ return null;
616
+ }
617
+ switch (locator.s) {
618
+ case "css":
619
+ var roots = deepRoots();
620
+ for (var i = 0; i < roots.length; i++) {
621
+ var hit = roots[i].querySelector(value);
622
+ if (hit) {
623
+ return hit;
624
+ }
625
+ }
626
+ return null;
627
+ case "id":
628
+ var poolId = deepElements();
629
+ for (var a = 0; a < poolId.length; a++) {
630
+ if (poolId[a].getAttribute(TESTID_ATTR) === value) {
631
+ return poolId[a];
632
+ }
633
+ }
634
+ return null;
635
+ case "t":
636
+ return findByText(value, deepElements());
637
+ case "x":
638
+ var rootsX = deepRoots();
639
+ for (var b = 0; b < rootsX.length; b++) {
640
+ var result = evaluateXPathInRoot(
641
+ value,
642
+ rootsX[b],
643
+ XPathResult.FIRST_ORDERED_NODE_TYPE
644
+ );
645
+ var node = result && result.singleNodeValue;
646
+ if (node && node.nodeType === 1) {
647
+ return node;
648
+ }
649
+ }
650
+ return null;
651
+ case "r":
652
+ return findByRole(value, deepElements());
653
+ case "l":
654
+ return findByLabel(value, deepElements());
655
+ case "e":
656
+ return findByExt(value);
657
+ default:
658
+ return null;
659
+ }
660
+ }
661
+
662
+ function resolveAllDeep(locator) {
663
+ var value = locator.v;
664
+ var out = [];
665
+ var roots = deepRoots();
666
+ switch (locator.s) {
667
+ case "css":
668
+ for (var i = 0; i < roots.length; i++) {
669
+ out = out.concat(
670
+ Array.prototype.slice.call(roots[i].querySelectorAll(value))
671
+ );
672
+ }
673
+ return out;
674
+ case "x":
675
+ for (var j = 0; j < roots.length; j++) {
676
+ var snap = evaluateXPathInRoot(
677
+ value,
678
+ roots[j],
679
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
680
+ );
681
+ if (!snap) {
682
+ continue;
683
+ }
684
+ for (var k = 0; k < snap.snapshotLength; k++) {
685
+ var node = snap.snapshotItem(k);
686
+ if (node && node.nodeType === 1) {
687
+ out.push(node);
688
+ }
689
+ }
690
+ }
691
+ return out;
692
+ default:
693
+ // id/t/r/l — flatten the deep pool in document order, apply the same predicate.
694
+ return resolveAll({
695
+ s: locator.s,
696
+ v: value,
697
+ p: 0,
698
+ __deepPool: deepElements(),
699
+ });
700
+ }
701
+ }
702
+
703
+ function resolveAll(locator) {
704
+ if (!locator || typeof locator !== "object") {
705
+ return [];
706
+ }
707
+ if (locator.p) {
708
+ return resolveAllDeep(locator);
709
+ }
710
+ var value = locator.v;
711
+ if (typeof value !== "string") {
712
+ return [];
713
+ }
714
+ var out = [];
715
+ switch (locator.s) {
716
+ case "css":
717
+ return Array.prototype.slice.call(document.querySelectorAll(value));
718
+ case "id":
719
+ var candidates = locator.__deepPool || document.querySelectorAll(
720
+ "[" + TESTID_ATTR + "]"
721
+ );
722
+ for (var i = 0; i < candidates.length; i++) {
723
+ if (candidates[i].getAttribute(TESTID_ATTR) === value) {
724
+ out.push(candidates[i]);
725
+ }
726
+ }
727
+ return out;
728
+ case "x":
729
+ var snap = document.evaluate(
730
+ value,
731
+ document,
732
+ null,
733
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
734
+ null
735
+ );
736
+ for (var j = 0; j < snap.snapshotLength; j++) {
737
+ var node = snap.snapshotItem(j);
738
+ if (node && node.nodeType === 1) {
739
+ out.push(node);
740
+ }
741
+ }
742
+ return out;
743
+ case "t":
744
+ var wanted = normText(value);
745
+ var lowerT = wanted.toLowerCase();
746
+ var elementsT = locator.__deepPool || allElements();
747
+ for (var k = 0; k < elementsT.length; k++) {
748
+ var elT = elementsT[k];
749
+ if (SKIP_TAGS[elT.tagName]) {
750
+ continue;
751
+ }
752
+ var textT = normText(elT.textContent);
753
+ if (!textT) {
754
+ continue;
755
+ }
756
+ if (textT === wanted || textT.toLowerCase().indexOf(lowerT) !== -1) {
757
+ out.push(elT);
758
+ }
759
+ }
760
+ return out;
761
+ case "r":
762
+ var spec = normText(value);
763
+ var sepR = spec.indexOf(" ");
764
+ var wantedRole = (sepR === -1 ? spec : spec.slice(0, sepR)).toLowerCase();
765
+ var wantedNameR = sepR === -1 ? null : spec.slice(sepR + 1);
766
+ var elementsR = locator.__deepPool || allElements();
767
+ for (var m = 0; m < elementsR.length; m++) {
768
+ var elR = elementsR[m];
769
+ if (SKIP_TAGS[elR.tagName]) {
770
+ continue;
771
+ }
772
+ if (effectiveRoles(elR).indexOf(wantedRole) === -1) {
773
+ continue;
774
+ }
775
+ if (wantedNameR !== null && !matchesName(elR, wantedNameR)) {
776
+ continue;
777
+ }
778
+ out.push(elR);
779
+ }
780
+ return out;
781
+ case "l":
782
+ var wantedL = normText(value).toLowerCase();
783
+ var elementsL = locator.__deepPool || allElements();
784
+ for (var n = 0; n < elementsL.length; n++) {
785
+ var elL = elementsL[n];
786
+ if (SKIP_TAGS[elL.tagName]) {
787
+ continue;
788
+ }
789
+ var nameL = accessibleName(elL);
790
+ if (nameL && nameL.toLowerCase() === wantedL) {
791
+ out.push(elL);
792
+ }
793
+ }
794
+ return out;
795
+ case "e":
796
+ return extComponents(value);
797
+ default:
798
+ return [];
799
+ }
800
+ }
801
+
802
+ function count(locator) {
803
+ var n = resolveAll(locator).length;
804
+ return { count: n };
805
+ }
806
+
807
+ function focus(locator) {
808
+ var el = resolve(locator);
809
+ if (!el) {
810
+ return { ok: false, reason: "not-found" };
811
+ }
812
+ if (typeof el.focus !== "function") {
813
+ return { ok: false, reason: "not-focusable" };
814
+ }
815
+ el.focus();
816
+ return { ok: true };
817
+ }
818
+
819
+ function rect(locator) {
820
+ var el = resolve(locator);
821
+ if (!el) {
822
+ return { ok: false, reason: "not-found" };
823
+ }
824
+ var r = el.getBoundingClientRect();
825
+ return {
826
+ ok: true,
827
+ x: r.left,
828
+ y: r.top,
829
+ width: r.width,
830
+ height: r.height,
831
+ };
832
+ }
833
+
834
+ function scrollIntoView(locator) {
835
+ var el = resolve(locator);
836
+ if (!el) {
837
+ return { ok: false, reason: "not-found" };
838
+ }
839
+ if (typeof el.scrollIntoViewIfNeeded === "function") {
840
+ el.scrollIntoViewIfNeeded(true);
841
+ } else if (typeof el.scrollIntoView === "function") {
842
+ el.scrollIntoView({ block: "center", inline: "center" });
843
+ }
844
+ return { ok: true };
845
+ }
846
+
847
+ // Visual regression masking — lays a fixed black overlay over each matched
848
+ // element. Using the same mask on both baseline and actual excludes
849
+ // volatile content (timestamps etc.) from the comparison. position:fixed
850
+ // does not trigger reflow.
851
+ var MASK_CLASS = "__cdpb_mask__";
852
+
853
+ function mask(locators) {
854
+ if (!Array.isArray(locators)) {
855
+ locators = [locators];
856
+ }
857
+ var count = 0;
858
+ for (var i = 0; i < locators.length; i++) {
859
+ var matches = resolveAll(locators[i]);
860
+ for (var j = 0; j < matches.length; j++) {
861
+ var el = matches[j];
862
+ var r = el.getBoundingClientRect();
863
+ if (r.width <= 0 || r.height <= 0) {
864
+ continue; // hidden elements have no surface to mask.
865
+ }
866
+ var box = document.createElement("div");
867
+ box.className = MASK_CLASS;
868
+ box.setAttribute("data-testid", "__cdpb_mask__");
869
+ box.style.position = "fixed";
870
+ box.style.left = r.left + "px";
871
+ box.style.top = r.top + "px";
872
+ box.style.width = r.width + "px";
873
+ box.style.height = r.height + "px";
874
+ box.style.background = "#000";
875
+ box.style.zIndex = "2147483647";
876
+ document.documentElement.appendChild(box);
877
+ count++;
878
+ }
879
+ }
880
+ return { ok: true, masked: count };
881
+ }
882
+
883
+ function unmask() {
884
+ var all = document.querySelectorAll("." + MASK_CLASS);
885
+ for (var i = 0; i < all.length; i++) {
886
+ all[i].remove();
887
+ }
888
+ return { ok: true, removed: all.length };
889
+ }
890
+
891
+ window.__cdpb = {
892
+ findOne: resolve,
893
+ _state: state,
894
+ _describe: describe,
895
+ exists: status,
896
+ visible: status,
897
+ enabled: status,
898
+ click: click,
899
+ setValue: setValue,
900
+ getText: getText,
901
+ getAttr: getAttr,
902
+ focus: focus,
903
+ rect: rect,
904
+ scrollIntoView: scrollIntoView,
905
+ mask: mask,
906
+ unmask: unmask,
907
+ count: count,
908
+ };
909
+ })();