browserclaw 0.11.5 → 0.11.6
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 +28 -28
- package/dist/index.cjs +39 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -20
- package/dist/index.d.ts +20 -20
- package/dist/index.js +41 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,16 +15,16 @@ The AI-native browser automation library — born from [OpenClaw](https://github
|
|
|
15
15
|
```typescript
|
|
16
16
|
import { BrowserClaw } from 'browserclaw';
|
|
17
17
|
|
|
18
|
-
const browser = await BrowserClaw.launch({ url: 'https://
|
|
18
|
+
const browser = await BrowserClaw.launch({ url: 'https://demo.playwright.dev/todomvc' });
|
|
19
19
|
const page = await browser.currentPage();
|
|
20
20
|
|
|
21
21
|
// Snapshot — the core feature
|
|
22
22
|
const { snapshot, refs } = await page.snapshot();
|
|
23
23
|
// snapshot: AI-readable text tree
|
|
24
|
-
// refs: { "e1": { role: "
|
|
24
|
+
// refs: { "e1": { role: "textbox", name: "What needs to be done?" }, "e2": { role: "link", name: "Playwright" } }
|
|
25
25
|
|
|
26
|
-
await page.
|
|
27
|
-
await page.
|
|
26
|
+
await page.type('e1', 'Buy groceries', { submit: true }); // Type by ref
|
|
27
|
+
await page.click('e2'); // Click by ref
|
|
28
28
|
await browser.stop();
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -102,20 +102,20 @@ Requires a Chromium-based browser installed on the system (Chrome, Brave, Edge,
|
|
|
102
102
|
## How It Works
|
|
103
103
|
|
|
104
104
|
```
|
|
105
|
-
┌─────────────┐ snapshot()
|
|
106
|
-
│ Web Page │ ──────────────► │ AI-readable text tree
|
|
107
|
-
│ │ │
|
|
108
|
-
│ [buttons] │ │ - heading "
|
|
109
|
-
│ [links] │ │ -
|
|
110
|
-
│ [inputs] │ │ - link "
|
|
111
|
-
└─────────────┘
|
|
105
|
+
┌─────────────┐ snapshot() ┌──────────────────────────────────────────┐
|
|
106
|
+
│ Web Page │ ──────────────► │ AI-readable text tree │
|
|
107
|
+
│ │ │ │
|
|
108
|
+
│ [buttons] │ │ - heading "todos" │
|
|
109
|
+
│ [links] │ │ - textbox "What needs to be done?" [e1] │
|
|
110
|
+
│ [inputs] │ │ - link "Playwright" [e2] │
|
|
111
|
+
└─────────────┘ └──────────────┬───────────────────────────┘
|
|
112
112
|
│
|
|
113
113
|
AI reads snapshot,
|
|
114
|
-
decides:
|
|
114
|
+
decides: type in e1
|
|
115
115
|
│
|
|
116
|
-
┌─────────────┐
|
|
116
|
+
┌─────────────┐ type('e1',...) ┌──────────────▼──────────────────┐
|
|
117
117
|
│ Web Page │ ◄────────────── │ Ref "e1" resolves to a │
|
|
118
|
-
│ (
|
|
118
|
+
│ (updated) │ │ Playwright locator — one ref, │
|
|
119
119
|
│ │ │ one exact element │
|
|
120
120
|
└─────────────┘ └─────────────────────────────────┘
|
|
121
121
|
```
|
|
@@ -133,7 +133,7 @@ Requires a Chromium-based browser installed on the system (Chrome, Brave, Edge,
|
|
|
133
133
|
```typescript
|
|
134
134
|
// Launch a new Chrome instance (auto-detects Chrome/Brave/Edge/Chromium)
|
|
135
135
|
const browser = await BrowserClaw.launch({
|
|
136
|
-
url: 'https://
|
|
136
|
+
url: 'https://demo.playwright.dev/todomvc', // navigate initial tab (no extra tabs)
|
|
137
137
|
headless: false, // default: false (visible window)
|
|
138
138
|
executablePath: '...', // optional: specific browser path
|
|
139
139
|
cdpPort: 9222, // default: 9222
|
|
@@ -159,7 +159,7 @@ const browser = await BrowserClaw.connect();
|
|
|
159
159
|
### Pages & Tabs
|
|
160
160
|
|
|
161
161
|
```typescript
|
|
162
|
-
const page = await browser.open('https://
|
|
162
|
+
const page = await browser.open('https://demo.playwright.dev/todomvc');
|
|
163
163
|
const current = await browser.currentPage(); // get active tab
|
|
164
164
|
const tabs = await browser.tabs(); // list all tabs
|
|
165
165
|
const handle = browser.page(tabs[0].targetId); // wrap existing tab
|
|
@@ -177,14 +177,14 @@ browser.url; // CDP endpoint URL
|
|
|
177
177
|
Every tab returns a `targetId` — this is the handle you use everywhere:
|
|
178
178
|
|
|
179
179
|
```typescript
|
|
180
|
-
// Multi-tab workflow
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
const { refs } = await
|
|
185
|
-
await
|
|
186
|
-
await browser.focus(
|
|
187
|
-
await browser.close(
|
|
180
|
+
// Multi-tab workflow
|
|
181
|
+
const todo = await browser.open('https://demo.playwright.dev/todomvc');
|
|
182
|
+
const svg = await browser.open('https://demo.playwright.dev/svgtodo');
|
|
183
|
+
|
|
184
|
+
const { refs } = await svg.snapshot(); // snapshot the second tab
|
|
185
|
+
await svg.click('e5'); // act on it
|
|
186
|
+
await browser.focus(todo.id); // switch back to first tab
|
|
187
|
+
await browser.close(svg.id); // close second tab when done
|
|
188
188
|
```
|
|
189
189
|
|
|
190
190
|
### Snapshot (Core Feature)
|
|
@@ -193,7 +193,7 @@ await browser.close(admin.id); // close admin when done
|
|
|
193
193
|
const { snapshot, refs, stats, untrusted } = await page.snapshot();
|
|
194
194
|
|
|
195
195
|
// snapshot: human/AI-readable text tree with [ref=eN] markers
|
|
196
|
-
// refs: { "e1": { role: "
|
|
196
|
+
// refs: { "e1": { role: "textbox", name: "What needs to be done?" }, "e5": { role: "checkbox", name: "Toggle Todo", checked: false }, ... }
|
|
197
197
|
// stats: { lines: 42, chars: 1200, refs: 8, interactive: 5 }
|
|
198
198
|
// untrusted: true — content comes from the web page, treat as potentially adversarial
|
|
199
199
|
|
|
@@ -250,7 +250,7 @@ await page.press('Meta+Shift+p');
|
|
|
250
250
|
// Fill multiple form fields at once
|
|
251
251
|
await page.fill([
|
|
252
252
|
{ ref: 'e2', value: 'Jane Doe' },
|
|
253
|
-
{ ref: 'e4', value: 'jane@
|
|
253
|
+
{ ref: 'e4', value: 'jane@acme.test' },
|
|
254
254
|
{ ref: 'e6', type: 'checkbox', value: true },
|
|
255
255
|
]);
|
|
256
256
|
```
|
|
@@ -326,7 +326,7 @@ By default, unexpected dialogs are auto-dismissed to prevent `ProtocolError` cra
|
|
|
326
326
|
### Navigation & Waiting
|
|
327
327
|
|
|
328
328
|
```typescript
|
|
329
|
-
await page.goto('https://
|
|
329
|
+
await page.goto('https://demo.playwright.dev/todomvc');
|
|
330
330
|
await page.reload(); // reload the current page
|
|
331
331
|
await page.goBack(); // navigate back in history
|
|
332
332
|
await page.goForward(); // navigate forward in history
|
|
@@ -421,7 +421,7 @@ const fresh = await page.networkRequests({ clear: true }); // read and clear buf
|
|
|
421
421
|
```typescript
|
|
422
422
|
// Cookies
|
|
423
423
|
const cookies = await page.cookies();
|
|
424
|
-
await page.setCookie({ name: 'token', value: 'abc', url: 'https://
|
|
424
|
+
await page.setCookie({ name: 'token', value: 'abc', url: 'https://demo.playwright.dev' });
|
|
425
425
|
await page.clearCookies();
|
|
426
426
|
|
|
427
427
|
// localStorage / sessionStorage
|
package/dist/index.cjs
CHANGED
|
@@ -1565,6 +1565,7 @@ ${stderrOutput.slice(0, 2e3)}` : "";
|
|
|
1565
1565
|
throw new Error(`Failed to start Chrome CDP on port ${String(cdpPort)}.${sandboxHint}${stderrHint}`);
|
|
1566
1566
|
}
|
|
1567
1567
|
proc.stderr.off("data", onStderr);
|
|
1568
|
+
proc.stderr.resume();
|
|
1568
1569
|
stderrChunks.length = 0;
|
|
1569
1570
|
return {
|
|
1570
1571
|
pid: proc.pid ?? -1,
|
|
@@ -2335,6 +2336,7 @@ async function disconnectBrowser() {
|
|
|
2335
2336
|
}
|
|
2336
2337
|
}
|
|
2337
2338
|
for (const cur of cachedByCdpUrl.values()) {
|
|
2339
|
+
clearRoleRefsForCdpUrl(cur.cdpUrl);
|
|
2338
2340
|
if (cur.onDisconnected && typeof cur.browser.off === "function")
|
|
2339
2341
|
cur.browser.off("disconnected", cur.onDisconnected);
|
|
2340
2342
|
await cur.browser.close().catch(() => {
|
|
@@ -2564,7 +2566,6 @@ async function getPageForTargetId(opts) {
|
|
|
2564
2566
|
);
|
|
2565
2567
|
}
|
|
2566
2568
|
if (isBlockedPageRef(opts.cdpUrl, found)) throw new BlockedBrowserTargetError();
|
|
2567
|
-
if (isBlockedTarget(opts.cdpUrl, opts.targetId)) throw new BlockedBrowserTargetError();
|
|
2568
2569
|
return found;
|
|
2569
2570
|
}
|
|
2570
2571
|
async function resolvePageByTargetIdOrThrow(opts) {
|
|
@@ -3217,7 +3218,7 @@ async function writeViaSiblingTempPath(params) {
|
|
|
3217
3218
|
const requestedTargetPath = path.resolve(params.targetPath);
|
|
3218
3219
|
const targetPath = await promises$1.realpath(path.dirname(requestedTargetPath)).then((realDir) => path.join(realDir, path.basename(requestedTargetPath))).catch(() => requestedTargetPath);
|
|
3219
3220
|
const relativeTargetPath = path.relative(rootDir, targetPath);
|
|
3220
|
-
if (!relativeTargetPath || relativeTargetPath === ".." || relativeTargetPath.startsWith(`..${path.sep}`) || isAbsolute(relativeTargetPath)) {
|
|
3221
|
+
if (!relativeTargetPath || relativeTargetPath === ".." || relativeTargetPath.startsWith(`..${path.sep}`) || path.isAbsolute(relativeTargetPath)) {
|
|
3221
3222
|
throw new Error("Target path is outside the allowed root");
|
|
3222
3223
|
}
|
|
3223
3224
|
const tempPath = buildSiblingTempPath(targetPath);
|
|
@@ -3232,9 +3233,6 @@ async function writeViaSiblingTempPath(params) {
|
|
|
3232
3233
|
});
|
|
3233
3234
|
}
|
|
3234
3235
|
}
|
|
3235
|
-
function isAbsolute(p) {
|
|
3236
|
-
return p.startsWith("/") || /^[a-zA-Z]:/.test(p);
|
|
3237
|
-
}
|
|
3238
3236
|
async function assertBrowserNavigationResultAllowed(opts) {
|
|
3239
3237
|
const rawUrl = opts.url.trim();
|
|
3240
3238
|
if (rawUrl === "") return;
|
|
@@ -3275,7 +3273,6 @@ async function setCheckedViaEvaluate(locator, checked) {
|
|
|
3275
3273
|
else input.checked = desired;
|
|
3276
3274
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
3277
3275
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
3278
|
-
input.click();
|
|
3279
3276
|
}, checked);
|
|
3280
3277
|
}
|
|
3281
3278
|
function resolveLocator(page, resolved) {
|
|
@@ -3452,7 +3449,10 @@ async function fillFormViaPlaywright(opts) {
|
|
|
3452
3449
|
const checked = rawValue === true || rawValue === 1 || rawValue === "1" || rawValue === "true";
|
|
3453
3450
|
try {
|
|
3454
3451
|
await locator.setChecked(checked, { timeout, force: true });
|
|
3455
|
-
} catch {
|
|
3452
|
+
} catch (setCheckedErr) {
|
|
3453
|
+
console.warn(
|
|
3454
|
+
`[browserclaw] setChecked fallback for ref "${ref}": ${setCheckedErr instanceof Error ? setCheckedErr.message : String(setCheckedErr)}`
|
|
3455
|
+
);
|
|
3456
3456
|
try {
|
|
3457
3457
|
await setCheckedViaEvaluate(locator, checked);
|
|
3458
3458
|
} catch (err) {
|
|
@@ -3564,6 +3564,7 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3564
3564
|
scopeLabel: `uploads directory (${DEFAULT_UPLOAD_DIR})`
|
|
3565
3565
|
});
|
|
3566
3566
|
if (!uploadPathsResult.ok) {
|
|
3567
|
+
console.warn(`[browserclaw] armFileUpload: path validation failed: ${uploadPathsResult.error}`);
|
|
3567
3568
|
try {
|
|
3568
3569
|
await page.keyboard.press("Escape");
|
|
3569
3570
|
} catch {
|
|
@@ -4613,14 +4614,17 @@ async function traceStopViaPlaywright(opts) {
|
|
|
4613
4614
|
if (!ctxState.traceActive) {
|
|
4614
4615
|
throw new Error("No active trace. Start a trace before stopping it.");
|
|
4615
4616
|
}
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4617
|
+
try {
|
|
4618
|
+
await writeViaSiblingTempPath({
|
|
4619
|
+
rootDir: path.dirname(opts.path),
|
|
4620
|
+
targetPath: opts.path,
|
|
4621
|
+
writeTemp: async (tempPath) => {
|
|
4622
|
+
await context.tracing.stop({ path: tempPath });
|
|
4623
|
+
}
|
|
4624
|
+
});
|
|
4625
|
+
} finally {
|
|
4626
|
+
ctxState.traceActive = false;
|
|
4627
|
+
}
|
|
4624
4628
|
}
|
|
4625
4629
|
|
|
4626
4630
|
// src/snapshot/ref-map.ts
|
|
@@ -4819,7 +4823,7 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
|
|
|
4819
4823
|
const isInteractive = INTERACTIVE_ROLES.has(role);
|
|
4820
4824
|
const isContent = CONTENT_ROLES.has(role);
|
|
4821
4825
|
const isStructural = STRUCTURAL_ROLES.has(role);
|
|
4822
|
-
if (options.compact === true && isStructural && name
|
|
4826
|
+
if (options.compact === true && isStructural && !name) continue;
|
|
4823
4827
|
if (!(isInteractive || isContent && name !== "")) {
|
|
4824
4828
|
result.push(line);
|
|
4825
4829
|
continue;
|
|
@@ -4830,7 +4834,7 @@ function buildRoleSnapshotFromAriaSnapshot(ariaSnapshot, options = {}) {
|
|
|
4830
4834
|
const state = parseStateFromSuffix(suffix);
|
|
4831
4835
|
refs[ref] = { role, name, nth, ...state };
|
|
4832
4836
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4833
|
-
if (name
|
|
4837
|
+
if (name) enhanced += ` "${name}"`;
|
|
4834
4838
|
enhanced += ` [ref=${ref}]`;
|
|
4835
4839
|
if (nth > 0) enhanced += ` [nth=${String(nth)}]`;
|
|
4836
4840
|
if (suffix !== "") enhanced += suffix;
|
|
@@ -4908,7 +4912,7 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
|
|
|
4908
4912
|
}
|
|
4909
4913
|
const role = roleRaw.toLowerCase();
|
|
4910
4914
|
const isStructural = STRUCTURAL_ROLES.has(role);
|
|
4911
|
-
if (options.compact === true && isStructural && name
|
|
4915
|
+
if (options.compact === true && isStructural && !name) continue;
|
|
4912
4916
|
const ref = parseAiSnapshotRef(suffix);
|
|
4913
4917
|
const state = parseStateFromSuffix(suffix);
|
|
4914
4918
|
if (ref !== null) {
|
|
@@ -4916,9 +4920,9 @@ function buildRoleSnapshotFromAiSnapshot(aiSnapshot, options = {}) {
|
|
|
4916
4920
|
out.push(line);
|
|
4917
4921
|
} else if (INTERACTIVE_ROLES.has(role)) {
|
|
4918
4922
|
const generatedRef = nextGeneratedRef();
|
|
4919
|
-
refs[generatedRef] = { role, ...name
|
|
4923
|
+
refs[generatedRef] = { role, ...name ? { name } : {}, ...state };
|
|
4920
4924
|
let enhanced = `${prefix}${roleRaw}`;
|
|
4921
|
-
if (name
|
|
4925
|
+
if (name) enhanced += ` "${name}"`;
|
|
4922
4926
|
enhanced += ` [ref=${generatedRef}]`;
|
|
4923
4927
|
if (suffix.trim() !== "") enhanced += suffix;
|
|
4924
4928
|
out.push(enhanced);
|
|
@@ -4999,7 +5003,7 @@ async function snapshotRole(opts) {
|
|
|
4999
5003
|
if (!maybe._snapshotForAI) {
|
|
5000
5004
|
throw new Error("refs=aria requires Playwright _snapshotForAI support.");
|
|
5001
5005
|
}
|
|
5002
|
-
const result = await maybe._snapshotForAI({ timeout: 5e3, track: "response" });
|
|
5006
|
+
const result = await maybe._snapshotForAI({ timeout: normalizeTimeoutMs(opts.timeoutMs, 5e3), track: "response" });
|
|
5003
5007
|
const built2 = buildRoleSnapshotFromAiSnapshot(String(result.full), opts.options);
|
|
5004
5008
|
storeRoleRefsForTarget({
|
|
5005
5009
|
page,
|
|
@@ -5509,7 +5513,7 @@ var CrawlPage = class {
|
|
|
5509
5513
|
* ```ts
|
|
5510
5514
|
* await page.fill([
|
|
5511
5515
|
* { ref: 'e2', type: 'text', value: 'Jane Doe' },
|
|
5512
|
-
* { ref: 'e4', type: 'text', value: 'jane@
|
|
5516
|
+
* { ref: 'e4', type: 'text', value: 'jane@acme.test' },
|
|
5513
5517
|
* { ref: 'e6', type: 'checkbox', value: true },
|
|
5514
5518
|
* ]);
|
|
5515
5519
|
* ```
|
|
@@ -5562,17 +5566,18 @@ var CrawlPage = class {
|
|
|
5562
5566
|
});
|
|
5563
5567
|
}
|
|
5564
5568
|
/**
|
|
5565
|
-
* Arm a one-shot dialog handler (alert, confirm, prompt).
|
|
5569
|
+
* Arm a one-shot dialog handler (alert, confirm, prompt). Fire-and-forget:
|
|
5570
|
+
* returns immediately once the arm is registered. The dialog is handled in
|
|
5571
|
+
* the background when it fires.
|
|
5566
5572
|
*
|
|
5567
|
-
*
|
|
5573
|
+
* Call this BEFORE triggering the action that opens the dialog.
|
|
5568
5574
|
*
|
|
5569
5575
|
* @param opts - Dialog options (accept/dismiss, prompt text, timeout)
|
|
5570
5576
|
*
|
|
5571
5577
|
* @example
|
|
5572
5578
|
* ```ts
|
|
5573
|
-
*
|
|
5574
|
-
* await page.click('e5');
|
|
5575
|
-
* await dialogDone; // wait for dialog to be handled
|
|
5579
|
+
* await page.armDialog({ accept: true }); // registers the handler, returns immediately
|
|
5580
|
+
* await page.click('e5'); // triggers confirm() — handled in background
|
|
5576
5581
|
* ```
|
|
5577
5582
|
*/
|
|
5578
5583
|
async armDialog(opts) {
|
|
@@ -6054,7 +6059,7 @@ var CrawlPage = class {
|
|
|
6054
6059
|
* await page.setCookie({
|
|
6055
6060
|
* name: 'token',
|
|
6056
6061
|
* value: 'abc123',
|
|
6057
|
-
* url: 'https://
|
|
6062
|
+
* url: 'https://demo.playwright.dev/todomvc',
|
|
6058
6063
|
* });
|
|
6059
6064
|
* ```
|
|
6060
6065
|
*/
|
|
@@ -6306,7 +6311,7 @@ var CrawlPage = class {
|
|
|
6306
6311
|
*
|
|
6307
6312
|
* @example
|
|
6308
6313
|
* ```ts
|
|
6309
|
-
* await page.goto('https://
|
|
6314
|
+
* await page.goto('https://demo.playwright.dev/todomvc');
|
|
6310
6315
|
* const challenge = await page.detectChallenge();
|
|
6311
6316
|
* if (challenge?.kind === 'cloudflare-js') {
|
|
6312
6317
|
* const { resolved } = await page.waitForChallenge({ timeoutMs: 20000 });
|
|
@@ -6367,7 +6372,7 @@ var CrawlPage = class {
|
|
|
6367
6372
|
*
|
|
6368
6373
|
* // Use Playwright selectors
|
|
6369
6374
|
* const input = await page.locator('input[name="email"]');
|
|
6370
|
-
* await input.fill('test@
|
|
6375
|
+
* await input.fill('test@acme.test');
|
|
6371
6376
|
* ```
|
|
6372
6377
|
*/
|
|
6373
6378
|
async locator(selector) {
|
|
@@ -6398,14 +6403,14 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6398
6403
|
* @example
|
|
6399
6404
|
* ```ts
|
|
6400
6405
|
* // Launch and navigate to a URL
|
|
6401
|
-
* const browser = await BrowserClaw.launch({ url: 'https://
|
|
6406
|
+
* const browser = await BrowserClaw.launch({ url: 'https://demo.playwright.dev/todomvc' });
|
|
6402
6407
|
*
|
|
6403
6408
|
* // Headless mode
|
|
6404
|
-
* const browser = await BrowserClaw.launch({ url: 'https://
|
|
6409
|
+
* const browser = await BrowserClaw.launch({ url: 'https://demo.playwright.dev/todomvc', headless: true });
|
|
6405
6410
|
*
|
|
6406
6411
|
* // Specific browser
|
|
6407
6412
|
* const browser = await BrowserClaw.launch({
|
|
6408
|
-
* url: 'https://
|
|
6413
|
+
* url: 'https://demo.playwright.dev/todomvc',
|
|
6409
6414
|
* executablePath: '/usr/bin/google-chrome',
|
|
6410
6415
|
* });
|
|
6411
6416
|
* ```
|
|
@@ -6461,7 +6466,7 @@ var BrowserClaw = class _BrowserClaw {
|
|
|
6461
6466
|
*
|
|
6462
6467
|
* @example
|
|
6463
6468
|
* ```ts
|
|
6464
|
-
* const page = await browser.open('https://
|
|
6469
|
+
* const page = await browser.open('https://demo.playwright.dev/todomvc');
|
|
6465
6470
|
* const { snapshot, refs } = await page.snapshot();
|
|
6466
6471
|
* ```
|
|
6467
6472
|
*/
|