@geometra/proxy 1.36.0 → 1.38.0
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 +167 -24
- package/dist/dom-actions.js.map +1 -1
- package/dist/extractor.d.ts.map +1 -1
- package/dist/extractor.js +57 -2
- package/dist/extractor.js.map +1 -1
- package/dist/geometry-ws.d.ts.map +1 -1
- package/dist/geometry-ws.js +69 -11
- package/dist/geometry-ws.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;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAMnE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;AAqCvE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAA;IACpC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAA;IACrC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAA;CACvC;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAMvD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAIjE;
|
|
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
|
@@ -1553,6 +1553,55 @@ async function elementHandleDisplayedValues(handle) {
|
|
|
1553
1553
|
return [];
|
|
1554
1554
|
}
|
|
1555
1555
|
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Read the authoritative `aria-invalid` state of a combobox trigger (or its
|
|
1558
|
+
* nearest owning combobox ancestor).
|
|
1559
|
+
*
|
|
1560
|
+
* This is the ONLY reliable signal for whether a custom listbox library
|
|
1561
|
+
* (react-select, Radix, Headless UI, Downshift, Workday PTX, Ashby, Lever
|
|
1562
|
+
* forms, etc.) considers a required field committed. Displayed-value reads
|
|
1563
|
+
* can return stale placeholder text or empty strings during closing
|
|
1564
|
+
* animations, and the library's internal React state may revert a silent
|
|
1565
|
+
* commit a few frames after an option click — but whenever that happens,
|
|
1566
|
+
* the library sets `aria-invalid="true"` back on the trigger element.
|
|
1567
|
+
*
|
|
1568
|
+
* Returns:
|
|
1569
|
+
* - `true` if the trigger (or an owning combobox ancestor) is explicitly
|
|
1570
|
+
* marked `aria-invalid="true"`.
|
|
1571
|
+
* - `false` if it is explicitly `"false"` or unset. Callers should treat
|
|
1572
|
+
* `false` as "no veto" — not as "definitely committed" — because some
|
|
1573
|
+
* libraries simply omit the attribute when valid.
|
|
1574
|
+
* - `false` on read errors (detached handles, cross-origin frames, etc.)
|
|
1575
|
+
* so verification proceeds on the other positive signals.
|
|
1576
|
+
*/
|
|
1577
|
+
async function readAriaInvalid(handle) {
|
|
1578
|
+
if (!handle)
|
|
1579
|
+
return false;
|
|
1580
|
+
try {
|
|
1581
|
+
return await handle.evaluate((el) => {
|
|
1582
|
+
if (!(el instanceof Element))
|
|
1583
|
+
return false;
|
|
1584
|
+
// Walk the element itself plus a few ancestors. react-select exposes the
|
|
1585
|
+
// hidden input as the focusable trigger; its aria-invalid is mirrored on
|
|
1586
|
+
// the combobox wrapper. Radix Select puts aria-invalid on the trigger
|
|
1587
|
+
// button. Covering both patterns with a short climb keeps this generic.
|
|
1588
|
+
let cursor = el;
|
|
1589
|
+
let depth = 0;
|
|
1590
|
+
while (cursor && depth < 4) {
|
|
1591
|
+
const attr = cursor.getAttribute('aria-invalid');
|
|
1592
|
+
if (attr !== null) {
|
|
1593
|
+
return attr === 'true' || attr === '';
|
|
1594
|
+
}
|
|
1595
|
+
cursor = cursor.parentElement;
|
|
1596
|
+
depth++;
|
|
1597
|
+
}
|
|
1598
|
+
return false;
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
catch {
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1556
1605
|
/**
|
|
1557
1606
|
* Read the picked option from a custom combobox's sibling presentational
|
|
1558
1607
|
* element ONLY. Skips the input's own .value (which is the typed search
|
|
@@ -1764,6 +1813,14 @@ async function confirmListboxSelection(page, fieldLabel, label, exact, anchor, c
|
|
|
1764
1813
|
const popupState = await collectVisibleOptionHints(page, anchor);
|
|
1765
1814
|
return !popupState.hasPopup;
|
|
1766
1815
|
};
|
|
1816
|
+
// ARIA veto: if the combobox still advertises aria-invalid="true" after the
|
|
1817
|
+
// option click, the library itself is telling us the commit did not land.
|
|
1818
|
+
// Displayed-value reads can still return stale placeholder text or an in-
|
|
1819
|
+
// flight sibling value during react-select's closing animation, so trusting
|
|
1820
|
+
// them without consulting aria-invalid produces the silent-success bug this
|
|
1821
|
+
// whole function exists to prevent. See readAriaInvalid() for why this is
|
|
1822
|
+
// the right generic signal.
|
|
1823
|
+
const ariaVeto = async () => currentHandle ? readAriaInvalid(currentHandle) : false;
|
|
1767
1824
|
if (currentHandle) {
|
|
1768
1825
|
// Sibling-only check first: react-select / Radix / similar libraries
|
|
1769
1826
|
// only populate `.select__single-value` (and friends) AFTER they commit
|
|
@@ -1772,11 +1829,13 @@ async function confirmListboxSelection(page, fieldLabel, label, exact, anchor, c
|
|
|
1772
1829
|
// fast-path for the v1.34.0 silent-fill bug.
|
|
1773
1830
|
const trustedSibling = await trustedSiblingComboboxValue(currentHandle);
|
|
1774
1831
|
if (trustedSibling && displayedValueMatchesSelection(trustedSibling, label, exact, selectedOptionText)) {
|
|
1775
|
-
|
|
1832
|
+
if (!(await ariaVeto()))
|
|
1833
|
+
return true;
|
|
1776
1834
|
}
|
|
1777
1835
|
const immediateValues = await elementHandleDisplayedValues(currentHandle);
|
|
1778
1836
|
if (immediateValues.some(value => displayedValueMatchesSelection(value, label, exact, selectedOptionText)) &&
|
|
1779
|
-
await canTrustEditableDisplayMatch()
|
|
1837
|
+
await canTrustEditableDisplayMatch() &&
|
|
1838
|
+
!(await ariaVeto())) {
|
|
1780
1839
|
return true;
|
|
1781
1840
|
}
|
|
1782
1841
|
}
|
|
@@ -1788,7 +1847,8 @@ async function confirmListboxSelection(page, fieldLabel, label, exact, anchor, c
|
|
|
1788
1847
|
// schedule state updates via React transitions.
|
|
1789
1848
|
const trustedSibling = await trustedSiblingComboboxValue(currentHandle);
|
|
1790
1849
|
if (trustedSibling && displayedValueMatchesSelection(trustedSibling, label, exact, selectedOptionText)) {
|
|
1791
|
-
|
|
1850
|
+
if (!(await ariaVeto()))
|
|
1851
|
+
return true;
|
|
1792
1852
|
}
|
|
1793
1853
|
}
|
|
1794
1854
|
for (const frame of page.frames()) {
|
|
@@ -1797,11 +1857,12 @@ async function confirmListboxSelection(page, fieldLabel, label, exact, anchor, c
|
|
|
1797
1857
|
continue;
|
|
1798
1858
|
const values = await locatorDisplayedValues(locator);
|
|
1799
1859
|
if (values.some(value => displayedValueMatchesSelection(value, label, exact, selectedOptionText)) &&
|
|
1800
|
-
await canTrustEditableDisplayMatch()
|
|
1860
|
+
await canTrustEditableDisplayMatch() &&
|
|
1861
|
+
!(await ariaVeto())) {
|
|
1801
1862
|
return true;
|
|
1802
1863
|
}
|
|
1803
1864
|
}
|
|
1804
|
-
if (await visibleOptionIsSelected(page, label, exact, anchor))
|
|
1865
|
+
if (await visibleOptionIsSelected(page, label, exact, anchor) && !(await ariaVeto()))
|
|
1805
1866
|
return true;
|
|
1806
1867
|
// Check for multi-select chips/tags (Greenhouse-style React-Select multi)
|
|
1807
1868
|
if (currentHandle) {
|
|
@@ -1819,7 +1880,7 @@ async function confirmListboxSelection(page, fieldLabel, label, exact, anchor, c
|
|
|
1819
1880
|
}
|
|
1820
1881
|
return false;
|
|
1821
1882
|
}, label);
|
|
1822
|
-
if (hasChip)
|
|
1883
|
+
if (hasChip && !(await ariaVeto()))
|
|
1823
1884
|
return true;
|
|
1824
1885
|
}
|
|
1825
1886
|
catch { /* handle detached */ }
|
|
@@ -1898,11 +1959,30 @@ async function dismissAndReVerifySelection(page, label, exact, currentHandle, se
|
|
|
1898
1959
|
}
|
|
1899
1960
|
await delay(100);
|
|
1900
1961
|
}
|
|
1901
|
-
// Deadline elapsed without a positive signal.
|
|
1902
|
-
//
|
|
1903
|
-
//
|
|
1904
|
-
//
|
|
1905
|
-
|
|
1962
|
+
// Deadline elapsed without a positive signal. Two scenarios:
|
|
1963
|
+
//
|
|
1964
|
+
// (a) sawAnyValue === true:
|
|
1965
|
+
// The field did report *some* displayed value, but none of them
|
|
1966
|
+
// matched the target label. That's a silent revert — React Select
|
|
1967
|
+
// restored the placeholder after the blur event. Return false so the
|
|
1968
|
+
// caller retries via keyboard or surfaces the failure.
|
|
1969
|
+
//
|
|
1970
|
+
// (b) sawAnyValue === false:
|
|
1971
|
+
// The field never reported a displayed value during the poll. Used
|
|
1972
|
+
// to return true here (legacy "optimistic success so well-formed
|
|
1973
|
+
// selects don't suddenly fail"), but that's exactly how the silent
|
|
1974
|
+
// commit bug reached production: an unfilled required combobox with
|
|
1975
|
+
// only a sr-only placeholder would pass through this branch despite
|
|
1976
|
+
// still being empty. The authoritative check is aria-invalid: if the
|
|
1977
|
+
// library still marks the trigger invalid, it is NOT committed, full
|
|
1978
|
+
// stop. If aria-invalid is absent or false, keep the legacy optimism
|
|
1979
|
+
// so well-formed controls that never expose a displayed value (pure
|
|
1980
|
+
// ARIA patterns like Radix with SelectValue empty text) still pass.
|
|
1981
|
+
if (sawAnyValue)
|
|
1982
|
+
return false;
|
|
1983
|
+
if (await readAriaInvalid(currentHandle))
|
|
1984
|
+
return false;
|
|
1985
|
+
return true;
|
|
1906
1986
|
}
|
|
1907
1987
|
/**
|
|
1908
1988
|
* Resolve and validate paths on the machine running the proxy (not the agent host).
|
|
@@ -1926,14 +2006,30 @@ export function resolveExistingFiles(rawPaths) {
|
|
|
1926
2006
|
return paths;
|
|
1927
2007
|
}
|
|
1928
2008
|
async function findLabeledFileInput(frame, fieldLabel, exact) {
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2009
|
+
// Try exact-match candidates first, even when the caller passed
|
|
2010
|
+
// exact=false. Same Greenhouse-style failure mode as findLabeledControl:
|
|
2011
|
+
// a substring match (e.g. file labeled "Resume" hijacked by another
|
|
2012
|
+
// "Please attach your resume below" field) silently picks the wrong
|
|
2013
|
+
// input. Trying exact first guarantees the literal label wins when
|
|
2014
|
+
// present, and only falls through to substring matching otherwise.
|
|
2015
|
+
const exactDirect = frame.getByLabel(fieldLabel, { exact: true });
|
|
2016
|
+
const exactDirectCount = await exactDirect.count();
|
|
2017
|
+
for (let i = 0; i < exactDirectCount; i++) {
|
|
2018
|
+
const candidate = exactDirect.nth(i);
|
|
1933
2019
|
const isFileInput = await candidate.evaluate(el => el instanceof HTMLInputElement && el.type === 'file').catch(() => false);
|
|
1934
2020
|
if (isFileInput)
|
|
1935
2021
|
return candidate;
|
|
1936
2022
|
}
|
|
2023
|
+
if (!exact) {
|
|
2024
|
+
const direct = frame.getByLabel(fieldLabel, { exact: false });
|
|
2025
|
+
const directCount = await direct.count();
|
|
2026
|
+
for (let i = 0; i < directCount; i++) {
|
|
2027
|
+
const candidate = direct.nth(i);
|
|
2028
|
+
const isFileInput = await candidate.evaluate(el => el instanceof HTMLInputElement && el.type === 'file').catch(() => false);
|
|
2029
|
+
if (isFileInput)
|
|
2030
|
+
return candidate;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
1937
2033
|
const loc = frame.locator('input[type="file"]');
|
|
1938
2034
|
const count = await loc.count();
|
|
1939
2035
|
if (count === 0)
|
|
@@ -2201,13 +2297,20 @@ async function findLabeledEditableField(page, fieldLabel, exact, cache, fieldId)
|
|
|
2201
2297
|
if (cached !== undefined)
|
|
2202
2298
|
return cached;
|
|
2203
2299
|
for (const frame of page.frames()) {
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2300
|
+
// Always try exact-match candidates first, even when the caller passed
|
|
2301
|
+
// exact=false. Same Greenhouse-style failure as findLabeledControl: a
|
|
2302
|
+
// text field labeled "Authorization code" gets silently hijacked by
|
|
2303
|
+
// a sibling react-select labeled "Are you legally authorized to work
|
|
2304
|
+
// in the country in which you are applying?" because the substring
|
|
2305
|
+
// pass matches "auth" in both. Trying exact first guarantees the
|
|
2306
|
+
// literal label wins when present.
|
|
2307
|
+
const exactCandidates = [
|
|
2308
|
+
frame.getByLabel(fieldLabel, { exact: true }),
|
|
2309
|
+
frame.getByPlaceholder(fieldLabel, { exact: true }),
|
|
2310
|
+
frame.getByRole('textbox', { name: fieldLabel, exact: true }),
|
|
2311
|
+
frame.getByRole('combobox', { name: fieldLabel, exact: true }),
|
|
2209
2312
|
];
|
|
2210
|
-
for (const candidate of
|
|
2313
|
+
for (const candidate of exactCandidates) {
|
|
2211
2314
|
const visible = await firstVisible(candidate, { minWidth: 1, minHeight: 1 });
|
|
2212
2315
|
if (!visible)
|
|
2213
2316
|
continue;
|
|
@@ -2216,6 +2319,23 @@ async function findLabeledEditableField(page, fieldLabel, exact, cache, fieldId)
|
|
|
2216
2319
|
return visible;
|
|
2217
2320
|
}
|
|
2218
2321
|
}
|
|
2322
|
+
if (!exact) {
|
|
2323
|
+
const candidates = [
|
|
2324
|
+
frame.getByLabel(fieldLabel, { exact: false }),
|
|
2325
|
+
frame.getByPlaceholder(fieldLabel, { exact: false }),
|
|
2326
|
+
frame.getByRole('textbox', { name: fieldLabel, exact: false }),
|
|
2327
|
+
frame.getByRole('combobox', { name: fieldLabel, exact: false }),
|
|
2328
|
+
];
|
|
2329
|
+
for (const candidate of candidates) {
|
|
2330
|
+
const visible = await firstVisible(candidate, { minWidth: 1, minHeight: 1 });
|
|
2331
|
+
if (!visible)
|
|
2332
|
+
continue;
|
|
2333
|
+
if (await locatorIsEditable(visible)) {
|
|
2334
|
+
writeCachedLocator(cache, 'editable', fieldLabel, exact, fieldId, visible);
|
|
2335
|
+
return visible;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2219
2339
|
const fallback = await findLabeledControl(frame, fieldLabel, exact);
|
|
2220
2340
|
if (fallback && await locatorIsEditable(fallback)) {
|
|
2221
2341
|
writeCachedLocator(cache, 'editable', fieldLabel, exact, fieldId, fallback);
|
|
@@ -3166,9 +3286,32 @@ export async function pickListboxOption(page, label, opts) {
|
|
|
3166
3286
|
}
|
|
3167
3287
|
return false;
|
|
3168
3288
|
};
|
|
3289
|
+
/**
|
|
3290
|
+
* Final post-commit sanity check. Even after `confirmListboxSelection` and
|
|
3291
|
+
* `dismissAndReVerifySelection` say the option was committed, some libraries
|
|
3292
|
+
* (Greenhouse's forked react-select, Workday PTX, old Ashby forms) can
|
|
3293
|
+
* revert the selection a few frames later when the library re-runs its
|
|
3294
|
+
* validator on the next render. The aria-invalid attribute is the
|
|
3295
|
+
* authoritative signal — if the trigger advertises invalid after all the
|
|
3296
|
+
* verification steps, the commit did not persist, full stop.
|
|
3297
|
+
*
|
|
3298
|
+
* This is defense-in-depth: confirmListboxSelection already vetos on
|
|
3299
|
+
* aria-invalid, but that check runs while the popup is still closing.
|
|
3300
|
+
* This check runs after the popup is fully gone, giving the library a
|
|
3301
|
+
* chance to expose its final committed state.
|
|
3302
|
+
*/
|
|
3303
|
+
const postCommitAriaValid = async () => {
|
|
3304
|
+
if (!openedHandle)
|
|
3305
|
+
return true;
|
|
3306
|
+
// Give React Select a couple of animation frames to settle its final
|
|
3307
|
+
// aria-invalid state after the blur/commit. The cost is a few ms on the
|
|
3308
|
+
// happy path and a correct failure signal on the unhappy one.
|
|
3309
|
+
await delay(40);
|
|
3310
|
+
return !(await readAriaInvalid(openedHandle));
|
|
3311
|
+
};
|
|
3169
3312
|
try {
|
|
3170
3313
|
if (await attemptClickSelection()) {
|
|
3171
|
-
if (await dismissAfterSelection())
|
|
3314
|
+
if (await dismissAfterSelection() && await postCommitAriaValid())
|
|
3172
3315
|
return;
|
|
3173
3316
|
}
|
|
3174
3317
|
let visibleHints = await collectVisibleOptionHints(page, anchor);
|
|
@@ -3181,7 +3324,7 @@ export async function pickListboxOption(page, label, opts) {
|
|
|
3181
3324
|
// next attempt still searches inside the right container.
|
|
3182
3325
|
await refreshPopupScope();
|
|
3183
3326
|
if (await attemptClickSelection()) {
|
|
3184
|
-
if (await dismissAfterSelection())
|
|
3327
|
+
if (await dismissAfterSelection() && await postCommitAriaValid())
|
|
3185
3328
|
return;
|
|
3186
3329
|
}
|
|
3187
3330
|
visibleHints = await collectVisibleOptionHints(page, anchor);
|
|
@@ -3195,7 +3338,7 @@ export async function pickListboxOption(page, label, opts) {
|
|
|
3195
3338
|
await confirmListboxSelection(page, opts.fieldLabel, label, exact, anchor, openedHandle, selectedOptionText, {
|
|
3196
3339
|
editable: openedEditable,
|
|
3197
3340
|
})) {
|
|
3198
|
-
if (await dismissAfterSelection())
|
|
3341
|
+
if (await dismissAfterSelection() && await postCommitAriaValid())
|
|
3199
3342
|
return;
|
|
3200
3343
|
}
|
|
3201
3344
|
}
|