🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

browser-pilot-cli

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

browser-pilot-cli - npm Package Compare versions

Comparing version
0.1.1
to
0.1.2
+67
-0
dist/cli.js

@@ -811,2 +811,69 @@ #!/usr/bin/env node

}));
async function typeViaKeyboard(t, sid, text) {
for (const char of text) {
if (char === "\n") {
await dispatchKey(t, sid, "Enter");
} else if (char === " ") {
await dispatchKey(t, sid, "Tab");
} else if (char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126) {
const code = char === " " ? "Space" : `Key${char.toUpperCase()}`;
const keyCode = char.toUpperCase().charCodeAt(0);
await t.send("Input.dispatchKeyEvent", { type: "keyDown", key: char, code, windowsVirtualKeyCode: keyCode, text: char }, sid);
await t.send("Input.dispatchKeyEvent", { type: "keyUp", key: char, code, windowsVirtualKeyCode: keyCode }, sid);
} else {
await t.send("Input.insertText", { text: char }, sid);
}
}
}
program.command("keyboard <text>").description("Type text via keyboard events (for canvas editors like Google Docs)").option("-c, --clear", "select all + delete before typing").option("-s, --submit", "press Enter after typing").option("-d, --delay <ms>", "delay between keystrokes in ms").option("--click <selector>", "click element by CSS selector first to focus it").option("-l, --limit <n>", "max elements in snapshot", "50").addHelpText("after", `
Unlike 'type', this does not target a specific element. It sends real
keyboard events to whatever is currently focused \u2014 works with canvas-based
editors (Google Docs, Google Sheets, Figma) that don't expose DOM inputs.
Use --click to focus an element before typing (sends a real CDP mouse click).
Examples:
bp keyboard "hello world"
bp keyboard "new content" --clear
bp keyboard "search query" --submit
bp keyboard "Hello Docs!" --click ".kix-appview-editor"
bp keyboard "slow typing" --delay 50`).action(action(async (text, opts) => {
const limit = parseLimit(opts.limit);
await withPilot(async ({ transport, sessionId, state }) => {
if (opts.click) {
const { result } = await transport.send("Runtime.evaluate", {
expression: `JSON.stringify((function(){var el=document.querySelector(${JSON.stringify(opts.click)});if(!el)return null;el.scrollIntoView({block:'center'});var r=el.getBoundingClientRect();return{x:r.x+r.width/2,y:r.y+r.height/2}})())`,
returnByValue: true
}, sessionId);
const coords = result.value ? JSON.parse(result.value) : null;
if (!coords) throw new Error(`Element not found: ${opts.click}`);
await dispatchClick(transport, sessionId, coords.x, coords.y);
await new Promise((r) => setTimeout(r, 300));
}
if (opts.clear) {
await dispatchKey(transport, sessionId, "Meta+a");
await dispatchKey(transport, sessionId, "Delete");
}
if (opts.delay) {
const delay = parseInt(opts.delay, 10);
for (const char of text) {
if (char === "\n") {
await dispatchKey(transport, sessionId, "Enter");
} else if (char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126) {
const code = char === " " ? "Space" : `Key${char.toUpperCase()}`;
const keyCode = char.toUpperCase().charCodeAt(0);
await transport.send("Input.dispatchKeyEvent", { type: "keyDown", key: char, code, windowsVirtualKeyCode: keyCode, text: char }, sessionId);
await transport.send("Input.dispatchKeyEvent", { type: "keyUp", key: char, code, windowsVirtualKeyCode: keyCode }, sessionId);
} else {
await transport.send("Input.insertText", { text: char }, sessionId);
}
await new Promise((r) => setTimeout(r, delay));
}
} else {
await typeViaKeyboard(transport, sessionId, text);
}
if (opts.submit) await dispatchKey(transport, sessionId, "Enter");
emitSnapshot(await snap(transport, sessionId, state.activeTargetId, limit));
});
}));
program.command("press <key>").description("Press key combo (e.g. Enter, Escape, Control+a) and return snapshot").option("-l, --limit <n>", "max elements in snapshot", "50").addHelpText("after", "\nKeys: Enter, Escape, Tab, Space, Backspace, Delete,\n ArrowUp, ArrowDown, ArrowLeft, ArrowRight,\n Home, End, PageUp, PageDown\nModifiers: Control (Ctrl), Shift, Alt, Meta (Cmd)\n\nExamples:\n bp press Enter\n bp press Escape\n bp press Control+a\n bp press Meta+c").action(action(async (key, opts) => {

@@ -813,0 +880,0 @@ const limit = parseLimit(opts.limit);

+1
-1
{
"name": "browser-pilot-cli",
"version": "0.1.1",
"version": "0.1.2",
"description": "CLI tool to control your browser via Chrome DevTools Protocol",

@@ -5,0 +5,0 @@ "type": "module",

@@ -57,2 +57,3 @@ # browser-pilot

- **Lightweight** — 78KB npm package. No bundled Chromium (unlike Playwright's 400MB+)
- **Rich editor support** — Works with contenteditable editors (Draft.js, ProseMirror, Quill, Slate) and Shadow DOM elements out of the box

@@ -131,3 +132,3 @@ ## Comparison

| `bp net block <pattern>` | Block requests matching URL pattern |
| `bp net mock <pattern>` | Mock responses (`--body`, `--file`, `--status`) |
| `bp net mock <pattern>` | Mock responses (`--body`, `--file`) |
| `bp net headers <pattern> <header...>` | Add/override request headers |

@@ -154,4 +155,9 @@ | `bp net rules` | List active interception rules |

[1] link "Home"
[2] textbox "Search"
[3] button "Submit"
[2] textbox "Search" ← <input>, <textarea>, or contenteditable
[3] textbox "" ← unnamed input (still interactive)
[4] combobox "" ← <select> dropdown
[5] spinbutton "Quantity" ← <input type="number">
[6] button "Submit"
[7] checkbox "Agree" checked
[8] slider "Volume" ← <input type="range">
```

@@ -161,3 +167,3 @@

Refs are scoped to the current page — they refresh automatically after every action.
Refs are scoped to the current page — they refresh automatically after every action. Elements inside Shadow DOM are included automatically.

@@ -204,2 +210,18 @@ ## Output

## Rich Text Editors & Shadow DOM
`bp type` works with contenteditable-based editors (Draft.js, ProseMirror, Quill, Slate, Lexical). They appear as `textbox` in snapshots:
```bash
bp type 3 "new content" --clear # replace content in a rich text editor
```
Shadow DOM elements are traversed automatically — no special commands needed. Elements inside open shadow roots (even deeply nested) appear in snapshots and can be clicked/typed normally.
For `<select>` dropdowns (shown as `combobox`), use `bp eval`:
```bash
bp eval 'document.querySelector("select").value = "opt2"; document.querySelector("select").dispatchEvent(new Event("change",{bubbles:true}))'
```
## Network Interception

@@ -221,3 +243,3 @@

bp net mock "*api/data*" --body '{"ok":true}'
bp net mock "*api/users*" --file mock.json --status 200
bp net mock "*api/users*" --file mock.json

@@ -234,2 +256,19 @@ # Override request headers

## Testing
121 tests across 4 Playwright projects, adapted from Playwright/Puppeteer test fixtures and real-world sites:
```bash
npm test # core + compat + network (105 tests, ~2min)
npm run test:integration # real-site tests against the-internet.herokuapp.com
npm run test:all # all 121 tests
```
| Project | Tests | Coverage |
|---------|-------|----------|
| **core** | 41 | lifecycle, nav, click, type, press, eval, screenshot, pdf, cookies, frames, upload, auth, tabs, dialogs |
| **compat** | 46 | contenteditable (5 variants), Shadow DOM (3 levels), input types, scrollable, overlay, select |
| **network** | 19 | request monitoring, block, mock, headers, rule management |
| **integration** | 15 | checkboxes, dropdown, key presses, dynamic controls, Shadow DOM, nested frames, TinyMCE, large DOM |
## Requirements

@@ -236,0 +275,0 @@

import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: 0,
workers: 1, // bp uses a single browser session — must be serial
globalSetup: './tests/global-setup.ts',
globalTeardown: './tests/global-teardown.ts',
use: {
baseURL: 'http://127.0.0.1:18274',
},
webServer: {
command: 'node tests/server.mjs 18274',
port: 18274,
reuseExistingServer: true,
},
projects: [
{
name: 'core',
testMatch: /core\.spec/,
},
{
name: 'compat',
testMatch: /fill|click|snapshot/,
},
{
name: 'network',
testMatch: /network\.spec/,
},
{
name: 'integration',
testMatch: /real-site\.spec/,
},
],
});
{
"status": "passed",
"failedTests": []
}