@geometra/proxy 1.58.0 → 1.59.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/dist/dom-actions.d.ts.map +1 -1
- package/dist/dom-actions.js +134 -1
- package/dist/dom-actions.js.map +1 -1
- package/dist/index.js +34 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime.d.ts +21 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +8 -0
- package/dist/runtime.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dom-actions.d.ts","sourceRoot":"","sources":["../src/dom-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAwB,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAErE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEnE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"dom-actions.d.ts","sourceRoot":"","sources":["../src/dom-actions.ts"],"names":[],"mappings":"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"}
|
package/dist/dom-actions.js
CHANGED
|
@@ -4,12 +4,15 @@ import { canonicalizeHtmlInputValue } from './input-canonical.js';
|
|
|
4
4
|
export function delay(ms) {
|
|
5
5
|
return new Promise(r => setTimeout(r, ms));
|
|
6
6
|
}
|
|
7
|
-
const LABELED_CONTROL_SELECTOR = 'input, select, textarea, button, [role="combobox"], [role="textbox"], [aria-haspopup="listbox"], [contenteditable="true"]';
|
|
7
|
+
const LABELED_CONTROL_SELECTOR = 'input, select, textarea, button, [role="combobox"], [role="textbox"], [aria-haspopup="listbox"], [aria-haspopup="menu"], [aria-haspopup="true"], [contenteditable="true"]';
|
|
8
|
+
const MENU_TRIGGER_SELECTOR = 'button[aria-haspopup="menu"], button[aria-haspopup="true"], [role="button"][aria-haspopup="menu"], [role="button"][aria-haspopup="true"]';
|
|
8
9
|
const POPUP_CONTAINER_SELECTOR = '[role="listbox"], [role="menu"], [role="dialog"], [aria-modal="true"], [data-radix-popper-content-wrapper], [class*="menu"], [class*="option"], [class*="select"], [class*="dropdown"]';
|
|
9
10
|
const POPUP_ROOT_SELECTOR = '[role="listbox"], [role="menu"], [role="dialog"], [aria-modal="true"], [data-radix-popper-content-wrapper], [class*="menu"], [class*="dropdown"], [class*="popover"], [class*="listbox"], [class*="options"]';
|
|
10
11
|
const OPTION_PICKER_SELECTOR = [
|
|
11
12
|
'[role="option"]',
|
|
12
13
|
'[role="menuitem"]',
|
|
14
|
+
'[role="menuitemradio"]',
|
|
15
|
+
'[role="menuitemcheckbox"]',
|
|
13
16
|
'[role="treeitem"]',
|
|
14
17
|
'button',
|
|
15
18
|
'li',
|
|
@@ -579,6 +582,121 @@ function stripTruncationEllipsis(label) {
|
|
|
579
582
|
// optionally preceded by whitespace, anchored at the end.
|
|
580
583
|
return label.replace(/\s*(?:\u2026|\.\.\.)\s*$/u, '').trimEnd();
|
|
581
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Locate a menu-trigger button whose container-level label matches `fieldLabel`.
|
|
587
|
+
*
|
|
588
|
+
* Motivating case: GitHub Primer (and similar design systems) renders a
|
|
589
|
+
* dropdown as `<label>Ref type</label><button aria-haspopup="menu">Branch</button>`
|
|
590
|
+
* where the button's accessible name is the *current value* ("Branch"), not
|
|
591
|
+
* the field name. Playwright's `getByRole('button', { name: 'Ref type' })`
|
|
592
|
+
* therefore misses the trigger, and the existing `<label>/<legend>` proximity
|
|
593
|
+
* scan doesn't match either because the visible label is a plain `<span>` /
|
|
594
|
+
* `<div>` rather than a `<label>` element.
|
|
595
|
+
*
|
|
596
|
+
* This helper broadens the label search to any visible element whose text
|
|
597
|
+
* (minus the button's own text) matches the field name, then returns the
|
|
598
|
+
* menu-trigger button inside that container. It complements `findLabeledControl`
|
|
599
|
+
* rather than replacing it — the scan is restricted to menu-trigger buttons
|
|
600
|
+
* so it can't shadow existing combobox / textbox / native-select matches.
|
|
601
|
+
*/
|
|
602
|
+
async function findMenuTriggerByContainerLabel(frame, fieldLabel, exact) {
|
|
603
|
+
const triggers = frame.locator(MENU_TRIGGER_SELECTOR);
|
|
604
|
+
const count = await triggers.count();
|
|
605
|
+
if (count === 0)
|
|
606
|
+
return null;
|
|
607
|
+
const bestIndex = await triggers.evaluateAll((elements, payload) => {
|
|
608
|
+
function normalize(value) {
|
|
609
|
+
return value.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
610
|
+
}
|
|
611
|
+
function stripTrailingColon(value) {
|
|
612
|
+
return value.replace(/\s*:\s*$/u, '').trim();
|
|
613
|
+
}
|
|
614
|
+
function matches(candidate) {
|
|
615
|
+
if (!candidate)
|
|
616
|
+
return false;
|
|
617
|
+
const normalizedCandidate = normalize(stripTrailingColon(candidate));
|
|
618
|
+
const normalizedExpected = normalize(payload.fieldLabel);
|
|
619
|
+
if (!normalizedCandidate || !normalizedExpected)
|
|
620
|
+
return false;
|
|
621
|
+
return payload.exact
|
|
622
|
+
? normalizedCandidate === normalizedExpected
|
|
623
|
+
: normalizedCandidate.includes(normalizedExpected);
|
|
624
|
+
}
|
|
625
|
+
function visible(el) {
|
|
626
|
+
if (!(el instanceof HTMLElement))
|
|
627
|
+
return false;
|
|
628
|
+
const rect = el.getBoundingClientRect();
|
|
629
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
630
|
+
return false;
|
|
631
|
+
const style = getComputedStyle(el);
|
|
632
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
|
|
633
|
+
}
|
|
634
|
+
function containerLabelText(el) {
|
|
635
|
+
// Walk up a few parent levels looking for a container that holds BOTH
|
|
636
|
+
// the trigger and some sibling label text. The label text is the
|
|
637
|
+
// container's visible text minus the trigger's own text.
|
|
638
|
+
let container = el.parentElement;
|
|
639
|
+
let depth = 0;
|
|
640
|
+
const triggerText = el.textContent?.trim() ?? '';
|
|
641
|
+
while (container && depth < 4) {
|
|
642
|
+
const containerText = container.textContent?.trim() ?? '';
|
|
643
|
+
if (containerText && containerText.length < 240) {
|
|
644
|
+
const residual = triggerText && containerText.endsWith(triggerText)
|
|
645
|
+
? containerText.slice(0, containerText.length - triggerText.length).trim()
|
|
646
|
+
: triggerText && containerText.includes(triggerText)
|
|
647
|
+
? containerText.split(triggerText)[0]?.trim() ?? ''
|
|
648
|
+
: containerText;
|
|
649
|
+
if (residual && residual.length > 0 && residual.length < 120) {
|
|
650
|
+
return residual;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
container = container.parentElement;
|
|
654
|
+
depth++;
|
|
655
|
+
}
|
|
656
|
+
return undefined;
|
|
657
|
+
}
|
|
658
|
+
let best = null;
|
|
659
|
+
for (let i = 0; i < elements.length; i++) {
|
|
660
|
+
const el = elements[i];
|
|
661
|
+
if (!(el instanceof HTMLElement))
|
|
662
|
+
continue;
|
|
663
|
+
if (!visible(el))
|
|
664
|
+
continue;
|
|
665
|
+
const aria = el.getAttribute('aria-label')?.trim();
|
|
666
|
+
if (matches(aria)) {
|
|
667
|
+
const score = 0;
|
|
668
|
+
if (!best || score < best.score)
|
|
669
|
+
best = { index: i, score };
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const labelledBy = el.getAttribute('aria-labelledby');
|
|
673
|
+
if (labelledBy) {
|
|
674
|
+
const text = labelledBy
|
|
675
|
+
.split(/\s+/)
|
|
676
|
+
.map(id => document.getElementById(id)?.textContent?.trim() ?? '')
|
|
677
|
+
.filter(Boolean)
|
|
678
|
+
.join(' ')
|
|
679
|
+
.trim();
|
|
680
|
+
if (matches(text)) {
|
|
681
|
+
const score = 10;
|
|
682
|
+
if (!best || score < best.score)
|
|
683
|
+
best = { index: i, score };
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const container = containerLabelText(el);
|
|
688
|
+
if (matches(container)) {
|
|
689
|
+
const rect = el.getBoundingClientRect();
|
|
690
|
+
const sizePenalty = rect.width < 48 || rect.height < 18 ? 120 : 0;
|
|
691
|
+
const score = 100 + sizePenalty;
|
|
692
|
+
if (!best || score < best.score)
|
|
693
|
+
best = { index: i, score };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return best?.index ?? -1;
|
|
697
|
+
}, { fieldLabel, exact });
|
|
698
|
+
return bestIndex >= 0 ? triggers.nth(bestIndex) : null;
|
|
699
|
+
}
|
|
582
700
|
async function findLabeledControlInPage(page, fieldLabel, exact, opts) {
|
|
583
701
|
const cacheable = !opts?.preferredAnchor;
|
|
584
702
|
const cached = cacheable ? readCachedLocator(opts?.cache, 'control', fieldLabel, exact, opts?.fieldId) : undefined;
|
|
@@ -612,6 +730,21 @@ async function findLabeledControlInPage(page, fieldLabel, exact, opts) {
|
|
|
612
730
|
return locator;
|
|
613
731
|
}
|
|
614
732
|
}
|
|
733
|
+
// Fallback: menu-trigger buttons whose accessible name is the CURRENT VALUE
|
|
734
|
+
// (e.g. "Branch" for a "Ref type: Branch" GitHub Primer dropdown) rather
|
|
735
|
+
// than the field name. Restricted to aria-haspopup="menu"/"true" triggers
|
|
736
|
+
// so it cannot shadow existing combobox/textbox/native-select matches.
|
|
737
|
+
const candidateLabels = [fieldLabel, stripped].filter((label, index, array) => label && label.length > 0 && array.indexOf(label) === index);
|
|
738
|
+
for (const candidate of candidateLabels) {
|
|
739
|
+
for (const frame of page.frames()) {
|
|
740
|
+
const locator = await findMenuTriggerByContainerLabel(frame, candidate, exact);
|
|
741
|
+
if (!locator)
|
|
742
|
+
continue;
|
|
743
|
+
if (cacheable)
|
|
744
|
+
writeCachedLocator(opts?.cache, 'control', fieldLabel, exact, opts?.fieldId, locator);
|
|
745
|
+
return locator;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
615
748
|
if (cacheable)
|
|
616
749
|
writeCachedLocator(opts?.cache, 'control', fieldLabel, exact, opts?.fieldId, null);
|
|
617
750
|
return null;
|