browser-pilot 0.0.12 → 0.0.13
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/README.md +13 -7
- package/dist/actions.cjs +63 -10
- package/dist/actions.d.cts +3 -3
- package/dist/actions.d.ts +3 -3
- package/dist/actions.mjs +1 -1
- package/dist/browser.cjs +629 -57
- package/dist/browser.d.cts +9 -3
- package/dist/browser.d.ts +9 -3
- package/dist/browser.mjs +3 -3
- package/dist/cdp.cjs +1 -1
- package/dist/cdp.d.cts +1 -1
- package/dist/cdp.d.ts +1 -1
- package/dist/cdp.mjs +1 -1
- package/dist/{chunk-NLIARNEE.mjs → chunk-A2ZRAEO3.mjs} +63 -10
- package/dist/{chunk-4MBSALQL.mjs → chunk-HP6R3W32.mjs} +1 -1
- package/dist/{chunk-RUWAXHDX.mjs → chunk-VDAMDOS6.mjs} +606 -57
- package/dist/cli.mjs +1145 -127
- package/dist/{client-7Nqka5MV.d.ts → client-DRqxBdHv.d.cts} +1 -1
- package/dist/{client-7Nqka5MV.d.cts → client-DRqxBdHv.d.ts} +1 -1
- package/dist/index.cjs +668 -66
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +3 -3
- package/dist/{types-j23Iqo2L.d.ts → types-BXMGFtnB.d.cts} +46 -5
- package/dist/{types-BOPu0OQZ.d.cts → types-CzgQjai9.d.ts} +46 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -998,6 +998,9 @@ var BatchExecutor = class {
|
|
|
998
998
|
const snapshot = await this.page.snapshot();
|
|
999
999
|
return { value: snapshot };
|
|
1000
1000
|
}
|
|
1001
|
+
case "forms": {
|
|
1002
|
+
return { value: await this.page.forms() };
|
|
1003
|
+
}
|
|
1001
1004
|
case "screenshot": {
|
|
1002
1005
|
const data = await this.page.screenshot({
|
|
1003
1006
|
format: step.format,
|
|
@@ -1017,6 +1020,21 @@ var BatchExecutor = class {
|
|
|
1017
1020
|
const text = await this.page.text(selector);
|
|
1018
1021
|
return { text, selectorUsed: selector };
|
|
1019
1022
|
}
|
|
1023
|
+
case "newTab": {
|
|
1024
|
+
const { targetId } = await this.page.cdpClient.send(
|
|
1025
|
+
"Target.createTarget",
|
|
1026
|
+
{
|
|
1027
|
+
url: step.url ?? "about:blank"
|
|
1028
|
+
},
|
|
1029
|
+
null
|
|
1030
|
+
);
|
|
1031
|
+
return { value: { targetId } };
|
|
1032
|
+
}
|
|
1033
|
+
case "closeTab": {
|
|
1034
|
+
const targetId = step.targetId ?? this.page.targetId;
|
|
1035
|
+
await this.page.cdpClient.send("Target.closeTarget", { targetId }, null);
|
|
1036
|
+
return { value: { targetId, closedCurrent: targetId === this.page.targetId } };
|
|
1037
|
+
}
|
|
1020
1038
|
case "switchFrame": {
|
|
1021
1039
|
if (!step.selector) throw new Error("switchFrame requires selector");
|
|
1022
1040
|
await this.page.switchToFrame(step.selector, { timeout, optional });
|
|
@@ -1131,10 +1149,15 @@ var BatchExecutor = class {
|
|
|
1131
1149
|
snap: "snapshot",
|
|
1132
1150
|
accessibility: "snapshot",
|
|
1133
1151
|
a11y: "snapshot",
|
|
1152
|
+
formslist: "forms",
|
|
1134
1153
|
image: "screenshot",
|
|
1135
1154
|
pic: "screenshot",
|
|
1136
1155
|
frame: "switchFrame",
|
|
1137
1156
|
iframe: "switchFrame",
|
|
1157
|
+
newtab: "newTab",
|
|
1158
|
+
opentab: "newTab",
|
|
1159
|
+
createtab: "newTab",
|
|
1160
|
+
closetab: "closeTab",
|
|
1138
1161
|
assert_visible: "assertVisible",
|
|
1139
1162
|
assert_exists: "assertExists",
|
|
1140
1163
|
assert_text: "assertText",
|
|
@@ -1148,7 +1171,7 @@ var BatchExecutor = class {
|
|
|
1148
1171
|
};
|
|
1149
1172
|
const suggestion = aliases[action.toLowerCase()];
|
|
1150
1173
|
const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
1151
|
-
const valid = "goto, click, fill, type, select, check, uncheck, submit, press, shortcut, focus, hover, scroll, wait, snapshot, screenshot, evaluate, text, switchFrame, switchToMain, assertVisible, assertExists, assertText, assertUrl, assertValue";
|
|
1174
|
+
const valid = "goto, click, fill, type, select, check, uncheck, submit, press, shortcut, focus, hover, scroll, wait, snapshot, forms, screenshot, evaluate, text, newTab, closeTab, switchFrame, switchToMain, assertVisible, assertExists, assertText, assertUrl, assertValue";
|
|
1152
1175
|
throw new Error(`Unknown action "${action}".${hint}
|
|
1153
1176
|
|
|
1154
1177
|
Valid actions: ${valid}`);
|
|
@@ -1219,6 +1242,11 @@ var ACTION_ALIASES = {
|
|
|
1219
1242
|
pic: "screenshot",
|
|
1220
1243
|
frame: "switchFrame",
|
|
1221
1244
|
iframe: "switchFrame",
|
|
1245
|
+
formslist: "forms",
|
|
1246
|
+
newtab: "newTab",
|
|
1247
|
+
opentab: "newTab",
|
|
1248
|
+
createtab: "newTab",
|
|
1249
|
+
closetab: "closeTab",
|
|
1222
1250
|
assert_visible: "assertVisible",
|
|
1223
1251
|
assert_exists: "assertExists",
|
|
1224
1252
|
assert_text: "assertText",
|
|
@@ -1255,7 +1283,8 @@ var PROPERTY_ALIASES = {
|
|
|
1255
1283
|
button: "key",
|
|
1256
1284
|
address: "url",
|
|
1257
1285
|
page: "url",
|
|
1258
|
-
path: "url"
|
|
1286
|
+
path: "url",
|
|
1287
|
+
tabId: "targetId"
|
|
1259
1288
|
};
|
|
1260
1289
|
var ACTION_RULES = {
|
|
1261
1290
|
goto: {
|
|
@@ -1356,6 +1385,10 @@ var ACTION_RULES = {
|
|
|
1356
1385
|
fullPage: { type: "boolean" }
|
|
1357
1386
|
}
|
|
1358
1387
|
},
|
|
1388
|
+
forms: {
|
|
1389
|
+
required: {},
|
|
1390
|
+
optional: {}
|
|
1391
|
+
},
|
|
1359
1392
|
evaluate: {
|
|
1360
1393
|
required: { value: { type: "string" } },
|
|
1361
1394
|
optional: {}
|
|
@@ -1370,6 +1403,18 @@ var ACTION_RULES = {
|
|
|
1370
1403
|
required: { selector: { type: "string|string[]" } },
|
|
1371
1404
|
optional: {}
|
|
1372
1405
|
},
|
|
1406
|
+
newTab: {
|
|
1407
|
+
required: {},
|
|
1408
|
+
optional: {
|
|
1409
|
+
url: { type: "string" }
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
closeTab: {
|
|
1413
|
+
required: {},
|
|
1414
|
+
optional: {
|
|
1415
|
+
targetId: { type: "string" }
|
|
1416
|
+
}
|
|
1417
|
+
},
|
|
1373
1418
|
switchToMain: {
|
|
1374
1419
|
required: {},
|
|
1375
1420
|
optional: {}
|
|
@@ -1412,6 +1457,7 @@ var KNOWN_STEP_FIELDS = /* @__PURE__ */ new Set([
|
|
|
1412
1457
|
"selector",
|
|
1413
1458
|
"url",
|
|
1414
1459
|
"value",
|
|
1460
|
+
"targetId",
|
|
1415
1461
|
"key",
|
|
1416
1462
|
"combo",
|
|
1417
1463
|
"modifiers",
|
|
@@ -1566,15 +1612,22 @@ function validateSteps(steps) {
|
|
|
1566
1612
|
const rule = ACTION_RULES[action];
|
|
1567
1613
|
for (const key of Object.keys(obj)) {
|
|
1568
1614
|
if (key === "action") continue;
|
|
1569
|
-
if (
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1615
|
+
if (KNOWN_STEP_FIELDS.has(key)) continue;
|
|
1616
|
+
const canonical = PROPERTY_ALIASES[key];
|
|
1617
|
+
if (canonical) {
|
|
1618
|
+
if (!(canonical in obj)) {
|
|
1619
|
+
obj[canonical] = obj[key];
|
|
1620
|
+
}
|
|
1621
|
+
delete obj[key];
|
|
1622
|
+
continue;
|
|
1577
1623
|
}
|
|
1624
|
+
const suggestion = suggestProperty(key);
|
|
1625
|
+
errors.push({
|
|
1626
|
+
stepIndex: i,
|
|
1627
|
+
field: key,
|
|
1628
|
+
message: suggestion ? `unknown property "${key}". Did you mean "${suggestion}"?` : `unknown property "${key}".`,
|
|
1629
|
+
suggestion: suggestion ? `Did you mean "${suggestion}"?` : void 0
|
|
1630
|
+
});
|
|
1578
1631
|
}
|
|
1579
1632
|
for (const [field, fieldRule] of Object.entries(rule.required)) {
|
|
1580
1633
|
if (!(field in obj) || obj[field] === void 0) {
|
|
@@ -3311,7 +3364,7 @@ async function createCDPClient(wsUrl, options = {}) {
|
|
|
3311
3364
|
throw new Error("CDP client is not connected");
|
|
3312
3365
|
}
|
|
3313
3366
|
const id = ++messageId;
|
|
3314
|
-
const effectiveSessionId = sessionId ?? currentSessionId;
|
|
3367
|
+
const effectiveSessionId = sessionId === null ? void 0 : sessionId ?? currentSessionId;
|
|
3315
3368
|
const request = { id, method };
|
|
3316
3369
|
if (params !== void 0) {
|
|
3317
3370
|
request.params = params;
|
|
@@ -3793,6 +3846,285 @@ var RequestInterceptor = class {
|
|
|
3793
3846
|
}
|
|
3794
3847
|
};
|
|
3795
3848
|
|
|
3849
|
+
// src/browser/special-selectors.ts
|
|
3850
|
+
function stripQuotes(value) {
|
|
3851
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
3852
|
+
return value.slice(1, -1);
|
|
3853
|
+
}
|
|
3854
|
+
return value;
|
|
3855
|
+
}
|
|
3856
|
+
function parseTextSelector(selector) {
|
|
3857
|
+
if (!selector.startsWith("text:")) return null;
|
|
3858
|
+
let raw = selector.slice(5).trim();
|
|
3859
|
+
let exact = false;
|
|
3860
|
+
if (raw.startsWith("=")) {
|
|
3861
|
+
exact = true;
|
|
3862
|
+
raw = raw.slice(1).trim();
|
|
3863
|
+
}
|
|
3864
|
+
const query = stripQuotes(raw);
|
|
3865
|
+
if (!query) return null;
|
|
3866
|
+
return { query, exact };
|
|
3867
|
+
}
|
|
3868
|
+
function parseRoleSelector(selector) {
|
|
3869
|
+
if (!selector.startsWith("role:")) return null;
|
|
3870
|
+
const body = selector.slice(5);
|
|
3871
|
+
const separator = body.indexOf(":");
|
|
3872
|
+
const role = (separator === -1 ? body : body.slice(0, separator)).trim().toLowerCase();
|
|
3873
|
+
const name = separator === -1 ? void 0 : stripQuotes(body.slice(separator + 1).trim());
|
|
3874
|
+
if (!role) return null;
|
|
3875
|
+
return { role, name: name || void 0 };
|
|
3876
|
+
}
|
|
3877
|
+
var SPECIAL_SELECTOR_SCRIPT = `
|
|
3878
|
+
function bpNormalizeSpace(value) {
|
|
3879
|
+
return String(value == null ? '' : value).replace(/\\s+/g, ' ').trim();
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
function bpCollectElements(root) {
|
|
3883
|
+
var elements = [];
|
|
3884
|
+
|
|
3885
|
+
function visit(node) {
|
|
3886
|
+
if (!node || typeof node.querySelectorAll !== 'function') return;
|
|
3887
|
+
var matches = node.querySelectorAll('*');
|
|
3888
|
+
for (var i = 0; i < matches.length; i++) {
|
|
3889
|
+
var el = matches[i];
|
|
3890
|
+
elements.push(el);
|
|
3891
|
+
if (el.shadowRoot) {
|
|
3892
|
+
visit(el.shadowRoot);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
|
|
3897
|
+
if (root && root.documentElement) {
|
|
3898
|
+
elements.push(root.documentElement);
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
visit(root);
|
|
3902
|
+
return elements;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
function bpIsVisible(el) {
|
|
3906
|
+
if (!el) return false;
|
|
3907
|
+
var style = getComputedStyle(el);
|
|
3908
|
+
if (style.display === 'none') return false;
|
|
3909
|
+
if (style.visibility === 'hidden') return false;
|
|
3910
|
+
if (parseFloat(style.opacity || '1') === 0) return false;
|
|
3911
|
+
var rect = el.getBoundingClientRect();
|
|
3912
|
+
return rect.width > 0 && rect.height > 0;
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
function bpInferRole(el) {
|
|
3916
|
+
if (!el || !el.tagName) return '';
|
|
3917
|
+
|
|
3918
|
+
var explicitRole = bpNormalizeSpace(el.getAttribute && el.getAttribute('role'));
|
|
3919
|
+
if (explicitRole) return explicitRole.toLowerCase();
|
|
3920
|
+
|
|
3921
|
+
var tag = el.tagName.toLowerCase();
|
|
3922
|
+
if (tag === 'button') return 'button';
|
|
3923
|
+
if (tag === 'a' && el.hasAttribute('href')) return 'link';
|
|
3924
|
+
if (tag === 'textarea') return 'textbox';
|
|
3925
|
+
if (tag === 'select') return el.multiple ? 'listbox' : 'combobox';
|
|
3926
|
+
if (tag === 'option') return 'option';
|
|
3927
|
+
if (tag === 'summary') return 'button';
|
|
3928
|
+
|
|
3929
|
+
if (tag === 'input') {
|
|
3930
|
+
var type = (el.type || 'text').toLowerCase();
|
|
3931
|
+
if (type === 'checkbox') return 'checkbox';
|
|
3932
|
+
if (type === 'radio') return 'radio';
|
|
3933
|
+
if (type === 'search') return 'searchbox';
|
|
3934
|
+
if (type === 'number') return 'spinbutton';
|
|
3935
|
+
if (type === 'button' || type === 'submit' || type === 'reset' || type === 'image') {
|
|
3936
|
+
return 'button';
|
|
3937
|
+
}
|
|
3938
|
+
return 'textbox';
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3941
|
+
return '';
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
function bpTextFromIdRefs(refs) {
|
|
3945
|
+
if (!refs) return '';
|
|
3946
|
+
var ids = refs.split(/\\s+/).filter(Boolean);
|
|
3947
|
+
var parts = [];
|
|
3948
|
+
for (var i = 0; i < ids.length; i++) {
|
|
3949
|
+
var node = document.getElementById(ids[i]);
|
|
3950
|
+
if (!node) continue;
|
|
3951
|
+
var text = bpNormalizeSpace(node.innerText || node.textContent || '');
|
|
3952
|
+
if (text) parts.push(text);
|
|
3953
|
+
}
|
|
3954
|
+
return bpNormalizeSpace(parts.join(' '));
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
function bpAccessibleName(el) {
|
|
3958
|
+
if (!el) return '';
|
|
3959
|
+
|
|
3960
|
+
var labelledBy = bpTextFromIdRefs(el.getAttribute && el.getAttribute('aria-labelledby'));
|
|
3961
|
+
if (labelledBy) return labelledBy;
|
|
3962
|
+
|
|
3963
|
+
var ariaLabel = bpNormalizeSpace(el.getAttribute && el.getAttribute('aria-label'));
|
|
3964
|
+
if (ariaLabel) return ariaLabel;
|
|
3965
|
+
|
|
3966
|
+
if (el.labels && el.labels.length) {
|
|
3967
|
+
var labels = [];
|
|
3968
|
+
for (var i = 0; i < el.labels.length; i++) {
|
|
3969
|
+
var labelText = bpNormalizeSpace(el.labels[i].innerText || el.labels[i].textContent || '');
|
|
3970
|
+
if (labelText) labels.push(labelText);
|
|
3971
|
+
}
|
|
3972
|
+
if (labels.length) return bpNormalizeSpace(labels.join(' '));
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
if (el.id) {
|
|
3976
|
+
var fallbackLabel = document.querySelector('label[for="' + el.id.replace(/"/g, '\\\\"') + '"]');
|
|
3977
|
+
if (fallbackLabel) {
|
|
3978
|
+
var fallbackText = bpNormalizeSpace(
|
|
3979
|
+
fallbackLabel.innerText || fallbackLabel.textContent || ''
|
|
3980
|
+
);
|
|
3981
|
+
if (fallbackText) return fallbackText;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
var type = (el.type || '').toLowerCase();
|
|
3986
|
+
if (
|
|
3987
|
+
el.tagName === 'INPUT' &&
|
|
3988
|
+
(type === 'submit' || type === 'button' || type === 'reset' || type === 'image')
|
|
3989
|
+
) {
|
|
3990
|
+
var inputValue = bpNormalizeSpace(el.value || el.getAttribute('value'));
|
|
3991
|
+
if (inputValue) return inputValue;
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
var alt = bpNormalizeSpace(el.getAttribute && el.getAttribute('alt'));
|
|
3995
|
+
if (alt) return alt;
|
|
3996
|
+
|
|
3997
|
+
var text = bpNormalizeSpace(el.innerText || el.textContent || '');
|
|
3998
|
+
if (text) return text;
|
|
3999
|
+
|
|
4000
|
+
var placeholder = bpNormalizeSpace(el.getAttribute && el.getAttribute('placeholder'));
|
|
4001
|
+
if (placeholder) return placeholder;
|
|
4002
|
+
|
|
4003
|
+
var title = bpNormalizeSpace(el.getAttribute && el.getAttribute('title'));
|
|
4004
|
+
if (title) return title;
|
|
4005
|
+
|
|
4006
|
+
var value = bpNormalizeSpace(el.value);
|
|
4007
|
+
if (value) return value;
|
|
4008
|
+
|
|
4009
|
+
return bpNormalizeSpace(el.name || el.id || '');
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
function bpIsInteractive(role, el) {
|
|
4013
|
+
if (
|
|
4014
|
+
role === 'button' ||
|
|
4015
|
+
role === 'link' ||
|
|
4016
|
+
role === 'textbox' ||
|
|
4017
|
+
role === 'checkbox' ||
|
|
4018
|
+
role === 'radio' ||
|
|
4019
|
+
role === 'combobox' ||
|
|
4020
|
+
role === 'listbox' ||
|
|
4021
|
+
role === 'option' ||
|
|
4022
|
+
role === 'searchbox' ||
|
|
4023
|
+
role === 'spinbutton' ||
|
|
4024
|
+
role === 'switch' ||
|
|
4025
|
+
role === 'tab'
|
|
4026
|
+
) {
|
|
4027
|
+
return true;
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
if (!el || !el.tagName) return false;
|
|
4031
|
+
var tag = el.tagName.toLowerCase();
|
|
4032
|
+
return tag === 'button' || tag === 'a' || tag === 'input' || tag === 'select' || tag === 'textarea';
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
function bpFindByText(query, exact, includeHidden) {
|
|
4036
|
+
var needle = bpNormalizeSpace(query).toLowerCase();
|
|
4037
|
+
if (!needle) return null;
|
|
4038
|
+
|
|
4039
|
+
var best = null;
|
|
4040
|
+
var bestScore = -1;
|
|
4041
|
+
var elements = bpCollectElements(document);
|
|
4042
|
+
|
|
4043
|
+
for (var i = 0; i < elements.length; i++) {
|
|
4044
|
+
var el = elements[i];
|
|
4045
|
+
if (!includeHidden && !bpIsVisible(el)) continue;
|
|
4046
|
+
|
|
4047
|
+
var text = bpAccessibleName(el);
|
|
4048
|
+
if (!text) continue;
|
|
4049
|
+
|
|
4050
|
+
var haystack = text.toLowerCase();
|
|
4051
|
+
var matched = exact ? haystack === needle : haystack.includes(needle);
|
|
4052
|
+
if (!matched) continue;
|
|
4053
|
+
|
|
4054
|
+
var role = bpInferRole(el);
|
|
4055
|
+
var score = 0;
|
|
4056
|
+
if (bpIsInteractive(role, el)) score += 100;
|
|
4057
|
+
if (haystack === needle) score += 50;
|
|
4058
|
+
if (role === 'button' || role === 'link') score += 10;
|
|
4059
|
+
|
|
4060
|
+
if (score > bestScore) {
|
|
4061
|
+
best = el;
|
|
4062
|
+
bestScore = score;
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
return best;
|
|
4067
|
+
}
|
|
4068
|
+
|
|
4069
|
+
function bpFindByRole(role, name, includeHidden) {
|
|
4070
|
+
var targetRole = bpNormalizeSpace(role).toLowerCase();
|
|
4071
|
+
if (!targetRole) return null;
|
|
4072
|
+
|
|
4073
|
+
var nameNeedle = bpNormalizeSpace(name).toLowerCase();
|
|
4074
|
+
var best = null;
|
|
4075
|
+
var bestScore = -1;
|
|
4076
|
+
var elements = bpCollectElements(document);
|
|
4077
|
+
|
|
4078
|
+
for (var i = 0; i < elements.length; i++) {
|
|
4079
|
+
var el = elements[i];
|
|
4080
|
+
if (!includeHidden && !bpIsVisible(el)) continue;
|
|
4081
|
+
|
|
4082
|
+
var actualRole = bpInferRole(el);
|
|
4083
|
+
if (actualRole !== targetRole) continue;
|
|
4084
|
+
|
|
4085
|
+
var accessibleName = bpAccessibleName(el);
|
|
4086
|
+
if (nameNeedle) {
|
|
4087
|
+
var loweredName = accessibleName.toLowerCase();
|
|
4088
|
+
if (!loweredName.includes(nameNeedle)) continue;
|
|
4089
|
+
}
|
|
4090
|
+
|
|
4091
|
+
var score = 0;
|
|
4092
|
+
if (accessibleName) score += 10;
|
|
4093
|
+
if (nameNeedle && accessibleName.toLowerCase() === nameNeedle) score += 20;
|
|
4094
|
+
|
|
4095
|
+
if (score > bestScore) {
|
|
4096
|
+
best = el;
|
|
4097
|
+
bestScore = score;
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
return best;
|
|
4102
|
+
}
|
|
4103
|
+
`;
|
|
4104
|
+
function buildSpecialSelectorLookupExpression(selector, options = {}) {
|
|
4105
|
+
const includeHidden = options.includeHidden === true;
|
|
4106
|
+
const text = parseTextSelector(selector);
|
|
4107
|
+
if (text) {
|
|
4108
|
+
return `(() => {
|
|
4109
|
+
${SPECIAL_SELECTOR_SCRIPT}
|
|
4110
|
+
return bpFindByText(${JSON.stringify(text.query)}, ${text.exact}, ${includeHidden});
|
|
4111
|
+
})()`;
|
|
4112
|
+
}
|
|
4113
|
+
const role = parseRoleSelector(selector);
|
|
4114
|
+
if (role) {
|
|
4115
|
+
return `(() => {
|
|
4116
|
+
${SPECIAL_SELECTOR_SCRIPT}
|
|
4117
|
+
return bpFindByRole(${JSON.stringify(role.role)}, ${JSON.stringify(role.name ?? "")}, ${includeHidden});
|
|
4118
|
+
})()`;
|
|
4119
|
+
}
|
|
4120
|
+
return null;
|
|
4121
|
+
}
|
|
4122
|
+
function buildSpecialSelectorPredicateExpression(selector, options = {}) {
|
|
4123
|
+
const lookup = buildSpecialSelectorLookupExpression(selector, options);
|
|
4124
|
+
if (!lookup) return null;
|
|
4125
|
+
return `(() => !!(${lookup}))()`;
|
|
4126
|
+
}
|
|
4127
|
+
|
|
3796
4128
|
// src/wait/strategies.ts
|
|
3797
4129
|
var DEEP_QUERY_SCRIPT = `
|
|
3798
4130
|
function deepQuery(selector, root = document) {
|
|
@@ -3826,18 +4158,19 @@ function deepQuery(selector, root = document) {
|
|
|
3826
4158
|
}
|
|
3827
4159
|
`;
|
|
3828
4160
|
async function isElementVisible(cdp, selector, contextId) {
|
|
4161
|
+
const specialExpression = buildSpecialSelectorPredicateExpression(selector);
|
|
3829
4162
|
const params = {
|
|
3830
|
-
expression: `(() => {
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
4163
|
+
expression: specialExpression ?? `(() => {
|
|
4164
|
+
${DEEP_QUERY_SCRIPT}
|
|
4165
|
+
const el = deepQuery(${JSON.stringify(selector)});
|
|
4166
|
+
if (!el) return false;
|
|
4167
|
+
const style = getComputedStyle(el);
|
|
4168
|
+
if (style.display === 'none') return false;
|
|
4169
|
+
if (style.visibility === 'hidden') return false;
|
|
4170
|
+
if (parseFloat(style.opacity) === 0) return false;
|
|
4171
|
+
const rect = el.getBoundingClientRect();
|
|
4172
|
+
return rect.width > 0 && rect.height > 0;
|
|
4173
|
+
})()`,
|
|
3841
4174
|
returnByValue: true
|
|
3842
4175
|
};
|
|
3843
4176
|
if (contextId !== void 0) {
|
|
@@ -3847,11 +4180,14 @@ async function isElementVisible(cdp, selector, contextId) {
|
|
|
3847
4180
|
return result.result.value === true;
|
|
3848
4181
|
}
|
|
3849
4182
|
async function isElementAttached(cdp, selector, contextId) {
|
|
4183
|
+
const specialExpression = buildSpecialSelectorPredicateExpression(selector, {
|
|
4184
|
+
includeHidden: true
|
|
4185
|
+
});
|
|
3850
4186
|
const params = {
|
|
3851
|
-
expression: `(() => {
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
4187
|
+
expression: specialExpression ?? `(() => {
|
|
4188
|
+
${DEEP_QUERY_SCRIPT}
|
|
4189
|
+
return deepQuery(${JSON.stringify(selector)}) !== null;
|
|
4190
|
+
})()`,
|
|
3855
4191
|
returnByValue: true
|
|
3856
4192
|
};
|
|
3857
4193
|
if (contextId !== void 0) {
|
|
@@ -4512,6 +4848,9 @@ var Page = class {
|
|
|
4512
4848
|
timeout: options.timeout ?? DEFAULT_TIMEOUT2
|
|
4513
4849
|
});
|
|
4514
4850
|
} catch (e) {
|
|
4851
|
+
if (e instanceof ActionabilityError && e.failureType === "hitTarget" && await this.tryClickAssociatedLabel(objectId)) {
|
|
4852
|
+
return true;
|
|
4853
|
+
}
|
|
4515
4854
|
if (options.optional) return false;
|
|
4516
4855
|
throw e;
|
|
4517
4856
|
}
|
|
@@ -4900,7 +5239,12 @@ var Page = class {
|
|
|
4900
5239
|
returnByValue: true
|
|
4901
5240
|
});
|
|
4902
5241
|
if (!after.result.value) {
|
|
4903
|
-
|
|
5242
|
+
if (await this.tryToggleViaLabel(object.objectId, true)) {
|
|
5243
|
+
return true;
|
|
5244
|
+
}
|
|
5245
|
+
throw new Error(
|
|
5246
|
+
"Clicking the checkbox did not change its state. Tried the associated label too."
|
|
5247
|
+
);
|
|
4904
5248
|
}
|
|
4905
5249
|
return true;
|
|
4906
5250
|
});
|
|
@@ -4952,7 +5296,12 @@ var Page = class {
|
|
|
4952
5296
|
returnByValue: true
|
|
4953
5297
|
});
|
|
4954
5298
|
if (after.result.value) {
|
|
4955
|
-
|
|
5299
|
+
if (await this.tryToggleViaLabel(object.objectId, false)) {
|
|
5300
|
+
return true;
|
|
5301
|
+
}
|
|
5302
|
+
throw new Error(
|
|
5303
|
+
"Clicking the checkbox did not change its state. Tried the associated label too."
|
|
5304
|
+
);
|
|
4956
5305
|
}
|
|
4957
5306
|
return true;
|
|
4958
5307
|
});
|
|
@@ -5276,7 +5625,7 @@ var Page = class {
|
|
|
5276
5625
|
}
|
|
5277
5626
|
const result = await this.cdp.send("Runtime.evaluate", params);
|
|
5278
5627
|
if (result.exceptionDetails) {
|
|
5279
|
-
throw new Error(
|
|
5628
|
+
throw new Error(this.formatEvaluationError(result.exceptionDetails));
|
|
5280
5629
|
}
|
|
5281
5630
|
return result.result.value;
|
|
5282
5631
|
}
|
|
@@ -5328,6 +5677,75 @@ var Page = class {
|
|
|
5328
5677
|
return result.result.value ?? "";
|
|
5329
5678
|
});
|
|
5330
5679
|
}
|
|
5680
|
+
/**
|
|
5681
|
+
* Enumerate form controls on the page with labels and current state.
|
|
5682
|
+
*/
|
|
5683
|
+
async forms() {
|
|
5684
|
+
const result = await this.evaluateInFrame(
|
|
5685
|
+
`(() => {
|
|
5686
|
+
function normalize(value) {
|
|
5687
|
+
return String(value == null ? '' : value).replace(/\\s+/g, ' ').trim();
|
|
5688
|
+
}
|
|
5689
|
+
|
|
5690
|
+
function labelFor(el) {
|
|
5691
|
+
if (!el) return '';
|
|
5692
|
+
if (el.labels && el.labels.length) {
|
|
5693
|
+
return normalize(
|
|
5694
|
+
Array.from(el.labels)
|
|
5695
|
+
.map((label) => label.innerText || label.textContent || '')
|
|
5696
|
+
.join(' ')
|
|
5697
|
+
);
|
|
5698
|
+
}
|
|
5699
|
+
var ariaLabel = normalize(el.getAttribute && el.getAttribute('aria-label'));
|
|
5700
|
+
if (ariaLabel) return ariaLabel;
|
|
5701
|
+
if (el.id) {
|
|
5702
|
+
var byFor = document.querySelector('label[for="' + el.id.replace(/"/g, '\\\\"') + '"]');
|
|
5703
|
+
if (byFor) return normalize(byFor.innerText || byFor.textContent || '');
|
|
5704
|
+
}
|
|
5705
|
+
var closest = el.closest && el.closest('label');
|
|
5706
|
+
if (closest) return normalize(closest.innerText || closest.textContent || '');
|
|
5707
|
+
return '';
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5710
|
+
return Array.from(document.querySelectorAll('input, select, textarea')).map((el) => {
|
|
5711
|
+
var tag = el.tagName.toLowerCase();
|
|
5712
|
+
var type = tag === 'input' ? (el.type || 'text').toLowerCase() : tag;
|
|
5713
|
+
var value = null;
|
|
5714
|
+
|
|
5715
|
+
if (tag === 'select') {
|
|
5716
|
+
value = el.multiple
|
|
5717
|
+
? Array.from(el.selectedOptions).map((opt) => opt.value)
|
|
5718
|
+
: el.value || null;
|
|
5719
|
+
} else if (tag === 'textarea' || tag === 'input') {
|
|
5720
|
+
value = typeof el.value === 'string' ? el.value : null;
|
|
5721
|
+
}
|
|
5722
|
+
|
|
5723
|
+
return {
|
|
5724
|
+
tag: tag,
|
|
5725
|
+
type: type,
|
|
5726
|
+
id: el.id || undefined,
|
|
5727
|
+
name: el.getAttribute('name') || undefined,
|
|
5728
|
+
value: value,
|
|
5729
|
+
checked: 'checked' in el ? !!el.checked : undefined,
|
|
5730
|
+
required: !!el.required,
|
|
5731
|
+
disabled: !!el.disabled,
|
|
5732
|
+
label: labelFor(el) || undefined,
|
|
5733
|
+
placeholder: normalize(el.getAttribute && el.getAttribute('placeholder')) || undefined,
|
|
5734
|
+
options:
|
|
5735
|
+
tag === 'select'
|
|
5736
|
+
? Array.from(el.options).map((opt) => ({
|
|
5737
|
+
value: opt.value || '',
|
|
5738
|
+
text: normalize(opt.text || opt.label || ''),
|
|
5739
|
+
selected: !!opt.selected,
|
|
5740
|
+
disabled: !!opt.disabled,
|
|
5741
|
+
}))
|
|
5742
|
+
: undefined,
|
|
5743
|
+
};
|
|
5744
|
+
});
|
|
5745
|
+
})()`
|
|
5746
|
+
);
|
|
5747
|
+
return result.result.value ?? [];
|
|
5748
|
+
}
|
|
5331
5749
|
// ============ File Handling ============
|
|
5332
5750
|
/**
|
|
5333
5751
|
* Set files on a file input
|
|
@@ -5800,7 +6218,8 @@ var Page = class {
|
|
|
5800
6218
|
/**
|
|
5801
6219
|
* Get an accessibility tree snapshot of the page
|
|
5802
6220
|
*/
|
|
5803
|
-
async snapshot() {
|
|
6221
|
+
async snapshot(options = {}) {
|
|
6222
|
+
const roleFilter = new Set((options.roles ?? []).map((role) => role.trim().toLowerCase()));
|
|
5804
6223
|
const [url, title, axTree] = await Promise.all([
|
|
5805
6224
|
this.url(),
|
|
5806
6225
|
this.title(),
|
|
@@ -5821,7 +6240,7 @@ var Page = class {
|
|
|
5821
6240
|
const buildNode = (nodeId) => {
|
|
5822
6241
|
const node = nodeMap.get(nodeId);
|
|
5823
6242
|
if (!node) return null;
|
|
5824
|
-
const role = node.role?.value ?? "generic";
|
|
6243
|
+
const role = (node.role?.value ?? "generic").toLowerCase();
|
|
5825
6244
|
const name = node.name?.value;
|
|
5826
6245
|
const value = node.value?.value;
|
|
5827
6246
|
const ref = nodeRefs.get(nodeId);
|
|
@@ -5837,7 +6256,7 @@ var Page = class {
|
|
|
5837
6256
|
return {
|
|
5838
6257
|
role,
|
|
5839
6258
|
name,
|
|
5840
|
-
value,
|
|
6259
|
+
value: value !== void 0 ? String(value) : void 0,
|
|
5841
6260
|
ref,
|
|
5842
6261
|
children: children.length > 0 ? children : void 0,
|
|
5843
6262
|
disabled,
|
|
@@ -5845,7 +6264,24 @@ var Page = class {
|
|
|
5845
6264
|
};
|
|
5846
6265
|
};
|
|
5847
6266
|
const rootNodes = nodes.filter((n) => !n.parentId || !nodeMap.has(n.parentId));
|
|
5848
|
-
|
|
6267
|
+
let accessibilityTree = rootNodes.map((n) => buildNode(n.nodeId)).filter((n) => n !== null);
|
|
6268
|
+
if (roleFilter.size > 0) {
|
|
6269
|
+
const filteredAccessibilityTree = [];
|
|
6270
|
+
for (const node of nodes) {
|
|
6271
|
+
if (!roleFilter.has((node.role?.value ?? "generic").toLowerCase())) {
|
|
6272
|
+
continue;
|
|
6273
|
+
}
|
|
6274
|
+
const snapshotNode = buildNode(node.nodeId);
|
|
6275
|
+
if (!snapshotNode) {
|
|
6276
|
+
continue;
|
|
6277
|
+
}
|
|
6278
|
+
filteredAccessibilityTree.push({
|
|
6279
|
+
...snapshotNode,
|
|
6280
|
+
children: void 0
|
|
6281
|
+
});
|
|
6282
|
+
}
|
|
6283
|
+
accessibilityTree = filteredAccessibilityTree;
|
|
6284
|
+
}
|
|
5849
6285
|
const interactiveRoles = /* @__PURE__ */ new Set([
|
|
5850
6286
|
"button",
|
|
5851
6287
|
"link",
|
|
@@ -5867,37 +6303,44 @@ var Page = class {
|
|
|
5867
6303
|
]);
|
|
5868
6304
|
const interactiveElements = [];
|
|
5869
6305
|
for (const node of nodes) {
|
|
5870
|
-
const role = node.role?.value;
|
|
5871
|
-
if (role && interactiveRoles.has(role)) {
|
|
6306
|
+
const role = (node.role?.value ?? "").toLowerCase();
|
|
6307
|
+
if (role && interactiveRoles.has(role) && (roleFilter.size === 0 || roleFilter.has(role))) {
|
|
5872
6308
|
const ref = nodeRefs.get(node.nodeId);
|
|
5873
6309
|
const name = node.name?.value ?? "";
|
|
5874
6310
|
const disabled = node.properties?.find((p) => p.name === "disabled")?.value.value;
|
|
6311
|
+
const checked = node.properties?.find((p) => p.name === "checked")?.value.value;
|
|
6312
|
+
const value = node.value?.value;
|
|
5875
6313
|
const selector = node.backendDOMNodeId ? `[data-backend-node-id="${node.backendDOMNodeId}"]` : `[aria-label="${name}"]`;
|
|
5876
6314
|
interactiveElements.push({
|
|
5877
6315
|
ref,
|
|
5878
6316
|
role,
|
|
5879
6317
|
name,
|
|
5880
6318
|
selector,
|
|
5881
|
-
disabled
|
|
6319
|
+
disabled,
|
|
6320
|
+
checked,
|
|
6321
|
+
value: value !== void 0 ? String(value) : void 0
|
|
5882
6322
|
});
|
|
5883
6323
|
}
|
|
5884
6324
|
}
|
|
6325
|
+
const formatNode = (node, depth = 0) => {
|
|
6326
|
+
let line = `${" ".repeat(depth)}- ${node.role}`;
|
|
6327
|
+
if (node.name) line += ` "${node.name}"`;
|
|
6328
|
+
line += ` ref:${node.ref}`;
|
|
6329
|
+
if (node.disabled) line += " (disabled)";
|
|
6330
|
+
if (node.checked !== void 0) line += node.checked ? " (checked)" : " (unchecked)";
|
|
6331
|
+
return line;
|
|
6332
|
+
};
|
|
5885
6333
|
const formatTree = (nodes2, depth = 0) => {
|
|
5886
6334
|
const lines = [];
|
|
5887
6335
|
for (const node of nodes2) {
|
|
5888
|
-
|
|
5889
|
-
if (node.name) line += ` "${node.name}"`;
|
|
5890
|
-
line += ` [ref=${node.ref}]`;
|
|
5891
|
-
if (node.disabled) line += " (disabled)";
|
|
5892
|
-
if (node.checked !== void 0) line += node.checked ? " (checked)" : " (unchecked)";
|
|
5893
|
-
lines.push(line);
|
|
6336
|
+
lines.push(formatNode(node, depth));
|
|
5894
6337
|
if (node.children) {
|
|
5895
6338
|
lines.push(formatTree(node.children, depth + 1));
|
|
5896
6339
|
}
|
|
5897
6340
|
}
|
|
5898
6341
|
return lines.join("\n");
|
|
5899
6342
|
};
|
|
5900
|
-
const text = formatTree(accessibilityTree);
|
|
6343
|
+
const text = roleFilter.size > 0 ? accessibilityTree.map((node) => formatNode(node)).join("\n") : formatTree(accessibilityTree);
|
|
5901
6344
|
const result = {
|
|
5902
6345
|
url,
|
|
5903
6346
|
title,
|
|
@@ -5906,7 +6349,9 @@ var Page = class {
|
|
|
5906
6349
|
interactiveElements,
|
|
5907
6350
|
text
|
|
5908
6351
|
};
|
|
5909
|
-
|
|
6352
|
+
if (roleFilter.size === 0) {
|
|
6353
|
+
this.lastSnapshot = result;
|
|
6354
|
+
}
|
|
5910
6355
|
return result;
|
|
5911
6356
|
}
|
|
5912
6357
|
/**
|
|
@@ -6461,7 +6906,7 @@ var Page = class {
|
|
|
6461
6906
|
}
|
|
6462
6907
|
/**
|
|
6463
6908
|
* Find an element using single or multiple selectors
|
|
6464
|
-
* Supports ref
|
|
6909
|
+
* Supports ref:, text:, and role: selectors.
|
|
6465
6910
|
*/
|
|
6466
6911
|
async findElement(selectors, options = {}) {
|
|
6467
6912
|
const { timeout = DEFAULT_TIMEOUT2 } = options;
|
|
@@ -6528,11 +6973,11 @@ var Page = class {
|
|
|
6528
6973
|
}
|
|
6529
6974
|
}
|
|
6530
6975
|
}
|
|
6531
|
-
const
|
|
6532
|
-
if (
|
|
6976
|
+
const runtimeSelectors = selectorList.filter((s) => !s.startsWith("ref:"));
|
|
6977
|
+
if (runtimeSelectors.length === 0) {
|
|
6533
6978
|
return null;
|
|
6534
6979
|
}
|
|
6535
|
-
const result = await waitForAnyElement(this.cdp,
|
|
6980
|
+
const result = await waitForAnyElement(this.cdp, runtimeSelectors, {
|
|
6536
6981
|
state: "visible",
|
|
6537
6982
|
timeout,
|
|
6538
6983
|
contextId: this.currentFrameContextId ?? void 0
|
|
@@ -6540,6 +6985,14 @@ var Page = class {
|
|
|
6540
6985
|
if (!result.success || !result.selector) {
|
|
6541
6986
|
return null;
|
|
6542
6987
|
}
|
|
6988
|
+
const specialSelectorMatch = await this.resolveSpecialSelector(result.selector);
|
|
6989
|
+
if (specialSelectorMatch) {
|
|
6990
|
+
this._lastMatchedSelector = result.selector;
|
|
6991
|
+
return {
|
|
6992
|
+
...specialSelectorMatch,
|
|
6993
|
+
waitedMs: result.waitedMs
|
|
6994
|
+
};
|
|
6995
|
+
}
|
|
6543
6996
|
await this.ensureRootNode();
|
|
6544
6997
|
const queryResult = await this.cdp.send("DOM.querySelector", {
|
|
6545
6998
|
nodeId: this.rootNodeId,
|
|
@@ -6586,6 +7039,122 @@ var Page = class {
|
|
|
6586
7039
|
waitedMs: result.waitedMs
|
|
6587
7040
|
};
|
|
6588
7041
|
}
|
|
7042
|
+
formatEvaluationError(details) {
|
|
7043
|
+
const description = typeof details.exception?.description === "string" && details.exception.description || typeof details.exception?.value === "string" && details.exception.value || details.text || "Uncaught";
|
|
7044
|
+
return `Evaluation failed: ${description}`;
|
|
7045
|
+
}
|
|
7046
|
+
async resolveSpecialSelector(selector, options = {}) {
|
|
7047
|
+
const expression = buildSpecialSelectorLookupExpression(selector, options);
|
|
7048
|
+
if (!expression) return null;
|
|
7049
|
+
const result = await this.evaluateInFrame(expression, {
|
|
7050
|
+
returnByValue: false
|
|
7051
|
+
});
|
|
7052
|
+
if (!result.result.objectId) {
|
|
7053
|
+
return null;
|
|
7054
|
+
}
|
|
7055
|
+
const resolved = await this.objectIdToNode(result.result.objectId);
|
|
7056
|
+
if (!resolved) {
|
|
7057
|
+
return null;
|
|
7058
|
+
}
|
|
7059
|
+
return {
|
|
7060
|
+
nodeId: resolved.nodeId,
|
|
7061
|
+
backendNodeId: resolved.backendNodeId,
|
|
7062
|
+
selector,
|
|
7063
|
+
waitedMs: 0
|
|
7064
|
+
};
|
|
7065
|
+
}
|
|
7066
|
+
async readCheckedState(objectId) {
|
|
7067
|
+
const result = await this.cdp.send("Runtime.callFunctionOn", {
|
|
7068
|
+
objectId,
|
|
7069
|
+
functionDeclaration: "function() { return !!this.checked; }",
|
|
7070
|
+
returnByValue: true
|
|
7071
|
+
});
|
|
7072
|
+
return result.result.value === true;
|
|
7073
|
+
}
|
|
7074
|
+
async readInputType(objectId) {
|
|
7075
|
+
const result = await this.cdp.send(
|
|
7076
|
+
"Runtime.callFunctionOn",
|
|
7077
|
+
{
|
|
7078
|
+
objectId,
|
|
7079
|
+
functionDeclaration: 'function() { return this instanceof HTMLInputElement ? String(this.type || "").toLowerCase() : null; }',
|
|
7080
|
+
returnByValue: true
|
|
7081
|
+
}
|
|
7082
|
+
);
|
|
7083
|
+
return result.result.value ?? null;
|
|
7084
|
+
}
|
|
7085
|
+
async getAssociatedLabelNodeId(objectId) {
|
|
7086
|
+
const result = await this.cdp.send("Runtime.callFunctionOn", {
|
|
7087
|
+
objectId,
|
|
7088
|
+
functionDeclaration: `function() {
|
|
7089
|
+
if (!(this instanceof HTMLInputElement)) return null;
|
|
7090
|
+
|
|
7091
|
+
if (this.id) {
|
|
7092
|
+
var labels = Array.from(document.querySelectorAll('label'));
|
|
7093
|
+
for (var i = 0; i < labels.length; i++) {
|
|
7094
|
+
if (labels[i].htmlFor === this.id) return labels[i];
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
|
|
7098
|
+
return this.closest('label');
|
|
7099
|
+
}`,
|
|
7100
|
+
returnByValue: false
|
|
7101
|
+
});
|
|
7102
|
+
if (!result.result.objectId) {
|
|
7103
|
+
return null;
|
|
7104
|
+
}
|
|
7105
|
+
return (await this.objectIdToNode(result.result.objectId))?.nodeId ?? null;
|
|
7106
|
+
}
|
|
7107
|
+
async objectIdToNode(objectId) {
|
|
7108
|
+
const describeResult = await this.cdp.send("DOM.describeNode", {
|
|
7109
|
+
objectId,
|
|
7110
|
+
depth: 0
|
|
7111
|
+
});
|
|
7112
|
+
const backendNodeId = describeResult.node.backendNodeId;
|
|
7113
|
+
if (!backendNodeId) {
|
|
7114
|
+
return null;
|
|
7115
|
+
}
|
|
7116
|
+
if (describeResult.node.nodeId) {
|
|
7117
|
+
return {
|
|
7118
|
+
nodeId: describeResult.node.nodeId,
|
|
7119
|
+
backendNodeId
|
|
7120
|
+
};
|
|
7121
|
+
}
|
|
7122
|
+
await this.ensureRootNode();
|
|
7123
|
+
const pushResult = await this.cdp.send(
|
|
7124
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
7125
|
+
{
|
|
7126
|
+
backendNodeIds: [backendNodeId]
|
|
7127
|
+
}
|
|
7128
|
+
);
|
|
7129
|
+
const nodeId = pushResult.nodeIds?.[0];
|
|
7130
|
+
if (!nodeId) {
|
|
7131
|
+
return null;
|
|
7132
|
+
}
|
|
7133
|
+
return { nodeId, backendNodeId };
|
|
7134
|
+
}
|
|
7135
|
+
async tryClickAssociatedLabel(objectId) {
|
|
7136
|
+
const inputType = await this.readInputType(objectId);
|
|
7137
|
+
if (inputType !== "checkbox" && inputType !== "radio") {
|
|
7138
|
+
return false;
|
|
7139
|
+
}
|
|
7140
|
+
const labelNodeId = await this.getAssociatedLabelNodeId(objectId);
|
|
7141
|
+
if (!labelNodeId) {
|
|
7142
|
+
return false;
|
|
7143
|
+
}
|
|
7144
|
+
try {
|
|
7145
|
+
await this.scrollIntoView(labelNodeId);
|
|
7146
|
+
await this.clickElement(labelNodeId);
|
|
7147
|
+
return true;
|
|
7148
|
+
} catch {
|
|
7149
|
+
return false;
|
|
7150
|
+
}
|
|
7151
|
+
}
|
|
7152
|
+
async tryToggleViaLabel(objectId, desiredChecked) {
|
|
7153
|
+
if (!await this.tryClickAssociatedLabel(objectId)) {
|
|
7154
|
+
return false;
|
|
7155
|
+
}
|
|
7156
|
+
return await this.readCheckedState(objectId) === desiredChecked;
|
|
7157
|
+
}
|
|
6589
7158
|
/**
|
|
6590
7159
|
* Ensure we have a valid root node ID
|
|
6591
7160
|
*/
|
|
@@ -7003,6 +7572,7 @@ var Browser = class _Browser {
|
|
|
7003
7572
|
cdp;
|
|
7004
7573
|
providerSession;
|
|
7005
7574
|
pages = /* @__PURE__ */ new Map();
|
|
7575
|
+
pageCounter = 0;
|
|
7006
7576
|
constructor(cdp, _provider, providerSession, _options) {
|
|
7007
7577
|
this.cdp = cdp;
|
|
7008
7578
|
this.providerSession = providerSession;
|
|
@@ -7032,7 +7602,11 @@ var Browser = class _Browser {
|
|
|
7032
7602
|
const pageName = name ?? "default";
|
|
7033
7603
|
const cached = this.pages.get(pageName);
|
|
7034
7604
|
if (cached) return cached;
|
|
7035
|
-
const targets = await this.cdp.send(
|
|
7605
|
+
const targets = await this.cdp.send(
|
|
7606
|
+
"Target.getTargets",
|
|
7607
|
+
void 0,
|
|
7608
|
+
null
|
|
7609
|
+
);
|
|
7036
7610
|
let pageTargets = targets.targetInfos.filter((t) => t.type === "page");
|
|
7037
7611
|
if (options?.targetUrl) {
|
|
7038
7612
|
const urlFilter = options.targetUrl;
|
|
@@ -7054,16 +7628,24 @@ var Browser = class _Browser {
|
|
|
7054
7628
|
targetId = options.targetId;
|
|
7055
7629
|
} else {
|
|
7056
7630
|
console.warn(`[browser-pilot] Target ${options.targetId} no longer exists, falling back`);
|
|
7057
|
-
targetId = pickBestTarget(pageTargets) ?? (await this.cdp.send(
|
|
7058
|
-
|
|
7059
|
-
|
|
7631
|
+
targetId = pickBestTarget(pageTargets) ?? (await this.cdp.send(
|
|
7632
|
+
"Target.createTarget",
|
|
7633
|
+
{
|
|
7634
|
+
url: "about:blank"
|
|
7635
|
+
},
|
|
7636
|
+
null
|
|
7637
|
+
)).targetId;
|
|
7060
7638
|
}
|
|
7061
7639
|
} else if (pageTargets.length > 0) {
|
|
7062
7640
|
targetId = pickBestTarget(pageTargets);
|
|
7063
7641
|
} else {
|
|
7064
|
-
const result = await this.cdp.send(
|
|
7065
|
-
|
|
7066
|
-
|
|
7642
|
+
const result = await this.cdp.send(
|
|
7643
|
+
"Target.createTarget",
|
|
7644
|
+
{
|
|
7645
|
+
url: "about:blank"
|
|
7646
|
+
},
|
|
7647
|
+
null
|
|
7648
|
+
);
|
|
7067
7649
|
targetId = result.targetId;
|
|
7068
7650
|
}
|
|
7069
7651
|
await this.cdp.attachToTarget(targetId);
|
|
@@ -7091,13 +7673,17 @@ var Browser = class _Browser {
|
|
|
7091
7673
|
* Create a new page (tab)
|
|
7092
7674
|
*/
|
|
7093
7675
|
async newPage(url = "about:blank") {
|
|
7094
|
-
const result = await this.cdp.send(
|
|
7095
|
-
|
|
7096
|
-
|
|
7676
|
+
const result = await this.cdp.send(
|
|
7677
|
+
"Target.createTarget",
|
|
7678
|
+
{
|
|
7679
|
+
url
|
|
7680
|
+
},
|
|
7681
|
+
null
|
|
7682
|
+
);
|
|
7097
7683
|
await this.cdp.attachToTarget(result.targetId);
|
|
7098
7684
|
const page = new Page(this.cdp, result.targetId);
|
|
7099
7685
|
await page.init();
|
|
7100
|
-
const name = `page-${this.
|
|
7686
|
+
const name = `page-${++this.pageCounter}`;
|
|
7101
7687
|
this.pages.set(name, page);
|
|
7102
7688
|
return page;
|
|
7103
7689
|
}
|
|
@@ -7107,14 +7693,30 @@ var Browser = class _Browser {
|
|
|
7107
7693
|
async closePage(name) {
|
|
7108
7694
|
const page = this.pages.get(name);
|
|
7109
7695
|
if (!page) return;
|
|
7110
|
-
const
|
|
7111
|
-
|
|
7112
|
-
if (pageTargets.length > 0) {
|
|
7113
|
-
await this.cdp.send("Target.closeTarget", {
|
|
7114
|
-
targetId: pageTargets[0].targetId
|
|
7115
|
-
});
|
|
7116
|
-
}
|
|
7696
|
+
const targetId = page.targetId;
|
|
7697
|
+
await this.cdp.send("Target.closeTarget", { targetId }, null);
|
|
7117
7698
|
this.pages.delete(name);
|
|
7699
|
+
const deadline = Date.now() + 5e3;
|
|
7700
|
+
while (Date.now() < deadline) {
|
|
7701
|
+
const { targetInfos } = await this.cdp.send(
|
|
7702
|
+
"Target.getTargets",
|
|
7703
|
+
void 0,
|
|
7704
|
+
null
|
|
7705
|
+
);
|
|
7706
|
+
if (!targetInfos.some((t) => t.targetId === targetId)) return;
|
|
7707
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7710
|
+
/**
|
|
7711
|
+
* List all page targets in the connected browser.
|
|
7712
|
+
*/
|
|
7713
|
+
async listTargets() {
|
|
7714
|
+
const { targetInfos } = await this.cdp.send(
|
|
7715
|
+
"Target.getTargets",
|
|
7716
|
+
void 0,
|
|
7717
|
+
null
|
|
7718
|
+
);
|
|
7719
|
+
return targetInfos.filter((target) => target.type === "page");
|
|
7118
7720
|
}
|
|
7119
7721
|
/**
|
|
7120
7722
|
* Get the WebSocket URL for this browser connection
|