@letsrunit/playwright 0.6.0 → 0.7.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/index.d.ts +1 -1
- package/dist/index.js +221 -127
- package/dist/index.js.map +1 -1
- package/package.json +3 -6
- package/src/browser.ts +2 -1
- package/src/field/aria-select.ts +3 -3
- package/src/field/calendar.ts +9 -8
- package/src/field/date-group.ts +12 -12
- package/src/field/date-text-input.ts +14 -10
- package/src/field/index.ts +3 -1
- package/src/field/native-date.ts +1 -1
- package/src/field/radio-group.ts +2 -2
- package/src/field/toggle.ts +2 -2
- package/src/fuzzy-locator.ts +24 -29
- package/src/scrub-html.ts +2 -5
- package/src/suppress-interferences.ts +11 -3
- package/src/wait.ts +11 -2
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ declare function setFieldValue(el: Locator, value: Value, options?: SetOptions):
|
|
|
15
15
|
declare function formatHtml(page: string | Page): Promise<string>;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Locates an element using Playwright selectors, with fallbacks.
|
|
18
|
+
* Locates an element using Playwright selectors, with lazy fallbacks.
|
|
19
19
|
*/
|
|
20
20
|
declare function fuzzyLocator(page: Page, selector: string): Promise<Locator>;
|
|
21
21
|
|
package/dist/index.js
CHANGED
|
@@ -14,9 +14,12 @@ async function browse(browser, options = {}) {
|
|
|
14
14
|
locale: "en-US",
|
|
15
15
|
...options
|
|
16
16
|
});
|
|
17
|
-
await context.addInitScript(
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
await context.addInitScript(
|
|
18
|
+
/* v8 ignore next */
|
|
19
|
+
() => {
|
|
20
|
+
window.__name = window.__name || ((fn) => fn);
|
|
21
|
+
}
|
|
22
|
+
);
|
|
20
23
|
return await context.newPage();
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -101,6 +104,7 @@ async function waitForMeta(page, timeout = 2500) {
|
|
|
101
104
|
await waitForIdle(page);
|
|
102
105
|
page.getByRole("navigation");
|
|
103
106
|
await page.waitForFunction(
|
|
107
|
+
/* v8 ignore start */
|
|
104
108
|
() => {
|
|
105
109
|
const head = document.head;
|
|
106
110
|
if (!head) return false;
|
|
@@ -108,12 +112,14 @@ async function waitForMeta(page, timeout = 2500) {
|
|
|
108
112
|
document.title.trim() || head.querySelector('meta[property^="og:"]') || head.querySelector('meta[name^="twitter:"]') || head.querySelector('script[type="application/ld+json"]')
|
|
109
113
|
);
|
|
110
114
|
},
|
|
115
|
+
/* v8 ignore stop */
|
|
111
116
|
{ timeout }
|
|
112
117
|
).catch(() => {
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
120
|
async function waitForDomIdle(page, { quiet = 500, timeout = 1e4 } = {}) {
|
|
116
121
|
await page.waitForFunction(
|
|
122
|
+
/* v8 ignore start */
|
|
117
123
|
(q) => new Promise((resolve) => {
|
|
118
124
|
let last = performance.now();
|
|
119
125
|
const obs = new MutationObserver(() => last = performance.now());
|
|
@@ -133,23 +139,31 @@ async function waitForDomIdle(page, { quiet = 500, timeout = 1e4 } = {}) {
|
|
|
133
139
|
};
|
|
134
140
|
tick();
|
|
135
141
|
}),
|
|
142
|
+
/* v8 ignore stop */
|
|
136
143
|
quiet,
|
|
137
144
|
{ timeout }
|
|
138
145
|
);
|
|
139
146
|
}
|
|
140
147
|
async function waitForAnimationsToFinish(root) {
|
|
141
148
|
await root.page().waitForFunction(
|
|
149
|
+
/* v8 ignore start */
|
|
142
150
|
(el) => {
|
|
143
151
|
const animations = el.getAnimations?.({ subtree: true }) ?? [];
|
|
144
152
|
return animations.every((a) => a.playState !== "running");
|
|
145
153
|
},
|
|
154
|
+
/* v8 ignore stop */
|
|
146
155
|
await root.elementHandle()
|
|
147
156
|
);
|
|
148
157
|
await root.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r()))));
|
|
149
158
|
}
|
|
150
159
|
async function waitForUrlChange(page, prevUrl, timeout) {
|
|
151
160
|
try {
|
|
152
|
-
await page.waitForFunction(
|
|
161
|
+
await page.waitForFunction(
|
|
162
|
+
/* v8 ignore next */
|
|
163
|
+
(u) => location.href !== u,
|
|
164
|
+
prevUrl,
|
|
165
|
+
{ timeout }
|
|
166
|
+
);
|
|
153
167
|
return true;
|
|
154
168
|
} catch {
|
|
155
169
|
return false;
|
|
@@ -161,12 +175,14 @@ async function waitUntilEnabled(page, target, timeout) {
|
|
|
161
175
|
const handle = await target.elementHandle().catch(() => null);
|
|
162
176
|
if (!handle) return;
|
|
163
177
|
await page.waitForFunction(
|
|
178
|
+
/* v8 ignore start */
|
|
164
179
|
(el) => {
|
|
165
180
|
if (!el || !el.isConnected) return true;
|
|
166
181
|
const aria = el.getAttribute("aria-disabled");
|
|
167
182
|
const disabled = el.disabled || aria === "true" || el.getAttribute("disabled") !== null;
|
|
168
183
|
return !disabled;
|
|
169
184
|
},
|
|
185
|
+
/* v8 ignore stop */
|
|
170
186
|
handle,
|
|
171
187
|
{ timeout }
|
|
172
188
|
).catch(() => {
|
|
@@ -213,7 +229,12 @@ async function elementKind(target) {
|
|
|
213
229
|
const role = await target.getAttribute("role", { timeout: PROBE }).catch(() => null);
|
|
214
230
|
if (role === "link") return "link";
|
|
215
231
|
if (role === "button") return "button";
|
|
216
|
-
const tag = await target.evaluate(
|
|
232
|
+
const tag = await target.evaluate(
|
|
233
|
+
/* v8 ignore next */
|
|
234
|
+
(el) => el.tagName.toLowerCase(),
|
|
235
|
+
null,
|
|
236
|
+
{ timeout: PROBE }
|
|
237
|
+
).catch(() => "");
|
|
217
238
|
if (tag === "a") return "link";
|
|
218
239
|
if (tag === "button") return "button";
|
|
219
240
|
if (tag === "input") {
|
|
@@ -255,9 +276,7 @@ async function getCalendar(root, options) {
|
|
|
255
276
|
if (!container) return null;
|
|
256
277
|
const found = await container.locator(gridSelector).all();
|
|
257
278
|
const tables = [];
|
|
258
|
-
if (await isCalendarGrid(container))
|
|
259
|
-
tables.push(container);
|
|
260
|
-
}
|
|
279
|
+
if (await isCalendarGrid(container)) tables.push(container);
|
|
261
280
|
for (const grid of found) {
|
|
262
281
|
if (await isCalendarGrid(grid)) {
|
|
263
282
|
tables.push(grid);
|
|
@@ -467,33 +486,37 @@ async function getCandidateLocs(el, options) {
|
|
|
467
486
|
if (candidates.length < 2 || candidates.length > 3) return [];
|
|
468
487
|
return Promise.all(
|
|
469
488
|
candidates.map(async (c) => {
|
|
470
|
-
const info = await c.evaluate(
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
attrs
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
options2 =
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
489
|
+
const info = await c.evaluate(
|
|
490
|
+
/* v8 ignore start */
|
|
491
|
+
(node) => {
|
|
492
|
+
const e = node;
|
|
493
|
+
const attrs = {};
|
|
494
|
+
for (const attr of e.attributes) {
|
|
495
|
+
attrs[attr.name] = attr.value;
|
|
496
|
+
}
|
|
497
|
+
let options2 = [];
|
|
498
|
+
if (e.tagName.toLowerCase() === "select") {
|
|
499
|
+
options2 = Array.from(e.options).map((o) => ({
|
|
500
|
+
value: o.value,
|
|
501
|
+
text: o.text
|
|
502
|
+
}));
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
tag: e.tagName.toLowerCase(),
|
|
506
|
+
type: e.getAttribute("type"),
|
|
507
|
+
name: e.getAttribute("name"),
|
|
508
|
+
id: e.getAttribute("id"),
|
|
509
|
+
ariaLabel: e.getAttribute("aria-label"),
|
|
510
|
+
placeholder: e.getAttribute("placeholder"),
|
|
511
|
+
min: e.getAttribute("min"),
|
|
512
|
+
max: e.getAttribute("max"),
|
|
513
|
+
inputMode: e.getAttribute("inputmode"),
|
|
514
|
+
attrs,
|
|
515
|
+
options: options2
|
|
516
|
+
};
|
|
517
|
+
},
|
|
518
|
+
options
|
|
519
|
+
);
|
|
497
520
|
return { el: c, ...info };
|
|
498
521
|
})
|
|
499
522
|
);
|
|
@@ -529,50 +552,70 @@ async function behavioralProbe(candidateLocs, scores, options) {
|
|
|
529
552
|
for (let i = 0; i < candidateLocs.length; i++) {
|
|
530
553
|
const loc = candidateLocs[i];
|
|
531
554
|
if (loc.tag === "input") {
|
|
532
|
-
const can_be_day = await loc.el.evaluate(
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
555
|
+
const can_be_day = await loc.el.evaluate(
|
|
556
|
+
/* v8 ignore start */
|
|
557
|
+
(node) => {
|
|
558
|
+
const e = node;
|
|
559
|
+
const old = e.value;
|
|
560
|
+
e.value = "31";
|
|
561
|
+
const valid = e.checkValidity();
|
|
562
|
+
e.value = old;
|
|
563
|
+
return valid;
|
|
564
|
+
},
|
|
565
|
+
options
|
|
566
|
+
);
|
|
540
567
|
if (can_be_day) scores[i].day += 1;
|
|
541
|
-
const cannot_be_day = await loc.el.evaluate(
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
568
|
+
const cannot_be_day = await loc.el.evaluate(
|
|
569
|
+
/* v8 ignore start */
|
|
570
|
+
(node) => {
|
|
571
|
+
const e = node;
|
|
572
|
+
const old = e.value;
|
|
573
|
+
e.value = "32";
|
|
574
|
+
const valid = !e.checkValidity();
|
|
575
|
+
e.value = old;
|
|
576
|
+
return valid;
|
|
577
|
+
},
|
|
578
|
+
options
|
|
579
|
+
);
|
|
549
580
|
if (cannot_be_day) scores[i].day += 1;
|
|
550
|
-
const can_be_month = await loc.el.evaluate(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
581
|
+
const can_be_month = await loc.el.evaluate(
|
|
582
|
+
/* v8 ignore start */
|
|
583
|
+
(node) => {
|
|
584
|
+
const e = node;
|
|
585
|
+
const old = e.value;
|
|
586
|
+
e.value = "12";
|
|
587
|
+
const valid = e.checkValidity();
|
|
588
|
+
e.value = old;
|
|
589
|
+
return valid;
|
|
590
|
+
},
|
|
591
|
+
options
|
|
592
|
+
);
|
|
558
593
|
if (can_be_month) scores[i].month += 1;
|
|
559
|
-
const cannot_be_month = await loc.el.evaluate(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
594
|
+
const cannot_be_month = await loc.el.evaluate(
|
|
595
|
+
/* v8 ignore start */
|
|
596
|
+
(node) => {
|
|
597
|
+
const e = node;
|
|
598
|
+
const old = e.value;
|
|
599
|
+
e.value = "13";
|
|
600
|
+
const valid = !e.checkValidity();
|
|
601
|
+
e.value = old;
|
|
602
|
+
return valid;
|
|
603
|
+
},
|
|
604
|
+
options
|
|
605
|
+
);
|
|
567
606
|
if (cannot_be_month) scores[i].month += 1;
|
|
568
|
-
const can_be_year = await loc.el.evaluate(
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
607
|
+
const can_be_year = await loc.el.evaluate(
|
|
608
|
+
/* v8 ignore start */
|
|
609
|
+
(node) => {
|
|
610
|
+
const e = node;
|
|
611
|
+
const old = e.value;
|
|
612
|
+
e.value = "2024";
|
|
613
|
+
const valid = e.checkValidity();
|
|
614
|
+
e.value = old;
|
|
615
|
+
return valid;
|
|
616
|
+
},
|
|
617
|
+
options
|
|
618
|
+
);
|
|
576
619
|
if (can_be_year) scores[i].year += 1;
|
|
577
620
|
}
|
|
578
621
|
}
|
|
@@ -688,22 +731,26 @@ function parseDateString(value, order, sep) {
|
|
|
688
731
|
return dt;
|
|
689
732
|
}
|
|
690
733
|
async function inferLocaleAndPattern(el, options) {
|
|
691
|
-
return el.evaluate(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (
|
|
734
|
+
return el.evaluate(
|
|
735
|
+
/* v8 ignore start */
|
|
736
|
+
() => {
|
|
737
|
+
const lang = document.documentElement.getAttribute("lang") || navigator.language || "en-US";
|
|
738
|
+
const dtf = new Intl.DateTimeFormat(lang, { year: "numeric", month: "2-digit", day: "2-digit" });
|
|
739
|
+
const parts = dtf.formatToParts(new Date(2033, 10, 22));
|
|
740
|
+
const order = [];
|
|
741
|
+
let sep = "/";
|
|
742
|
+
for (const p of parts) {
|
|
743
|
+
if (p.type === "day" || p.type === "month" || p.type === "year") order.push(p.type);
|
|
744
|
+
if (p.type === "literal") {
|
|
745
|
+
const lit = p.value.trim();
|
|
746
|
+
if (lit) sep = lit;
|
|
747
|
+
}
|
|
702
748
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
749
|
+
const finalOrder = order.length === 3 ? order : ["day", "month", "year"];
|
|
750
|
+
return { locale: lang, order: finalOrder, sep };
|
|
751
|
+
},
|
|
752
|
+
options
|
|
753
|
+
);
|
|
707
754
|
}
|
|
708
755
|
async function fillAndReadBack(el, s, options, nextInput) {
|
|
709
756
|
await el.clear(options);
|
|
@@ -711,9 +758,16 @@ async function fillAndReadBack(el, s, options, nextInput) {
|
|
|
711
758
|
if (nextInput) {
|
|
712
759
|
await nextInput.focus(options);
|
|
713
760
|
} else {
|
|
714
|
-
await el.evaluate(
|
|
761
|
+
await el.evaluate(
|
|
762
|
+
/* v8 ignore next */
|
|
763
|
+
(el2) => el2.blur(),
|
|
764
|
+
options
|
|
765
|
+
);
|
|
715
766
|
}
|
|
716
|
-
await el.evaluate(
|
|
767
|
+
await el.evaluate(
|
|
768
|
+
/* v8 ignore next */
|
|
769
|
+
() => new Promise(requestAnimationFrame)
|
|
770
|
+
);
|
|
717
771
|
return await el.inputValue(options);
|
|
718
772
|
}
|
|
719
773
|
function isAmbiguous(value, value2) {
|
|
@@ -853,10 +907,13 @@ async function setSingleDate({ el, tag, type }, value, options) {
|
|
|
853
907
|
"input[type=date], input[type=datetime-local], input[type=month], input[type=week], input[type=time]"
|
|
854
908
|
);
|
|
855
909
|
if (await inputs.count() === 1) {
|
|
856
|
-
const isVisible = await inputs.evaluate(
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
910
|
+
const isVisible = await inputs.evaluate(
|
|
911
|
+
/* v8 ignore next */
|
|
912
|
+
(e) => {
|
|
913
|
+
const style = window.getComputedStyle(e);
|
|
914
|
+
return style.display !== "none" && style.visibility !== "hidden" && e.getAttribute("type") !== "hidden";
|
|
915
|
+
}
|
|
916
|
+
);
|
|
860
917
|
if (isVisible) {
|
|
861
918
|
target = inputs;
|
|
862
919
|
targetType = await target.getAttribute("type", options) || null;
|
|
@@ -909,11 +966,20 @@ async function setNativeInput({ el, tag }, value, options) {
|
|
|
909
966
|
// src/field/aria-select.ts
|
|
910
967
|
async function selectAria({ el }, value, options) {
|
|
911
968
|
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
912
|
-
const role = await el.getAttribute("role", options).catch(
|
|
969
|
+
const role = await el.getAttribute("role", options).catch(
|
|
970
|
+
/* v8 ignore next */
|
|
971
|
+
() => null
|
|
972
|
+
);
|
|
913
973
|
if (role !== "combobox") return false;
|
|
914
|
-
const ariaControls = await el.getAttribute("aria-controls", options).catch(
|
|
974
|
+
const ariaControls = await el.getAttribute("aria-controls", options).catch(
|
|
975
|
+
/* v8 ignore next */
|
|
976
|
+
() => null
|
|
977
|
+
);
|
|
915
978
|
if (!ariaControls) return false;
|
|
916
|
-
const ariaExpanded = await el.getAttribute("aria-expanded", options).catch(
|
|
979
|
+
const ariaExpanded = await el.getAttribute("aria-expanded", options).catch(
|
|
980
|
+
/* v8 ignore next */
|
|
981
|
+
() => null
|
|
982
|
+
);
|
|
917
983
|
if (ariaExpanded !== "true") await el.click(options);
|
|
918
984
|
const stringValue = String(value);
|
|
919
985
|
const listbox = el.page().locator(`#${ariaControls}`);
|
|
@@ -962,14 +1028,20 @@ async function setRadioGroup({ el }, value, options) {
|
|
|
962
1028
|
const ariaRadio = el.locator(`[role="radio"][value="${stringValue}"]`);
|
|
963
1029
|
if (await ariaRadio.count() >= 1) {
|
|
964
1030
|
const item = ariaRadio.first();
|
|
965
|
-
const ariaChecked = await item.getAttribute("aria-checked", options).catch(
|
|
1031
|
+
const ariaChecked = await item.getAttribute("aria-checked", options).catch(
|
|
1032
|
+
/* v8 ignore next */
|
|
1033
|
+
() => null
|
|
1034
|
+
);
|
|
966
1035
|
if (ariaChecked !== "true") await item.click(options);
|
|
967
1036
|
return true;
|
|
968
1037
|
}
|
|
969
1038
|
const ariaRadioByLabel = el.getByLabel(stringValue, { exact: true }).locator('[role="radio"]');
|
|
970
1039
|
if (await ariaRadioByLabel.count() >= 1) {
|
|
971
1040
|
const item = ariaRadioByLabel.first();
|
|
972
|
-
const ariaChecked = await item.getAttribute("aria-checked", options).catch(
|
|
1041
|
+
const ariaChecked = await item.getAttribute("aria-checked", options).catch(
|
|
1042
|
+
/* v8 ignore next */
|
|
1043
|
+
() => null
|
|
1044
|
+
);
|
|
973
1045
|
if (ariaChecked !== "true") await item.click(options);
|
|
974
1046
|
return true;
|
|
975
1047
|
}
|
|
@@ -1093,9 +1165,15 @@ async function setSliderByKeyboard(slider, initialValue, targetValue, options) {
|
|
|
1093
1165
|
// src/field/toggle.ts
|
|
1094
1166
|
async function setToggle({ el }, value, options) {
|
|
1095
1167
|
if (typeof value !== "boolean" && value !== null) return false;
|
|
1096
|
-
const role = await el.getAttribute("role", options).catch(
|
|
1168
|
+
const role = await el.getAttribute("role", options).catch(
|
|
1169
|
+
/* v8 ignore next */
|
|
1170
|
+
() => null
|
|
1171
|
+
);
|
|
1097
1172
|
if (role !== "checkbox" && role !== "switch") return false;
|
|
1098
|
-
const ariaChecked = await el.getAttribute("aria-checked", options).catch(
|
|
1173
|
+
const ariaChecked = await el.getAttribute("aria-checked", options).catch(
|
|
1174
|
+
/* v8 ignore next */
|
|
1175
|
+
() => null
|
|
1176
|
+
);
|
|
1099
1177
|
const isChecked = ariaChecked === "true";
|
|
1100
1178
|
if (Boolean(value) !== isChecked) await el.click(options);
|
|
1101
1179
|
return true;
|
|
@@ -1134,7 +1212,10 @@ async function setFieldValue(el, value, options) {
|
|
|
1134
1212
|
el = await pickFieldElement(el);
|
|
1135
1213
|
}
|
|
1136
1214
|
const tag = await el.evaluate((e) => e.tagName.toLowerCase(), options);
|
|
1137
|
-
const type = await el.getAttribute("type", options).then((s) => s && s.toLowerCase()).catch(
|
|
1215
|
+
const type = await el.getAttribute("type", options).then((s) => s && s.toLowerCase()).catch(
|
|
1216
|
+
/* v8 ignore next */
|
|
1217
|
+
() => null
|
|
1218
|
+
);
|
|
1138
1219
|
const loc = { el, tag, type };
|
|
1139
1220
|
await setValue(loc, value, options);
|
|
1140
1221
|
}
|
|
@@ -1146,47 +1227,51 @@ async function formatHtml(page) {
|
|
|
1146
1227
|
|
|
1147
1228
|
// src/fuzzy-locator.ts
|
|
1148
1229
|
async function fuzzyLocator(page, selector) {
|
|
1149
|
-
const primary = page.locator(selector)
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1230
|
+
const primary = page.locator(selector);
|
|
1231
|
+
const candidates = [
|
|
1232
|
+
tryRelaxNameToHasText(page, selector),
|
|
1233
|
+
tryTagInsteadOfRole(page, selector),
|
|
1234
|
+
tryRoleNameProximity(page, selector),
|
|
1235
|
+
tryFieldAlternative(page, selector),
|
|
1236
|
+
tryAsField(page, selector)
|
|
1237
|
+
];
|
|
1238
|
+
let combined = primary;
|
|
1239
|
+
for (const candidate of candidates) {
|
|
1240
|
+
if (!candidate) continue;
|
|
1241
|
+
combined = combined.or(candidate);
|
|
1157
1242
|
}
|
|
1158
|
-
return
|
|
1243
|
+
return combined.first();
|
|
1159
1244
|
}
|
|
1160
|
-
|
|
1245
|
+
function tryRelaxNameToHasText(page, selector) {
|
|
1161
1246
|
const matchAnyNameFull = selector.match(/^(role=.*)\[name="([^"]+)"i?](.*)$/i);
|
|
1162
1247
|
if (!matchAnyNameFull) return null;
|
|
1163
1248
|
const [, pre, nameText, post] = matchAnyNameFull;
|
|
1164
1249
|
const containsSelector = `${pre}${post}`;
|
|
1165
|
-
return
|
|
1250
|
+
return page.locator(containsSelector, { hasText: nameText });
|
|
1166
1251
|
}
|
|
1167
|
-
|
|
1252
|
+
function tryTagInsteadOfRole(page, selector) {
|
|
1168
1253
|
const matchAnyNameFull = selector.match(/^role=(link|button|option)\s*\[name="([^"]+)"i?](.*)$/i);
|
|
1169
1254
|
if (!matchAnyNameFull) return null;
|
|
1170
1255
|
const [, role, nameText, post] = matchAnyNameFull;
|
|
1171
1256
|
const tag = role === "link" ? "a" : role;
|
|
1172
1257
|
const containsSelector = `css=${tag}${post}`;
|
|
1173
|
-
return
|
|
1258
|
+
return page.locator(containsSelector, { hasText: nameText });
|
|
1174
1259
|
}
|
|
1175
|
-
|
|
1260
|
+
function tryRoleNameProximity(page, selector) {
|
|
1176
1261
|
const matchRole = selector.match(/^role=(\w+)\s*\[name="([^"]+)"i?](.*)$/i);
|
|
1177
1262
|
if (!matchRole) return null;
|
|
1178
1263
|
const [, role, name, rest] = matchRole;
|
|
1179
1264
|
const proximitySelector = `text=${name} >> .. >> role=${role}${rest}`;
|
|
1180
|
-
return
|
|
1265
|
+
return page.locator(proximitySelector);
|
|
1181
1266
|
}
|
|
1182
|
-
|
|
1267
|
+
function tryFieldAlternative(page, selector) {
|
|
1183
1268
|
const matchField = selector.match(/^field="([^"]+)"i?$/i);
|
|
1184
1269
|
if (!matchField) return null;
|
|
1185
1270
|
const [, field] = matchField;
|
|
1186
1271
|
if (!/^[a-zA-Z0-9_-]+$/.test(field)) return null;
|
|
1187
|
-
return
|
|
1272
|
+
return page.locator(`#${field} > input`);
|
|
1188
1273
|
}
|
|
1189
|
-
|
|
1274
|
+
function tryAsField(page, selector) {
|
|
1190
1275
|
const matchRole = selector.match(/^role=(\w+)\s*\[name="([^"]+)"i?](.*)$/i);
|
|
1191
1276
|
if (!matchRole) return null;
|
|
1192
1277
|
const [, role, name, rest] = matchRole;
|
|
@@ -1206,7 +1291,7 @@ async function tryAsField(page, selector) {
|
|
|
1206
1291
|
"option"
|
|
1207
1292
|
]);
|
|
1208
1293
|
if (!fieldRoles.has(role.toLowerCase())) return null;
|
|
1209
|
-
return
|
|
1294
|
+
return page.locator(`field=${name}${rest}`);
|
|
1210
1295
|
}
|
|
1211
1296
|
|
|
1212
1297
|
// src/selector/date-selector.ts
|
|
@@ -2934,8 +3019,12 @@ async function tryClick(page, selectors, _label) {
|
|
|
2934
3019
|
return false;
|
|
2935
3020
|
}
|
|
2936
3021
|
async function closeNativeJsAlerts(page) {
|
|
2937
|
-
page.on(
|
|
2938
|
-
|
|
3022
|
+
page.on(
|
|
3023
|
+
"dialog",
|
|
3024
|
+
/* v8 ignore next */
|
|
3025
|
+
(d) => d.accept().catch(() => {
|
|
3026
|
+
})
|
|
3027
|
+
);
|
|
2939
3028
|
}
|
|
2940
3029
|
async function sweepKnownCMPs(page, preferReject) {
|
|
2941
3030
|
for (const cmp of knownCMPSelectors) {
|
|
@@ -3014,6 +3103,7 @@ async function sweepOverlays(page, regex) {
|
|
|
3014
3103
|
const acceptRxSource = regex.accept.source;
|
|
3015
3104
|
const acceptRxFlags = regex.accept.flags;
|
|
3016
3105
|
return await page.evaluate(
|
|
3106
|
+
/* v8 ignore start */
|
|
3017
3107
|
([source, flags]) => {
|
|
3018
3108
|
const acceptRx = new RegExp(source, flags);
|
|
3019
3109
|
const isBig = (el) => {
|
|
@@ -3029,7 +3119,7 @@ async function sweepOverlays(page, regex) {
|
|
|
3029
3119
|
}).slice(0, 5);
|
|
3030
3120
|
for (const el of candidates) {
|
|
3031
3121
|
const btn = el.querySelector(
|
|
3032
|
-
|
|
3122
|
+
"[aria-label*=\u201Dclose\u201D i], button[aria-label*=\u201Dclose\u201D i], button:has(svg), .close, [data-close], .btn-close"
|
|
3033
3123
|
);
|
|
3034
3124
|
if (btn) {
|
|
3035
3125
|
btn.click();
|
|
@@ -3054,8 +3144,12 @@ async function sweepOverlays(page, regex) {
|
|
|
3054
3144
|
}
|
|
3055
3145
|
return false;
|
|
3056
3146
|
},
|
|
3147
|
+
/* v8 ignore stop */
|
|
3057
3148
|
[acceptRxSource, acceptRxFlags]
|
|
3058
|
-
).catch(
|
|
3149
|
+
).catch(
|
|
3150
|
+
/* v8 ignore next */
|
|
3151
|
+
() => false
|
|
3152
|
+
);
|
|
3059
3153
|
}
|
|
3060
3154
|
async function suppressInterferences(page, opts = {}) {
|
|
3061
3155
|
const timeoutMs = opts.timeoutMs ?? 4e3;
|