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/browser.cjs
CHANGED
|
@@ -216,7 +216,7 @@ async function createCDPClient(wsUrl, options = {}) {
|
|
|
216
216
|
throw new Error("CDP client is not connected");
|
|
217
217
|
}
|
|
218
218
|
const id = ++messageId;
|
|
219
|
-
const effectiveSessionId = sessionId ?? currentSessionId;
|
|
219
|
+
const effectiveSessionId = sessionId === null ? void 0 : sessionId ?? currentSessionId;
|
|
220
220
|
const request = { id, method };
|
|
221
221
|
if (params !== void 0) {
|
|
222
222
|
request.params = params;
|
|
@@ -1396,6 +1396,9 @@ var BatchExecutor = class {
|
|
|
1396
1396
|
const snapshot = await this.page.snapshot();
|
|
1397
1397
|
return { value: snapshot };
|
|
1398
1398
|
}
|
|
1399
|
+
case "forms": {
|
|
1400
|
+
return { value: await this.page.forms() };
|
|
1401
|
+
}
|
|
1399
1402
|
case "screenshot": {
|
|
1400
1403
|
const data = await this.page.screenshot({
|
|
1401
1404
|
format: step.format,
|
|
@@ -1415,6 +1418,21 @@ var BatchExecutor = class {
|
|
|
1415
1418
|
const text = await this.page.text(selector);
|
|
1416
1419
|
return { text, selectorUsed: selector };
|
|
1417
1420
|
}
|
|
1421
|
+
case "newTab": {
|
|
1422
|
+
const { targetId } = await this.page.cdpClient.send(
|
|
1423
|
+
"Target.createTarget",
|
|
1424
|
+
{
|
|
1425
|
+
url: step.url ?? "about:blank"
|
|
1426
|
+
},
|
|
1427
|
+
null
|
|
1428
|
+
);
|
|
1429
|
+
return { value: { targetId } };
|
|
1430
|
+
}
|
|
1431
|
+
case "closeTab": {
|
|
1432
|
+
const targetId = step.targetId ?? this.page.targetId;
|
|
1433
|
+
await this.page.cdpClient.send("Target.closeTarget", { targetId }, null);
|
|
1434
|
+
return { value: { targetId, closedCurrent: targetId === this.page.targetId } };
|
|
1435
|
+
}
|
|
1418
1436
|
case "switchFrame": {
|
|
1419
1437
|
if (!step.selector) throw new Error("switchFrame requires selector");
|
|
1420
1438
|
await this.page.switchToFrame(step.selector, { timeout, optional });
|
|
@@ -1529,10 +1547,15 @@ var BatchExecutor = class {
|
|
|
1529
1547
|
snap: "snapshot",
|
|
1530
1548
|
accessibility: "snapshot",
|
|
1531
1549
|
a11y: "snapshot",
|
|
1550
|
+
formslist: "forms",
|
|
1532
1551
|
image: "screenshot",
|
|
1533
1552
|
pic: "screenshot",
|
|
1534
1553
|
frame: "switchFrame",
|
|
1535
1554
|
iframe: "switchFrame",
|
|
1555
|
+
newtab: "newTab",
|
|
1556
|
+
opentab: "newTab",
|
|
1557
|
+
createtab: "newTab",
|
|
1558
|
+
closetab: "closeTab",
|
|
1536
1559
|
assert_visible: "assertVisible",
|
|
1537
1560
|
assert_exists: "assertExists",
|
|
1538
1561
|
assert_text: "assertText",
|
|
@@ -1546,7 +1569,7 @@ var BatchExecutor = class {
|
|
|
1546
1569
|
};
|
|
1547
1570
|
const suggestion = aliases[action.toLowerCase()];
|
|
1548
1571
|
const hint = suggestion ? ` Did you mean "${suggestion}"?` : "";
|
|
1549
|
-
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";
|
|
1572
|
+
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";
|
|
1550
1573
|
throw new Error(`Unknown action "${action}".${hint}
|
|
1551
1574
|
|
|
1552
1575
|
Valid actions: ${valid}`);
|
|
@@ -2964,6 +2987,285 @@ var RequestInterceptor = class {
|
|
|
2964
2987
|
}
|
|
2965
2988
|
};
|
|
2966
2989
|
|
|
2990
|
+
// src/browser/special-selectors.ts
|
|
2991
|
+
function stripQuotes(value) {
|
|
2992
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2993
|
+
return value.slice(1, -1);
|
|
2994
|
+
}
|
|
2995
|
+
return value;
|
|
2996
|
+
}
|
|
2997
|
+
function parseTextSelector(selector) {
|
|
2998
|
+
if (!selector.startsWith("text:")) return null;
|
|
2999
|
+
let raw = selector.slice(5).trim();
|
|
3000
|
+
let exact = false;
|
|
3001
|
+
if (raw.startsWith("=")) {
|
|
3002
|
+
exact = true;
|
|
3003
|
+
raw = raw.slice(1).trim();
|
|
3004
|
+
}
|
|
3005
|
+
const query = stripQuotes(raw);
|
|
3006
|
+
if (!query) return null;
|
|
3007
|
+
return { query, exact };
|
|
3008
|
+
}
|
|
3009
|
+
function parseRoleSelector(selector) {
|
|
3010
|
+
if (!selector.startsWith("role:")) return null;
|
|
3011
|
+
const body = selector.slice(5);
|
|
3012
|
+
const separator = body.indexOf(":");
|
|
3013
|
+
const role = (separator === -1 ? body : body.slice(0, separator)).trim().toLowerCase();
|
|
3014
|
+
const name = separator === -1 ? void 0 : stripQuotes(body.slice(separator + 1).trim());
|
|
3015
|
+
if (!role) return null;
|
|
3016
|
+
return { role, name: name || void 0 };
|
|
3017
|
+
}
|
|
3018
|
+
var SPECIAL_SELECTOR_SCRIPT = `
|
|
3019
|
+
function bpNormalizeSpace(value) {
|
|
3020
|
+
return String(value == null ? '' : value).replace(/\\s+/g, ' ').trim();
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
function bpCollectElements(root) {
|
|
3024
|
+
var elements = [];
|
|
3025
|
+
|
|
3026
|
+
function visit(node) {
|
|
3027
|
+
if (!node || typeof node.querySelectorAll !== 'function') return;
|
|
3028
|
+
var matches = node.querySelectorAll('*');
|
|
3029
|
+
for (var i = 0; i < matches.length; i++) {
|
|
3030
|
+
var el = matches[i];
|
|
3031
|
+
elements.push(el);
|
|
3032
|
+
if (el.shadowRoot) {
|
|
3033
|
+
visit(el.shadowRoot);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
if (root && root.documentElement) {
|
|
3039
|
+
elements.push(root.documentElement);
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
visit(root);
|
|
3043
|
+
return elements;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
function bpIsVisible(el) {
|
|
3047
|
+
if (!el) return false;
|
|
3048
|
+
var style = getComputedStyle(el);
|
|
3049
|
+
if (style.display === 'none') return false;
|
|
3050
|
+
if (style.visibility === 'hidden') return false;
|
|
3051
|
+
if (parseFloat(style.opacity || '1') === 0) return false;
|
|
3052
|
+
var rect = el.getBoundingClientRect();
|
|
3053
|
+
return rect.width > 0 && rect.height > 0;
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
function bpInferRole(el) {
|
|
3057
|
+
if (!el || !el.tagName) return '';
|
|
3058
|
+
|
|
3059
|
+
var explicitRole = bpNormalizeSpace(el.getAttribute && el.getAttribute('role'));
|
|
3060
|
+
if (explicitRole) return explicitRole.toLowerCase();
|
|
3061
|
+
|
|
3062
|
+
var tag = el.tagName.toLowerCase();
|
|
3063
|
+
if (tag === 'button') return 'button';
|
|
3064
|
+
if (tag === 'a' && el.hasAttribute('href')) return 'link';
|
|
3065
|
+
if (tag === 'textarea') return 'textbox';
|
|
3066
|
+
if (tag === 'select') return el.multiple ? 'listbox' : 'combobox';
|
|
3067
|
+
if (tag === 'option') return 'option';
|
|
3068
|
+
if (tag === 'summary') return 'button';
|
|
3069
|
+
|
|
3070
|
+
if (tag === 'input') {
|
|
3071
|
+
var type = (el.type || 'text').toLowerCase();
|
|
3072
|
+
if (type === 'checkbox') return 'checkbox';
|
|
3073
|
+
if (type === 'radio') return 'radio';
|
|
3074
|
+
if (type === 'search') return 'searchbox';
|
|
3075
|
+
if (type === 'number') return 'spinbutton';
|
|
3076
|
+
if (type === 'button' || type === 'submit' || type === 'reset' || type === 'image') {
|
|
3077
|
+
return 'button';
|
|
3078
|
+
}
|
|
3079
|
+
return 'textbox';
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
return '';
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
function bpTextFromIdRefs(refs) {
|
|
3086
|
+
if (!refs) return '';
|
|
3087
|
+
var ids = refs.split(/\\s+/).filter(Boolean);
|
|
3088
|
+
var parts = [];
|
|
3089
|
+
for (var i = 0; i < ids.length; i++) {
|
|
3090
|
+
var node = document.getElementById(ids[i]);
|
|
3091
|
+
if (!node) continue;
|
|
3092
|
+
var text = bpNormalizeSpace(node.innerText || node.textContent || '');
|
|
3093
|
+
if (text) parts.push(text);
|
|
3094
|
+
}
|
|
3095
|
+
return bpNormalizeSpace(parts.join(' '));
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
function bpAccessibleName(el) {
|
|
3099
|
+
if (!el) return '';
|
|
3100
|
+
|
|
3101
|
+
var labelledBy = bpTextFromIdRefs(el.getAttribute && el.getAttribute('aria-labelledby'));
|
|
3102
|
+
if (labelledBy) return labelledBy;
|
|
3103
|
+
|
|
3104
|
+
var ariaLabel = bpNormalizeSpace(el.getAttribute && el.getAttribute('aria-label'));
|
|
3105
|
+
if (ariaLabel) return ariaLabel;
|
|
3106
|
+
|
|
3107
|
+
if (el.labels && el.labels.length) {
|
|
3108
|
+
var labels = [];
|
|
3109
|
+
for (var i = 0; i < el.labels.length; i++) {
|
|
3110
|
+
var labelText = bpNormalizeSpace(el.labels[i].innerText || el.labels[i].textContent || '');
|
|
3111
|
+
if (labelText) labels.push(labelText);
|
|
3112
|
+
}
|
|
3113
|
+
if (labels.length) return bpNormalizeSpace(labels.join(' '));
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
if (el.id) {
|
|
3117
|
+
var fallbackLabel = document.querySelector('label[for="' + el.id.replace(/"/g, '\\\\"') + '"]');
|
|
3118
|
+
if (fallbackLabel) {
|
|
3119
|
+
var fallbackText = bpNormalizeSpace(
|
|
3120
|
+
fallbackLabel.innerText || fallbackLabel.textContent || ''
|
|
3121
|
+
);
|
|
3122
|
+
if (fallbackText) return fallbackText;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
var type = (el.type || '').toLowerCase();
|
|
3127
|
+
if (
|
|
3128
|
+
el.tagName === 'INPUT' &&
|
|
3129
|
+
(type === 'submit' || type === 'button' || type === 'reset' || type === 'image')
|
|
3130
|
+
) {
|
|
3131
|
+
var inputValue = bpNormalizeSpace(el.value || el.getAttribute('value'));
|
|
3132
|
+
if (inputValue) return inputValue;
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
var alt = bpNormalizeSpace(el.getAttribute && el.getAttribute('alt'));
|
|
3136
|
+
if (alt) return alt;
|
|
3137
|
+
|
|
3138
|
+
var text = bpNormalizeSpace(el.innerText || el.textContent || '');
|
|
3139
|
+
if (text) return text;
|
|
3140
|
+
|
|
3141
|
+
var placeholder = bpNormalizeSpace(el.getAttribute && el.getAttribute('placeholder'));
|
|
3142
|
+
if (placeholder) return placeholder;
|
|
3143
|
+
|
|
3144
|
+
var title = bpNormalizeSpace(el.getAttribute && el.getAttribute('title'));
|
|
3145
|
+
if (title) return title;
|
|
3146
|
+
|
|
3147
|
+
var value = bpNormalizeSpace(el.value);
|
|
3148
|
+
if (value) return value;
|
|
3149
|
+
|
|
3150
|
+
return bpNormalizeSpace(el.name || el.id || '');
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
function bpIsInteractive(role, el) {
|
|
3154
|
+
if (
|
|
3155
|
+
role === 'button' ||
|
|
3156
|
+
role === 'link' ||
|
|
3157
|
+
role === 'textbox' ||
|
|
3158
|
+
role === 'checkbox' ||
|
|
3159
|
+
role === 'radio' ||
|
|
3160
|
+
role === 'combobox' ||
|
|
3161
|
+
role === 'listbox' ||
|
|
3162
|
+
role === 'option' ||
|
|
3163
|
+
role === 'searchbox' ||
|
|
3164
|
+
role === 'spinbutton' ||
|
|
3165
|
+
role === 'switch' ||
|
|
3166
|
+
role === 'tab'
|
|
3167
|
+
) {
|
|
3168
|
+
return true;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
if (!el || !el.tagName) return false;
|
|
3172
|
+
var tag = el.tagName.toLowerCase();
|
|
3173
|
+
return tag === 'button' || tag === 'a' || tag === 'input' || tag === 'select' || tag === 'textarea';
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
function bpFindByText(query, exact, includeHidden) {
|
|
3177
|
+
var needle = bpNormalizeSpace(query).toLowerCase();
|
|
3178
|
+
if (!needle) return null;
|
|
3179
|
+
|
|
3180
|
+
var best = null;
|
|
3181
|
+
var bestScore = -1;
|
|
3182
|
+
var elements = bpCollectElements(document);
|
|
3183
|
+
|
|
3184
|
+
for (var i = 0; i < elements.length; i++) {
|
|
3185
|
+
var el = elements[i];
|
|
3186
|
+
if (!includeHidden && !bpIsVisible(el)) continue;
|
|
3187
|
+
|
|
3188
|
+
var text = bpAccessibleName(el);
|
|
3189
|
+
if (!text) continue;
|
|
3190
|
+
|
|
3191
|
+
var haystack = text.toLowerCase();
|
|
3192
|
+
var matched = exact ? haystack === needle : haystack.includes(needle);
|
|
3193
|
+
if (!matched) continue;
|
|
3194
|
+
|
|
3195
|
+
var role = bpInferRole(el);
|
|
3196
|
+
var score = 0;
|
|
3197
|
+
if (bpIsInteractive(role, el)) score += 100;
|
|
3198
|
+
if (haystack === needle) score += 50;
|
|
3199
|
+
if (role === 'button' || role === 'link') score += 10;
|
|
3200
|
+
|
|
3201
|
+
if (score > bestScore) {
|
|
3202
|
+
best = el;
|
|
3203
|
+
bestScore = score;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
return best;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
function bpFindByRole(role, name, includeHidden) {
|
|
3211
|
+
var targetRole = bpNormalizeSpace(role).toLowerCase();
|
|
3212
|
+
if (!targetRole) return null;
|
|
3213
|
+
|
|
3214
|
+
var nameNeedle = bpNormalizeSpace(name).toLowerCase();
|
|
3215
|
+
var best = null;
|
|
3216
|
+
var bestScore = -1;
|
|
3217
|
+
var elements = bpCollectElements(document);
|
|
3218
|
+
|
|
3219
|
+
for (var i = 0; i < elements.length; i++) {
|
|
3220
|
+
var el = elements[i];
|
|
3221
|
+
if (!includeHidden && !bpIsVisible(el)) continue;
|
|
3222
|
+
|
|
3223
|
+
var actualRole = bpInferRole(el);
|
|
3224
|
+
if (actualRole !== targetRole) continue;
|
|
3225
|
+
|
|
3226
|
+
var accessibleName = bpAccessibleName(el);
|
|
3227
|
+
if (nameNeedle) {
|
|
3228
|
+
var loweredName = accessibleName.toLowerCase();
|
|
3229
|
+
if (!loweredName.includes(nameNeedle)) continue;
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
var score = 0;
|
|
3233
|
+
if (accessibleName) score += 10;
|
|
3234
|
+
if (nameNeedle && accessibleName.toLowerCase() === nameNeedle) score += 20;
|
|
3235
|
+
|
|
3236
|
+
if (score > bestScore) {
|
|
3237
|
+
best = el;
|
|
3238
|
+
bestScore = score;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
return best;
|
|
3243
|
+
}
|
|
3244
|
+
`;
|
|
3245
|
+
function buildSpecialSelectorLookupExpression(selector, options = {}) {
|
|
3246
|
+
const includeHidden = options.includeHidden === true;
|
|
3247
|
+
const text = parseTextSelector(selector);
|
|
3248
|
+
if (text) {
|
|
3249
|
+
return `(() => {
|
|
3250
|
+
${SPECIAL_SELECTOR_SCRIPT}
|
|
3251
|
+
return bpFindByText(${JSON.stringify(text.query)}, ${text.exact}, ${includeHidden});
|
|
3252
|
+
})()`;
|
|
3253
|
+
}
|
|
3254
|
+
const role = parseRoleSelector(selector);
|
|
3255
|
+
if (role) {
|
|
3256
|
+
return `(() => {
|
|
3257
|
+
${SPECIAL_SELECTOR_SCRIPT}
|
|
3258
|
+
return bpFindByRole(${JSON.stringify(role.role)}, ${JSON.stringify(role.name ?? "")}, ${includeHidden});
|
|
3259
|
+
})()`;
|
|
3260
|
+
}
|
|
3261
|
+
return null;
|
|
3262
|
+
}
|
|
3263
|
+
function buildSpecialSelectorPredicateExpression(selector, options = {}) {
|
|
3264
|
+
const lookup = buildSpecialSelectorLookupExpression(selector, options);
|
|
3265
|
+
if (!lookup) return null;
|
|
3266
|
+
return `(() => !!(${lookup}))()`;
|
|
3267
|
+
}
|
|
3268
|
+
|
|
2967
3269
|
// src/wait/strategies.ts
|
|
2968
3270
|
var DEEP_QUERY_SCRIPT = `
|
|
2969
3271
|
function deepQuery(selector, root = document) {
|
|
@@ -2997,18 +3299,19 @@ function deepQuery(selector, root = document) {
|
|
|
2997
3299
|
}
|
|
2998
3300
|
`;
|
|
2999
3301
|
async function isElementVisible(cdp, selector, contextId) {
|
|
3302
|
+
const specialExpression = buildSpecialSelectorPredicateExpression(selector);
|
|
3000
3303
|
const params = {
|
|
3001
|
-
expression: `(() => {
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3304
|
+
expression: specialExpression ?? `(() => {
|
|
3305
|
+
${DEEP_QUERY_SCRIPT}
|
|
3306
|
+
const el = deepQuery(${JSON.stringify(selector)});
|
|
3307
|
+
if (!el) return false;
|
|
3308
|
+
const style = getComputedStyle(el);
|
|
3309
|
+
if (style.display === 'none') return false;
|
|
3310
|
+
if (style.visibility === 'hidden') return false;
|
|
3311
|
+
if (parseFloat(style.opacity) === 0) return false;
|
|
3312
|
+
const rect = el.getBoundingClientRect();
|
|
3313
|
+
return rect.width > 0 && rect.height > 0;
|
|
3314
|
+
})()`,
|
|
3012
3315
|
returnByValue: true
|
|
3013
3316
|
};
|
|
3014
3317
|
if (contextId !== void 0) {
|
|
@@ -3018,11 +3321,14 @@ async function isElementVisible(cdp, selector, contextId) {
|
|
|
3018
3321
|
return result.result.value === true;
|
|
3019
3322
|
}
|
|
3020
3323
|
async function isElementAttached(cdp, selector, contextId) {
|
|
3324
|
+
const specialExpression = buildSpecialSelectorPredicateExpression(selector, {
|
|
3325
|
+
includeHidden: true
|
|
3326
|
+
});
|
|
3021
3327
|
const params = {
|
|
3022
|
-
expression: `(() => {
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3328
|
+
expression: specialExpression ?? `(() => {
|
|
3329
|
+
${DEEP_QUERY_SCRIPT}
|
|
3330
|
+
return deepQuery(${JSON.stringify(selector)}) !== null;
|
|
3331
|
+
})()`,
|
|
3026
3332
|
returnByValue: true
|
|
3027
3333
|
};
|
|
3028
3334
|
if (contextId !== void 0) {
|
|
@@ -3642,6 +3948,9 @@ var Page = class {
|
|
|
3642
3948
|
timeout: options.timeout ?? DEFAULT_TIMEOUT2
|
|
3643
3949
|
});
|
|
3644
3950
|
} catch (e) {
|
|
3951
|
+
if (e instanceof ActionabilityError && e.failureType === "hitTarget" && await this.tryClickAssociatedLabel(objectId)) {
|
|
3952
|
+
return true;
|
|
3953
|
+
}
|
|
3645
3954
|
if (options.optional) return false;
|
|
3646
3955
|
throw e;
|
|
3647
3956
|
}
|
|
@@ -4030,7 +4339,12 @@ var Page = class {
|
|
|
4030
4339
|
returnByValue: true
|
|
4031
4340
|
});
|
|
4032
4341
|
if (!after.result.value) {
|
|
4033
|
-
|
|
4342
|
+
if (await this.tryToggleViaLabel(object.objectId, true)) {
|
|
4343
|
+
return true;
|
|
4344
|
+
}
|
|
4345
|
+
throw new Error(
|
|
4346
|
+
"Clicking the checkbox did not change its state. Tried the associated label too."
|
|
4347
|
+
);
|
|
4034
4348
|
}
|
|
4035
4349
|
return true;
|
|
4036
4350
|
});
|
|
@@ -4082,7 +4396,12 @@ var Page = class {
|
|
|
4082
4396
|
returnByValue: true
|
|
4083
4397
|
});
|
|
4084
4398
|
if (after.result.value) {
|
|
4085
|
-
|
|
4399
|
+
if (await this.tryToggleViaLabel(object.objectId, false)) {
|
|
4400
|
+
return true;
|
|
4401
|
+
}
|
|
4402
|
+
throw new Error(
|
|
4403
|
+
"Clicking the checkbox did not change its state. Tried the associated label too."
|
|
4404
|
+
);
|
|
4086
4405
|
}
|
|
4087
4406
|
return true;
|
|
4088
4407
|
});
|
|
@@ -4406,7 +4725,7 @@ var Page = class {
|
|
|
4406
4725
|
}
|
|
4407
4726
|
const result = await this.cdp.send("Runtime.evaluate", params);
|
|
4408
4727
|
if (result.exceptionDetails) {
|
|
4409
|
-
throw new Error(
|
|
4728
|
+
throw new Error(this.formatEvaluationError(result.exceptionDetails));
|
|
4410
4729
|
}
|
|
4411
4730
|
return result.result.value;
|
|
4412
4731
|
}
|
|
@@ -4458,6 +4777,75 @@ var Page = class {
|
|
|
4458
4777
|
return result.result.value ?? "";
|
|
4459
4778
|
});
|
|
4460
4779
|
}
|
|
4780
|
+
/**
|
|
4781
|
+
* Enumerate form controls on the page with labels and current state.
|
|
4782
|
+
*/
|
|
4783
|
+
async forms() {
|
|
4784
|
+
const result = await this.evaluateInFrame(
|
|
4785
|
+
`(() => {
|
|
4786
|
+
function normalize(value) {
|
|
4787
|
+
return String(value == null ? '' : value).replace(/\\s+/g, ' ').trim();
|
|
4788
|
+
}
|
|
4789
|
+
|
|
4790
|
+
function labelFor(el) {
|
|
4791
|
+
if (!el) return '';
|
|
4792
|
+
if (el.labels && el.labels.length) {
|
|
4793
|
+
return normalize(
|
|
4794
|
+
Array.from(el.labels)
|
|
4795
|
+
.map((label) => label.innerText || label.textContent || '')
|
|
4796
|
+
.join(' ')
|
|
4797
|
+
);
|
|
4798
|
+
}
|
|
4799
|
+
var ariaLabel = normalize(el.getAttribute && el.getAttribute('aria-label'));
|
|
4800
|
+
if (ariaLabel) return ariaLabel;
|
|
4801
|
+
if (el.id) {
|
|
4802
|
+
var byFor = document.querySelector('label[for="' + el.id.replace(/"/g, '\\\\"') + '"]');
|
|
4803
|
+
if (byFor) return normalize(byFor.innerText || byFor.textContent || '');
|
|
4804
|
+
}
|
|
4805
|
+
var closest = el.closest && el.closest('label');
|
|
4806
|
+
if (closest) return normalize(closest.innerText || closest.textContent || '');
|
|
4807
|
+
return '';
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4810
|
+
return Array.from(document.querySelectorAll('input, select, textarea')).map((el) => {
|
|
4811
|
+
var tag = el.tagName.toLowerCase();
|
|
4812
|
+
var type = tag === 'input' ? (el.type || 'text').toLowerCase() : tag;
|
|
4813
|
+
var value = null;
|
|
4814
|
+
|
|
4815
|
+
if (tag === 'select') {
|
|
4816
|
+
value = el.multiple
|
|
4817
|
+
? Array.from(el.selectedOptions).map((opt) => opt.value)
|
|
4818
|
+
: el.value || null;
|
|
4819
|
+
} else if (tag === 'textarea' || tag === 'input') {
|
|
4820
|
+
value = typeof el.value === 'string' ? el.value : null;
|
|
4821
|
+
}
|
|
4822
|
+
|
|
4823
|
+
return {
|
|
4824
|
+
tag: tag,
|
|
4825
|
+
type: type,
|
|
4826
|
+
id: el.id || undefined,
|
|
4827
|
+
name: el.getAttribute('name') || undefined,
|
|
4828
|
+
value: value,
|
|
4829
|
+
checked: 'checked' in el ? !!el.checked : undefined,
|
|
4830
|
+
required: !!el.required,
|
|
4831
|
+
disabled: !!el.disabled,
|
|
4832
|
+
label: labelFor(el) || undefined,
|
|
4833
|
+
placeholder: normalize(el.getAttribute && el.getAttribute('placeholder')) || undefined,
|
|
4834
|
+
options:
|
|
4835
|
+
tag === 'select'
|
|
4836
|
+
? Array.from(el.options).map((opt) => ({
|
|
4837
|
+
value: opt.value || '',
|
|
4838
|
+
text: normalize(opt.text || opt.label || ''),
|
|
4839
|
+
selected: !!opt.selected,
|
|
4840
|
+
disabled: !!opt.disabled,
|
|
4841
|
+
}))
|
|
4842
|
+
: undefined,
|
|
4843
|
+
};
|
|
4844
|
+
});
|
|
4845
|
+
})()`
|
|
4846
|
+
);
|
|
4847
|
+
return result.result.value ?? [];
|
|
4848
|
+
}
|
|
4461
4849
|
// ============ File Handling ============
|
|
4462
4850
|
/**
|
|
4463
4851
|
* Set files on a file input
|
|
@@ -4930,7 +5318,8 @@ var Page = class {
|
|
|
4930
5318
|
/**
|
|
4931
5319
|
* Get an accessibility tree snapshot of the page
|
|
4932
5320
|
*/
|
|
4933
|
-
async snapshot() {
|
|
5321
|
+
async snapshot(options = {}) {
|
|
5322
|
+
const roleFilter = new Set((options.roles ?? []).map((role) => role.trim().toLowerCase()));
|
|
4934
5323
|
const [url, title, axTree] = await Promise.all([
|
|
4935
5324
|
this.url(),
|
|
4936
5325
|
this.title(),
|
|
@@ -4951,7 +5340,7 @@ var Page = class {
|
|
|
4951
5340
|
const buildNode = (nodeId) => {
|
|
4952
5341
|
const node = nodeMap.get(nodeId);
|
|
4953
5342
|
if (!node) return null;
|
|
4954
|
-
const role = node.role?.value ?? "generic";
|
|
5343
|
+
const role = (node.role?.value ?? "generic").toLowerCase();
|
|
4955
5344
|
const name = node.name?.value;
|
|
4956
5345
|
const value = node.value?.value;
|
|
4957
5346
|
const ref = nodeRefs.get(nodeId);
|
|
@@ -4967,7 +5356,7 @@ var Page = class {
|
|
|
4967
5356
|
return {
|
|
4968
5357
|
role,
|
|
4969
5358
|
name,
|
|
4970
|
-
value,
|
|
5359
|
+
value: value !== void 0 ? String(value) : void 0,
|
|
4971
5360
|
ref,
|
|
4972
5361
|
children: children.length > 0 ? children : void 0,
|
|
4973
5362
|
disabled,
|
|
@@ -4975,7 +5364,24 @@ var Page = class {
|
|
|
4975
5364
|
};
|
|
4976
5365
|
};
|
|
4977
5366
|
const rootNodes = nodes.filter((n) => !n.parentId || !nodeMap.has(n.parentId));
|
|
4978
|
-
|
|
5367
|
+
let accessibilityTree = rootNodes.map((n) => buildNode(n.nodeId)).filter((n) => n !== null);
|
|
5368
|
+
if (roleFilter.size > 0) {
|
|
5369
|
+
const filteredAccessibilityTree = [];
|
|
5370
|
+
for (const node of nodes) {
|
|
5371
|
+
if (!roleFilter.has((node.role?.value ?? "generic").toLowerCase())) {
|
|
5372
|
+
continue;
|
|
5373
|
+
}
|
|
5374
|
+
const snapshotNode = buildNode(node.nodeId);
|
|
5375
|
+
if (!snapshotNode) {
|
|
5376
|
+
continue;
|
|
5377
|
+
}
|
|
5378
|
+
filteredAccessibilityTree.push({
|
|
5379
|
+
...snapshotNode,
|
|
5380
|
+
children: void 0
|
|
5381
|
+
});
|
|
5382
|
+
}
|
|
5383
|
+
accessibilityTree = filteredAccessibilityTree;
|
|
5384
|
+
}
|
|
4979
5385
|
const interactiveRoles = /* @__PURE__ */ new Set([
|
|
4980
5386
|
"button",
|
|
4981
5387
|
"link",
|
|
@@ -4997,37 +5403,44 @@ var Page = class {
|
|
|
4997
5403
|
]);
|
|
4998
5404
|
const interactiveElements = [];
|
|
4999
5405
|
for (const node of nodes) {
|
|
5000
|
-
const role = node.role?.value;
|
|
5001
|
-
if (role && interactiveRoles.has(role)) {
|
|
5406
|
+
const role = (node.role?.value ?? "").toLowerCase();
|
|
5407
|
+
if (role && interactiveRoles.has(role) && (roleFilter.size === 0 || roleFilter.has(role))) {
|
|
5002
5408
|
const ref = nodeRefs.get(node.nodeId);
|
|
5003
5409
|
const name = node.name?.value ?? "";
|
|
5004
5410
|
const disabled = node.properties?.find((p) => p.name === "disabled")?.value.value;
|
|
5411
|
+
const checked = node.properties?.find((p) => p.name === "checked")?.value.value;
|
|
5412
|
+
const value = node.value?.value;
|
|
5005
5413
|
const selector = node.backendDOMNodeId ? `[data-backend-node-id="${node.backendDOMNodeId}"]` : `[aria-label="${name}"]`;
|
|
5006
5414
|
interactiveElements.push({
|
|
5007
5415
|
ref,
|
|
5008
5416
|
role,
|
|
5009
5417
|
name,
|
|
5010
5418
|
selector,
|
|
5011
|
-
disabled
|
|
5419
|
+
disabled,
|
|
5420
|
+
checked,
|
|
5421
|
+
value: value !== void 0 ? String(value) : void 0
|
|
5012
5422
|
});
|
|
5013
5423
|
}
|
|
5014
5424
|
}
|
|
5425
|
+
const formatNode = (node, depth = 0) => {
|
|
5426
|
+
let line = `${" ".repeat(depth)}- ${node.role}`;
|
|
5427
|
+
if (node.name) line += ` "${node.name}"`;
|
|
5428
|
+
line += ` ref:${node.ref}`;
|
|
5429
|
+
if (node.disabled) line += " (disabled)";
|
|
5430
|
+
if (node.checked !== void 0) line += node.checked ? " (checked)" : " (unchecked)";
|
|
5431
|
+
return line;
|
|
5432
|
+
};
|
|
5015
5433
|
const formatTree = (nodes2, depth = 0) => {
|
|
5016
5434
|
const lines = [];
|
|
5017
5435
|
for (const node of nodes2) {
|
|
5018
|
-
|
|
5019
|
-
if (node.name) line += ` "${node.name}"`;
|
|
5020
|
-
line += ` [ref=${node.ref}]`;
|
|
5021
|
-
if (node.disabled) line += " (disabled)";
|
|
5022
|
-
if (node.checked !== void 0) line += node.checked ? " (checked)" : " (unchecked)";
|
|
5023
|
-
lines.push(line);
|
|
5436
|
+
lines.push(formatNode(node, depth));
|
|
5024
5437
|
if (node.children) {
|
|
5025
5438
|
lines.push(formatTree(node.children, depth + 1));
|
|
5026
5439
|
}
|
|
5027
5440
|
}
|
|
5028
5441
|
return lines.join("\n");
|
|
5029
5442
|
};
|
|
5030
|
-
const text = formatTree(accessibilityTree);
|
|
5443
|
+
const text = roleFilter.size > 0 ? accessibilityTree.map((node) => formatNode(node)).join("\n") : formatTree(accessibilityTree);
|
|
5031
5444
|
const result = {
|
|
5032
5445
|
url,
|
|
5033
5446
|
title,
|
|
@@ -5036,7 +5449,9 @@ var Page = class {
|
|
|
5036
5449
|
interactiveElements,
|
|
5037
5450
|
text
|
|
5038
5451
|
};
|
|
5039
|
-
|
|
5452
|
+
if (roleFilter.size === 0) {
|
|
5453
|
+
this.lastSnapshot = result;
|
|
5454
|
+
}
|
|
5040
5455
|
return result;
|
|
5041
5456
|
}
|
|
5042
5457
|
/**
|
|
@@ -5591,7 +6006,7 @@ var Page = class {
|
|
|
5591
6006
|
}
|
|
5592
6007
|
/**
|
|
5593
6008
|
* Find an element using single or multiple selectors
|
|
5594
|
-
* Supports ref
|
|
6009
|
+
* Supports ref:, text:, and role: selectors.
|
|
5595
6010
|
*/
|
|
5596
6011
|
async findElement(selectors, options = {}) {
|
|
5597
6012
|
const { timeout = DEFAULT_TIMEOUT2 } = options;
|
|
@@ -5658,11 +6073,11 @@ var Page = class {
|
|
|
5658
6073
|
}
|
|
5659
6074
|
}
|
|
5660
6075
|
}
|
|
5661
|
-
const
|
|
5662
|
-
if (
|
|
6076
|
+
const runtimeSelectors = selectorList.filter((s) => !s.startsWith("ref:"));
|
|
6077
|
+
if (runtimeSelectors.length === 0) {
|
|
5663
6078
|
return null;
|
|
5664
6079
|
}
|
|
5665
|
-
const result = await waitForAnyElement(this.cdp,
|
|
6080
|
+
const result = await waitForAnyElement(this.cdp, runtimeSelectors, {
|
|
5666
6081
|
state: "visible",
|
|
5667
6082
|
timeout,
|
|
5668
6083
|
contextId: this.currentFrameContextId ?? void 0
|
|
@@ -5670,6 +6085,14 @@ var Page = class {
|
|
|
5670
6085
|
if (!result.success || !result.selector) {
|
|
5671
6086
|
return null;
|
|
5672
6087
|
}
|
|
6088
|
+
const specialSelectorMatch = await this.resolveSpecialSelector(result.selector);
|
|
6089
|
+
if (specialSelectorMatch) {
|
|
6090
|
+
this._lastMatchedSelector = result.selector;
|
|
6091
|
+
return {
|
|
6092
|
+
...specialSelectorMatch,
|
|
6093
|
+
waitedMs: result.waitedMs
|
|
6094
|
+
};
|
|
6095
|
+
}
|
|
5673
6096
|
await this.ensureRootNode();
|
|
5674
6097
|
const queryResult = await this.cdp.send("DOM.querySelector", {
|
|
5675
6098
|
nodeId: this.rootNodeId,
|
|
@@ -5716,6 +6139,122 @@ var Page = class {
|
|
|
5716
6139
|
waitedMs: result.waitedMs
|
|
5717
6140
|
};
|
|
5718
6141
|
}
|
|
6142
|
+
formatEvaluationError(details) {
|
|
6143
|
+
const description = typeof details.exception?.description === "string" && details.exception.description || typeof details.exception?.value === "string" && details.exception.value || details.text || "Uncaught";
|
|
6144
|
+
return `Evaluation failed: ${description}`;
|
|
6145
|
+
}
|
|
6146
|
+
async resolveSpecialSelector(selector, options = {}) {
|
|
6147
|
+
const expression = buildSpecialSelectorLookupExpression(selector, options);
|
|
6148
|
+
if (!expression) return null;
|
|
6149
|
+
const result = await this.evaluateInFrame(expression, {
|
|
6150
|
+
returnByValue: false
|
|
6151
|
+
});
|
|
6152
|
+
if (!result.result.objectId) {
|
|
6153
|
+
return null;
|
|
6154
|
+
}
|
|
6155
|
+
const resolved = await this.objectIdToNode(result.result.objectId);
|
|
6156
|
+
if (!resolved) {
|
|
6157
|
+
return null;
|
|
6158
|
+
}
|
|
6159
|
+
return {
|
|
6160
|
+
nodeId: resolved.nodeId,
|
|
6161
|
+
backendNodeId: resolved.backendNodeId,
|
|
6162
|
+
selector,
|
|
6163
|
+
waitedMs: 0
|
|
6164
|
+
};
|
|
6165
|
+
}
|
|
6166
|
+
async readCheckedState(objectId) {
|
|
6167
|
+
const result = await this.cdp.send("Runtime.callFunctionOn", {
|
|
6168
|
+
objectId,
|
|
6169
|
+
functionDeclaration: "function() { return !!this.checked; }",
|
|
6170
|
+
returnByValue: true
|
|
6171
|
+
});
|
|
6172
|
+
return result.result.value === true;
|
|
6173
|
+
}
|
|
6174
|
+
async readInputType(objectId) {
|
|
6175
|
+
const result = await this.cdp.send(
|
|
6176
|
+
"Runtime.callFunctionOn",
|
|
6177
|
+
{
|
|
6178
|
+
objectId,
|
|
6179
|
+
functionDeclaration: 'function() { return this instanceof HTMLInputElement ? String(this.type || "").toLowerCase() : null; }',
|
|
6180
|
+
returnByValue: true
|
|
6181
|
+
}
|
|
6182
|
+
);
|
|
6183
|
+
return result.result.value ?? null;
|
|
6184
|
+
}
|
|
6185
|
+
async getAssociatedLabelNodeId(objectId) {
|
|
6186
|
+
const result = await this.cdp.send("Runtime.callFunctionOn", {
|
|
6187
|
+
objectId,
|
|
6188
|
+
functionDeclaration: `function() {
|
|
6189
|
+
if (!(this instanceof HTMLInputElement)) return null;
|
|
6190
|
+
|
|
6191
|
+
if (this.id) {
|
|
6192
|
+
var labels = Array.from(document.querySelectorAll('label'));
|
|
6193
|
+
for (var i = 0; i < labels.length; i++) {
|
|
6194
|
+
if (labels[i].htmlFor === this.id) return labels[i];
|
|
6195
|
+
}
|
|
6196
|
+
}
|
|
6197
|
+
|
|
6198
|
+
return this.closest('label');
|
|
6199
|
+
}`,
|
|
6200
|
+
returnByValue: false
|
|
6201
|
+
});
|
|
6202
|
+
if (!result.result.objectId) {
|
|
6203
|
+
return null;
|
|
6204
|
+
}
|
|
6205
|
+
return (await this.objectIdToNode(result.result.objectId))?.nodeId ?? null;
|
|
6206
|
+
}
|
|
6207
|
+
async objectIdToNode(objectId) {
|
|
6208
|
+
const describeResult = await this.cdp.send("DOM.describeNode", {
|
|
6209
|
+
objectId,
|
|
6210
|
+
depth: 0
|
|
6211
|
+
});
|
|
6212
|
+
const backendNodeId = describeResult.node.backendNodeId;
|
|
6213
|
+
if (!backendNodeId) {
|
|
6214
|
+
return null;
|
|
6215
|
+
}
|
|
6216
|
+
if (describeResult.node.nodeId) {
|
|
6217
|
+
return {
|
|
6218
|
+
nodeId: describeResult.node.nodeId,
|
|
6219
|
+
backendNodeId
|
|
6220
|
+
};
|
|
6221
|
+
}
|
|
6222
|
+
await this.ensureRootNode();
|
|
6223
|
+
const pushResult = await this.cdp.send(
|
|
6224
|
+
"DOM.pushNodesByBackendIdsToFrontend",
|
|
6225
|
+
{
|
|
6226
|
+
backendNodeIds: [backendNodeId]
|
|
6227
|
+
}
|
|
6228
|
+
);
|
|
6229
|
+
const nodeId = pushResult.nodeIds?.[0];
|
|
6230
|
+
if (!nodeId) {
|
|
6231
|
+
return null;
|
|
6232
|
+
}
|
|
6233
|
+
return { nodeId, backendNodeId };
|
|
6234
|
+
}
|
|
6235
|
+
async tryClickAssociatedLabel(objectId) {
|
|
6236
|
+
const inputType = await this.readInputType(objectId);
|
|
6237
|
+
if (inputType !== "checkbox" && inputType !== "radio") {
|
|
6238
|
+
return false;
|
|
6239
|
+
}
|
|
6240
|
+
const labelNodeId = await this.getAssociatedLabelNodeId(objectId);
|
|
6241
|
+
if (!labelNodeId) {
|
|
6242
|
+
return false;
|
|
6243
|
+
}
|
|
6244
|
+
try {
|
|
6245
|
+
await this.scrollIntoView(labelNodeId);
|
|
6246
|
+
await this.clickElement(labelNodeId);
|
|
6247
|
+
return true;
|
|
6248
|
+
} catch {
|
|
6249
|
+
return false;
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
async tryToggleViaLabel(objectId, desiredChecked) {
|
|
6253
|
+
if (!await this.tryClickAssociatedLabel(objectId)) {
|
|
6254
|
+
return false;
|
|
6255
|
+
}
|
|
6256
|
+
return await this.readCheckedState(objectId) === desiredChecked;
|
|
6257
|
+
}
|
|
5719
6258
|
/**
|
|
5720
6259
|
* Ensure we have a valid root node ID
|
|
5721
6260
|
*/
|
|
@@ -6133,6 +6672,7 @@ var Browser = class _Browser {
|
|
|
6133
6672
|
cdp;
|
|
6134
6673
|
providerSession;
|
|
6135
6674
|
pages = /* @__PURE__ */ new Map();
|
|
6675
|
+
pageCounter = 0;
|
|
6136
6676
|
constructor(cdp, _provider, providerSession, _options) {
|
|
6137
6677
|
this.cdp = cdp;
|
|
6138
6678
|
this.providerSession = providerSession;
|
|
@@ -6162,7 +6702,11 @@ var Browser = class _Browser {
|
|
|
6162
6702
|
const pageName = name ?? "default";
|
|
6163
6703
|
const cached = this.pages.get(pageName);
|
|
6164
6704
|
if (cached) return cached;
|
|
6165
|
-
const targets = await this.cdp.send(
|
|
6705
|
+
const targets = await this.cdp.send(
|
|
6706
|
+
"Target.getTargets",
|
|
6707
|
+
void 0,
|
|
6708
|
+
null
|
|
6709
|
+
);
|
|
6166
6710
|
let pageTargets = targets.targetInfos.filter((t) => t.type === "page");
|
|
6167
6711
|
if (options?.targetUrl) {
|
|
6168
6712
|
const urlFilter = options.targetUrl;
|
|
@@ -6184,16 +6728,24 @@ var Browser = class _Browser {
|
|
|
6184
6728
|
targetId = options.targetId;
|
|
6185
6729
|
} else {
|
|
6186
6730
|
console.warn(`[browser-pilot] Target ${options.targetId} no longer exists, falling back`);
|
|
6187
|
-
targetId = pickBestTarget(pageTargets) ?? (await this.cdp.send(
|
|
6188
|
-
|
|
6189
|
-
|
|
6731
|
+
targetId = pickBestTarget(pageTargets) ?? (await this.cdp.send(
|
|
6732
|
+
"Target.createTarget",
|
|
6733
|
+
{
|
|
6734
|
+
url: "about:blank"
|
|
6735
|
+
},
|
|
6736
|
+
null
|
|
6737
|
+
)).targetId;
|
|
6190
6738
|
}
|
|
6191
6739
|
} else if (pageTargets.length > 0) {
|
|
6192
6740
|
targetId = pickBestTarget(pageTargets);
|
|
6193
6741
|
} else {
|
|
6194
|
-
const result = await this.cdp.send(
|
|
6195
|
-
|
|
6196
|
-
|
|
6742
|
+
const result = await this.cdp.send(
|
|
6743
|
+
"Target.createTarget",
|
|
6744
|
+
{
|
|
6745
|
+
url: "about:blank"
|
|
6746
|
+
},
|
|
6747
|
+
null
|
|
6748
|
+
);
|
|
6197
6749
|
targetId = result.targetId;
|
|
6198
6750
|
}
|
|
6199
6751
|
await this.cdp.attachToTarget(targetId);
|
|
@@ -6221,13 +6773,17 @@ var Browser = class _Browser {
|
|
|
6221
6773
|
* Create a new page (tab)
|
|
6222
6774
|
*/
|
|
6223
6775
|
async newPage(url = "about:blank") {
|
|
6224
|
-
const result = await this.cdp.send(
|
|
6225
|
-
|
|
6226
|
-
|
|
6776
|
+
const result = await this.cdp.send(
|
|
6777
|
+
"Target.createTarget",
|
|
6778
|
+
{
|
|
6779
|
+
url
|
|
6780
|
+
},
|
|
6781
|
+
null
|
|
6782
|
+
);
|
|
6227
6783
|
await this.cdp.attachToTarget(result.targetId);
|
|
6228
6784
|
const page = new Page(this.cdp, result.targetId);
|
|
6229
6785
|
await page.init();
|
|
6230
|
-
const name = `page-${this.
|
|
6786
|
+
const name = `page-${++this.pageCounter}`;
|
|
6231
6787
|
this.pages.set(name, page);
|
|
6232
6788
|
return page;
|
|
6233
6789
|
}
|
|
@@ -6237,14 +6793,30 @@ var Browser = class _Browser {
|
|
|
6237
6793
|
async closePage(name) {
|
|
6238
6794
|
const page = this.pages.get(name);
|
|
6239
6795
|
if (!page) return;
|
|
6240
|
-
const
|
|
6241
|
-
|
|
6242
|
-
if (pageTargets.length > 0) {
|
|
6243
|
-
await this.cdp.send("Target.closeTarget", {
|
|
6244
|
-
targetId: pageTargets[0].targetId
|
|
6245
|
-
});
|
|
6246
|
-
}
|
|
6796
|
+
const targetId = page.targetId;
|
|
6797
|
+
await this.cdp.send("Target.closeTarget", { targetId }, null);
|
|
6247
6798
|
this.pages.delete(name);
|
|
6799
|
+
const deadline = Date.now() + 5e3;
|
|
6800
|
+
while (Date.now() < deadline) {
|
|
6801
|
+
const { targetInfos } = await this.cdp.send(
|
|
6802
|
+
"Target.getTargets",
|
|
6803
|
+
void 0,
|
|
6804
|
+
null
|
|
6805
|
+
);
|
|
6806
|
+
if (!targetInfos.some((t) => t.targetId === targetId)) return;
|
|
6807
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
6808
|
+
}
|
|
6809
|
+
}
|
|
6810
|
+
/**
|
|
6811
|
+
* List all page targets in the connected browser.
|
|
6812
|
+
*/
|
|
6813
|
+
async listTargets() {
|
|
6814
|
+
const { targetInfos } = await this.cdp.send(
|
|
6815
|
+
"Target.getTargets",
|
|
6816
|
+
void 0,
|
|
6817
|
+
null
|
|
6818
|
+
);
|
|
6819
|
+
return targetInfos.filter((target) => target.type === "page");
|
|
6248
6820
|
}
|
|
6249
6821
|
/**
|
|
6250
6822
|
* Get the WebSocket URL for this browser connection
|