@bubstack/moe-glass 0.1.0
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 +29 -0
- package/agents/browser-user.md +105 -0
- package/dist/LICENSE +25 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22517 -0
- package/dist/index.js.map +1 -0
- package/dist/payload.d.ts +214 -0
- package/dist/payload.d.ts.map +1 -0
- package/dist/payload.js +325 -0
- package/dist/payload.js.map +1 -0
- package/package.json +59 -0
- package/skills/browsing/COMMANDLINE-USAGE.md +595 -0
- package/skills/browsing/EXAMPLES.md +717 -0
- package/skills/browsing/README.md +55 -0
- package/skills/browsing/SKILL.md +478 -0
- package/skills/browsing/chrome-ws +1021 -0
- package/skills/browsing/chrome-ws-lib.js +461 -0
- package/skills/browsing/host-override.js +98 -0
- package/skills/browsing/lib/browser-bridge.js +175 -0
- package/skills/browsing/lib/browser-session.js +137 -0
- package/skills/browsing/lib/capture.js +499 -0
- package/skills/browsing/lib/cdp-router.js +72 -0
- package/skills/browsing/lib/cdp-utils.js +18 -0
- package/skills/browsing/lib/chrome-launcher-helpers.js +374 -0
- package/skills/browsing/lib/chrome-process.js +464 -0
- package/skills/browsing/lib/console-logging.js +70 -0
- package/skills/browsing/lib/cookies.js +17 -0
- package/skills/browsing/lib/dialogs-render.js +154 -0
- package/skills/browsing/lib/dialogs-router.js +117 -0
- package/skills/browsing/lib/dialogs.js +254 -0
- package/skills/browsing/lib/element-selector.js +91 -0
- package/skills/browsing/lib/evaluation.js +85 -0
- package/skills/browsing/lib/extraction.js +55 -0
- package/skills/browsing/lib/file-upload.js +56 -0
- package/skills/browsing/lib/html-diff.js +122 -0
- package/skills/browsing/lib/key-definitions.js +149 -0
- package/skills/browsing/lib/keyboard-input.js +288 -0
- package/skills/browsing/lib/mouse.js +423 -0
- package/skills/browsing/lib/navigation.js +272 -0
- package/skills/browsing/lib/page-scripts/dom-summary.js +31 -0
- package/skills/browsing/lib/page-scripts/markdown.js +85 -0
- package/skills/browsing/lib/page-scripts/permission-shim.js +80 -0
- package/skills/browsing/lib/page-session.js +106 -0
- package/skills/browsing/lib/profile-lock.js +179 -0
- package/skills/browsing/lib/screenshot.js +171 -0
- package/skills/browsing/lib/select-option.js +99 -0
- package/skills/browsing/lib/session-state.js +66 -0
- package/skills/browsing/lib/tabs.js +144 -0
- package/skills/browsing/lib/viewport.js +103 -0
- package/skills/browsing/lib/websocket-client.js +162 -0
- package/skills/browsing/package.json +11 -0
- package/skills/browsing/test-chrome-args.js +81 -0
- package/skills/browsing/test-cookies.js +21 -0
- package/skills/browsing/test-e2e.sh +51 -0
- package/skills/browsing/test-extract.sh +17 -0
- package/skills/browsing/test-interact.sh +11 -0
- package/skills/browsing/test-navigate.sh +9 -0
- package/skills/browsing/test-raw.sh +8 -0
- package/skills/browsing/test-tabs.sh +15 -0
- package/skills/browsing/test-viewport.js +27 -0
- package/skills/browsing/test-wait.sh +9 -0
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bubstack/moe-glass",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Direct Chrome DevTools Protocol access. Skill mode (17 CLI commands) plus MCP mode (single use_browser tool). Auto-starts Chrome.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Zak Keown",
|
|
9
|
+
"email": "zak.keown@outlook.com"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://gitlab.com/moe-ai/moe",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://gitlab.com/moe-ai/moe.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"claude-code",
|
|
18
|
+
"codex",
|
|
19
|
+
"chrome",
|
|
20
|
+
"devtools-protocol",
|
|
21
|
+
"browser",
|
|
22
|
+
"automation",
|
|
23
|
+
"screenshots",
|
|
24
|
+
"mcp"
|
|
25
|
+
],
|
|
26
|
+
"main": "dist/index.js",
|
|
27
|
+
"bin": {
|
|
28
|
+
"moe-glass": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"registry": "https://registry.npmjs.org/"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"agents",
|
|
37
|
+
"skills"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=24"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
44
|
+
"zod": "^3.23.8"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^24.7.0",
|
|
48
|
+
"esbuild": "^0.25.11",
|
|
49
|
+
"jsdom": "^29.0.2",
|
|
50
|
+
"typescript": "^5.9.0"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc -b && esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:fsevents && node ../../scripts/copy-license.mjs MIT dist/LICENSE",
|
|
54
|
+
"typecheck": "tsc -b --pretty",
|
|
55
|
+
"test": "vitest run --project unit",
|
|
56
|
+
"test:chrome": "vitest run --project chrome",
|
|
57
|
+
"lint": "biome check ."
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
# Command-Line Usage: chrome-ws Tool
|
|
2
|
+
|
|
3
|
+
Direct command-line access to Chrome DevTools Protocol via the `chrome-ws` bash tool.
|
|
4
|
+
|
|
5
|
+
**Note**: For use within Claude Code, the MCP `use_browser` tool is recommended. This document is for direct command-line usage or integration with other tools.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd ~/.claude/plugins/cache/using-chrome-directly/skills/using-chrome-directly
|
|
11
|
+
chmod +x chrome-ws
|
|
12
|
+
./chrome-ws start # Auto-detects platform, launches Chrome
|
|
13
|
+
./chrome-ws tabs # Verify running
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Chrome starts headed with `--remote-debugging-port=9222`, a separate profile in `/tmp/chrome-debug` (or `C:\temp\chrome-debug` on Windows), and the same automation flag set the MCP launch path uses.
|
|
17
|
+
|
|
18
|
+
## Environment Variables
|
|
19
|
+
|
|
20
|
+
| Variable | Default | Description |
|
|
21
|
+
|----------|---------|-------------|
|
|
22
|
+
| `CHROME_WS_BROWSER` | (auto-detect) | Path to browser executable. Overrides auto-detection. |
|
|
23
|
+
| `CHROME_WS_HOST` | `127.0.0.1` | Debug host address |
|
|
24
|
+
| `CHROME_WS_PORT` | `9222` | Debug port number |
|
|
25
|
+
| `CHROME_EXTRA_ARGS` | (none) | Whitespace-separated extra Chrome flags appended at launch. Needed on headless Linux / as root, e.g. `--no-sandbox --headless=new --disable-dev-shm-usage`. |
|
|
26
|
+
| `CHROME_WS_PROFILE` | (auto) | Profile name. Default is `moe-glass`; if another live process holds that profile's lock, the CLI / MCP falls through to `moe-glass-2`, `-3`, etc. Set this to opt out — explicit names always claim the named profile (sharing with whoever else has it). |
|
|
27
|
+
|
|
28
|
+
**Examples:**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Force Chromium instead of Chrome
|
|
32
|
+
CHROME_WS_BROWSER=/usr/bin/chromium ./chrome-ws start
|
|
33
|
+
|
|
34
|
+
# Use custom port
|
|
35
|
+
CHROME_WS_PORT=9333 ./chrome-ws start
|
|
36
|
+
|
|
37
|
+
# Use Brave browser
|
|
38
|
+
CHROME_WS_BROWSER="/usr/bin/brave-browser" ./chrome-ws start
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Command Reference
|
|
42
|
+
|
|
43
|
+
**Lifecycle:**
|
|
44
|
+
```bash
|
|
45
|
+
chrome-ws start [port] # Launch Chrome (auto-detects platform)
|
|
46
|
+
chrome-ws stop # Kill Chrome
|
|
47
|
+
chrome-ws pid # Print Chrome PID
|
|
48
|
+
chrome-ws info # Print Chrome info (JSON: pid, port, mode, profile, profileDir, running)
|
|
49
|
+
chrome-ws --help # Show usage
|
|
50
|
+
chrome-ws --version # Print version
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`chrome-ws --port=N <command>` overrides `CHROME_WS_PORT` for a single invocation.
|
|
54
|
+
|
|
55
|
+
Unknown commands now print `Unknown command: <name>` and point at `--help` instead of the raw-specific usage banner.
|
|
56
|
+
|
|
57
|
+
**Tab Management:**
|
|
58
|
+
```bash
|
|
59
|
+
chrome-ws tabs # List tabs as TSV (id<TAB>url<TAB>title); use `info` for JSON
|
|
60
|
+
chrome-ws new <url> # Create tab
|
|
61
|
+
chrome-ws close <tab> # Close tab (accepts numeric index or ws-url)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Navigation:**
|
|
65
|
+
```bash
|
|
66
|
+
chrome-ws navigate <tab> <url> # Navigate
|
|
67
|
+
chrome-ws wait-for <tab> <selector> # Wait for element
|
|
68
|
+
chrome-ws wait-text <tab> <text> # Wait for text
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Interaction:**
|
|
72
|
+
```bash
|
|
73
|
+
chrome-ws click <tab> <selector> # Click
|
|
74
|
+
chrome-ws fill <tab> <selector> <value> # Fill input
|
|
75
|
+
chrome-ws select <tab> <selector> <value> # Select dropdown
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Extraction:**
|
|
79
|
+
```bash
|
|
80
|
+
chrome-ws eval <tab> <js> # Execute JavaScript
|
|
81
|
+
chrome-ws extract <tab> <selector> # Get text content
|
|
82
|
+
chrome-ws attr <tab> <selector> <attr> # Get attribute
|
|
83
|
+
chrome-ws html <tab> [selector] # Get HTML
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Export:**
|
|
87
|
+
```bash
|
|
88
|
+
chrome-ws screenshot <tab> <file.png> # Capture screenshot
|
|
89
|
+
chrome-ws markdown <tab> <file.md> # Save as markdown
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Raw Protocol:**
|
|
93
|
+
```bash
|
|
94
|
+
chrome-ws raw <ws-url> <json-rpc> # Direct CDP access
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`<tab>` accepts either tab index (0, 1, 2) or full WebSocket URL.
|
|
98
|
+
|
|
99
|
+
## Examples
|
|
100
|
+
|
|
101
|
+
### Basic Operations
|
|
102
|
+
|
|
103
|
+
**Extract page content:**
|
|
104
|
+
```bash
|
|
105
|
+
chrome-ws navigate 0 "https://example.com"
|
|
106
|
+
chrome-ws wait-for 0 "h1"
|
|
107
|
+
|
|
108
|
+
# Get page title
|
|
109
|
+
TITLE=$(chrome-ws eval 0 "document.title")
|
|
110
|
+
|
|
111
|
+
# Get main heading
|
|
112
|
+
HEADING=$(chrome-ws extract 0 "h1")
|
|
113
|
+
|
|
114
|
+
# Get first link URL
|
|
115
|
+
LINK=$(chrome-ws attr 0 "a" "href")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Get all links:**
|
|
119
|
+
```bash
|
|
120
|
+
chrome-ws navigate 0 "https://example.com"
|
|
121
|
+
LINKS=$(chrome-ws eval 0 "Array.from(document.querySelectorAll('a')).map(a => ({
|
|
122
|
+
text: a.textContent.trim(),
|
|
123
|
+
href: a.href
|
|
124
|
+
}))")
|
|
125
|
+
echo "$LINKS"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Extract table data:**
|
|
129
|
+
```bash
|
|
130
|
+
chrome-ws navigate 0 "https://example.com/data"
|
|
131
|
+
chrome-ws wait-for 0 "table"
|
|
132
|
+
|
|
133
|
+
# Convert table to JSON array
|
|
134
|
+
TABLE=$(chrome-ws eval 0 "
|
|
135
|
+
Array.from(document.querySelectorAll('table tr')).map(row =>
|
|
136
|
+
Array.from(row.cells).map(cell => cell.textContent.trim())
|
|
137
|
+
)
|
|
138
|
+
")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Form Automation
|
|
142
|
+
|
|
143
|
+
**Simple login:**
|
|
144
|
+
```bash
|
|
145
|
+
chrome-ws navigate 0 "https://app.example.com/login"
|
|
146
|
+
chrome-ws wait-for 0 "input[name=email]"
|
|
147
|
+
|
|
148
|
+
# Fill credentials
|
|
149
|
+
chrome-ws fill 0 "input[name=email]" "user@example.com"
|
|
150
|
+
chrome-ws fill 0 "input[name=password]" "securepass123"
|
|
151
|
+
|
|
152
|
+
# Submit and wait for dashboard
|
|
153
|
+
chrome-ws click 0 "button[type=submit]"
|
|
154
|
+
chrome-ws wait-text 0 "Dashboard"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Multi-step form:**
|
|
158
|
+
```bash
|
|
159
|
+
chrome-ws navigate 0 "https://example.com/register"
|
|
160
|
+
|
|
161
|
+
# Step 1: Personal information
|
|
162
|
+
chrome-ws fill 0 "input[name=firstName]" "John"
|
|
163
|
+
chrome-ws fill 0 "input[name=lastName]" "Doe"
|
|
164
|
+
chrome-ws fill 0 "input[name=email]" "john@example.com"
|
|
165
|
+
chrome-ws click 0 "button.next"
|
|
166
|
+
|
|
167
|
+
# Wait for step 2 to load
|
|
168
|
+
chrome-ws wait-for 0 "input[name=address]"
|
|
169
|
+
|
|
170
|
+
# Step 2: Address
|
|
171
|
+
chrome-ws fill 0 "input[name=address]" "123 Main St"
|
|
172
|
+
chrome-ws select 0 "select[name=state]" "IL"
|
|
173
|
+
chrome-ws fill 0 "input[name=zip]" "62701"
|
|
174
|
+
chrome-ws click 0 "button.submit"
|
|
175
|
+
|
|
176
|
+
chrome-ws wait-text 0 "Registration complete"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Search with filters:**
|
|
180
|
+
```bash
|
|
181
|
+
chrome-ws navigate 0 "https://library.example.com/search"
|
|
182
|
+
chrome-ws wait-for 0 "form"
|
|
183
|
+
|
|
184
|
+
# Select category dropdown
|
|
185
|
+
chrome-ws select 0 "select[name=category]" "books"
|
|
186
|
+
|
|
187
|
+
# Fill search term
|
|
188
|
+
chrome-ws fill 0 "input[name=query]" "chrome devtools"
|
|
189
|
+
|
|
190
|
+
# Submit search
|
|
191
|
+
chrome-ws click 0 "button[type=submit]"
|
|
192
|
+
chrome-ws wait-for 0 ".results"
|
|
193
|
+
|
|
194
|
+
# Count results
|
|
195
|
+
RESULTS=$(chrome-ws eval 0 "document.querySelectorAll('.result').length")
|
|
196
|
+
echo "Found $RESULTS results"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Web Scraping
|
|
200
|
+
|
|
201
|
+
**Article content:**
|
|
202
|
+
```bash
|
|
203
|
+
chrome-ws navigate 0 "https://blog.example.com/article"
|
|
204
|
+
chrome-ws wait-for 0 "article"
|
|
205
|
+
|
|
206
|
+
# Extract metadata
|
|
207
|
+
TITLE=$(chrome-ws extract 0 "article h1")
|
|
208
|
+
AUTHOR=$(chrome-ws extract 0 ".author-name")
|
|
209
|
+
DATE=$(chrome-ws extract 0 "time")
|
|
210
|
+
CONTENT=$(chrome-ws extract 0 "article .content")
|
|
211
|
+
|
|
212
|
+
# Save to file
|
|
213
|
+
cat > article.txt <<EOF
|
|
214
|
+
Title: $TITLE
|
|
215
|
+
Author: $AUTHOR
|
|
216
|
+
Date: $DATE
|
|
217
|
+
|
|
218
|
+
$CONTENT
|
|
219
|
+
EOF
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Product information:**
|
|
223
|
+
```bash
|
|
224
|
+
chrome-ws navigate 0 "https://shop.example.com/product/123"
|
|
225
|
+
chrome-ws wait-for 0 ".product-details"
|
|
226
|
+
|
|
227
|
+
NAME=$(chrome-ws extract 0 "h1.product-name")
|
|
228
|
+
PRICE=$(chrome-ws extract 0 ".price")
|
|
229
|
+
IMAGE=$(chrome-ws attr 0 ".product-image img" "src")
|
|
230
|
+
STOCK=$(chrome-ws extract 0 ".stock-status")
|
|
231
|
+
|
|
232
|
+
# Output as JSON
|
|
233
|
+
cat <<EOF
|
|
234
|
+
{
|
|
235
|
+
"name": "$NAME",
|
|
236
|
+
"price": "$PRICE",
|
|
237
|
+
"image": "$IMAGE",
|
|
238
|
+
"in_stock": "$STOCK"
|
|
239
|
+
}
|
|
240
|
+
EOF
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Batch process URLs:**
|
|
244
|
+
```bash
|
|
245
|
+
URLS=("page1" "page2" "page3")
|
|
246
|
+
|
|
247
|
+
for URL in "${URLS[@]}"; do
|
|
248
|
+
chrome-ws navigate 0 "https://example.com/$URL"
|
|
249
|
+
chrome-ws wait-for 0 "h1"
|
|
250
|
+
TITLE=$(chrome-ws extract 0 "h1")
|
|
251
|
+
echo "$URL: $TITLE" >> results.txt
|
|
252
|
+
done
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Multi-Tab Workflows
|
|
256
|
+
|
|
257
|
+
**Email extraction:**
|
|
258
|
+
```bash
|
|
259
|
+
# List all tabs
|
|
260
|
+
chrome-ws tabs
|
|
261
|
+
|
|
262
|
+
# Use the email tab index from output (e.g., tab 2)
|
|
263
|
+
EMAIL_TAB=2
|
|
264
|
+
|
|
265
|
+
# Click specific email
|
|
266
|
+
chrome-ws click $EMAIL_TAB "a[title*='Organization receipt']"
|
|
267
|
+
|
|
268
|
+
# Wait for email to load
|
|
269
|
+
chrome-ws wait-for $EMAIL_TAB ".email-body"
|
|
270
|
+
|
|
271
|
+
# Extract donation amount
|
|
272
|
+
AMOUNT=$(chrome-ws extract $EMAIL_TAB ".donation-amount")
|
|
273
|
+
echo "Donation: $AMOUNT"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Price comparison:**
|
|
277
|
+
```bash
|
|
278
|
+
chrome-ws navigate 0 "https://store1.com/product"
|
|
279
|
+
chrome-ws new "https://store2.com/product"
|
|
280
|
+
chrome-ws new "https://store3.com/product"
|
|
281
|
+
sleep 3 # Let pages load
|
|
282
|
+
|
|
283
|
+
PRICE1=$(chrome-ws extract 0 ".price")
|
|
284
|
+
PRICE2=$(chrome-ws extract 1 ".price")
|
|
285
|
+
PRICE3=$(chrome-ws extract 2 ".price")
|
|
286
|
+
|
|
287
|
+
echo "Store 1: $PRICE1"
|
|
288
|
+
echo "Store 2: $PRICE2"
|
|
289
|
+
echo "Store 3: $PRICE3"
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Cross-reference between sites:**
|
|
293
|
+
```bash
|
|
294
|
+
# Get phone number from company site
|
|
295
|
+
chrome-ws navigate 0 "https://company.com/contact"
|
|
296
|
+
chrome-ws wait-for 0 ".phone"
|
|
297
|
+
PHONE=$(chrome-ws extract 0 ".phone")
|
|
298
|
+
|
|
299
|
+
# Look up phone number in verification site
|
|
300
|
+
chrome-ws new "https://lookup.com"
|
|
301
|
+
chrome-ws fill 1 "input[name=phone]" "$PHONE"
|
|
302
|
+
chrome-ws click 1 "button.search"
|
|
303
|
+
chrome-ws wait-for 1 ".results"
|
|
304
|
+
chrome-ws extract 1 ".verification-status"
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Dynamic Content
|
|
308
|
+
|
|
309
|
+
**Wait for AJAX to complete:**
|
|
310
|
+
```bash
|
|
311
|
+
chrome-ws navigate 0 "https://app.com/dashboard"
|
|
312
|
+
|
|
313
|
+
# Wait for spinner to disappear
|
|
314
|
+
chrome-ws eval 0 "new Promise(resolve => {
|
|
315
|
+
const check = () => {
|
|
316
|
+
if (!document.querySelector('.spinner')) {
|
|
317
|
+
resolve(true);
|
|
318
|
+
} else {
|
|
319
|
+
setTimeout(check, 100);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
check();
|
|
323
|
+
})"
|
|
324
|
+
|
|
325
|
+
# Now safe to extract
|
|
326
|
+
chrome-ws extract 0 ".dashboard-data"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Infinite scroll:**
|
|
330
|
+
```bash
|
|
331
|
+
chrome-ws navigate 0 "https://example.com/feed"
|
|
332
|
+
chrome-ws wait-for 0 ".feed-item"
|
|
333
|
+
|
|
334
|
+
# Scroll 5 times
|
|
335
|
+
for i in {1..5}; do
|
|
336
|
+
chrome-ws eval 0 "window.scrollTo(0, document.body.scrollHeight)"
|
|
337
|
+
sleep 2
|
|
338
|
+
done
|
|
339
|
+
|
|
340
|
+
# Count loaded items
|
|
341
|
+
chrome-ws eval 0 "document.querySelectorAll('.feed-item').length"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Monitor for changes:**
|
|
345
|
+
```bash
|
|
346
|
+
chrome-ws navigate 0 "https://example.com/status"
|
|
347
|
+
END=$(($(date +%s) + 300))
|
|
348
|
+
|
|
349
|
+
while [ $(date +%s) -lt $END ]; do
|
|
350
|
+
STATUS=$(chrome-ws extract 0 ".status")
|
|
351
|
+
echo "[$(date +%H:%M:%S)] $STATUS"
|
|
352
|
+
|
|
353
|
+
if [[ "$STATUS" == *"ERROR"* ]]; then
|
|
354
|
+
echo "ALERT: Error detected"
|
|
355
|
+
break
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
sleep 10
|
|
359
|
+
done
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Advanced Patterns
|
|
363
|
+
|
|
364
|
+
**Multi-step workflow:**
|
|
365
|
+
```bash
|
|
366
|
+
chrome-ws navigate 0 "https://booking.example.com"
|
|
367
|
+
|
|
368
|
+
# Search
|
|
369
|
+
chrome-ws fill 0 "input[name=destination]" "San Francisco"
|
|
370
|
+
chrome-ws fill 0 "input[name=checkin]" "2025-12-01"
|
|
371
|
+
chrome-ws click 0 "button.search"
|
|
372
|
+
|
|
373
|
+
# Select hotel
|
|
374
|
+
chrome-ws wait-for 0 ".hotel-results"
|
|
375
|
+
chrome-ws click 0 ".hotel-card:first-child .select"
|
|
376
|
+
|
|
377
|
+
# Choose room
|
|
378
|
+
chrome-ws wait-for 0 ".room-options"
|
|
379
|
+
chrome-ws click 0 ".room[data-type=deluxe] .book"
|
|
380
|
+
|
|
381
|
+
# Fill guest info
|
|
382
|
+
chrome-ws wait-for 0 "form.guest-info"
|
|
383
|
+
chrome-ws fill 0 "input[name=firstName]" "Jane"
|
|
384
|
+
chrome-ws fill 0 "input[name=lastName]" "Smith"
|
|
385
|
+
chrome-ws fill 0 "input[name=email]" "jane@example.com"
|
|
386
|
+
|
|
387
|
+
# Review
|
|
388
|
+
chrome-ws click 0 "button.review"
|
|
389
|
+
chrome-ws wait-for 0 ".summary"
|
|
390
|
+
|
|
391
|
+
# Extract confirmation
|
|
392
|
+
HOTEL=$(chrome-ws extract 0 ".hotel-name")
|
|
393
|
+
TOTAL=$(chrome-ws extract 0 ".total-price")
|
|
394
|
+
echo "$HOTEL: $TOTAL"
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
**Cookies and localStorage:**
|
|
398
|
+
```bash
|
|
399
|
+
# Get cookies
|
|
400
|
+
chrome-ws eval 0 "document.cookie"
|
|
401
|
+
|
|
402
|
+
# Set cookie
|
|
403
|
+
chrome-ws eval 0 "document.cookie = 'theme=dark; path=/'"
|
|
404
|
+
|
|
405
|
+
# Get localStorage
|
|
406
|
+
chrome-ws eval 0 "JSON.stringify(localStorage)"
|
|
407
|
+
|
|
408
|
+
# Set localStorage
|
|
409
|
+
chrome-ws eval 0 "localStorage.setItem('lastVisit', new Date().toISOString())"
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Handle modals:**
|
|
413
|
+
```bash
|
|
414
|
+
chrome-ws click 0 "button.open-modal"
|
|
415
|
+
chrome-ws wait-for 0 ".modal.visible"
|
|
416
|
+
|
|
417
|
+
# Fill modal form
|
|
418
|
+
chrome-ws fill 0 ".modal input[name=username]" "testuser"
|
|
419
|
+
chrome-ws click 0 ".modal button.submit"
|
|
420
|
+
|
|
421
|
+
# Wait for modal to close
|
|
422
|
+
chrome-ws eval 0 "new Promise(resolve => {
|
|
423
|
+
const check = () => {
|
|
424
|
+
if (!document.querySelector('.modal.visible')) {
|
|
425
|
+
resolve(true);
|
|
426
|
+
} else {
|
|
427
|
+
setTimeout(check, 100);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
check();
|
|
431
|
+
})"
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Network monitoring with raw CDP:**
|
|
435
|
+
```bash
|
|
436
|
+
# Enable network monitoring
|
|
437
|
+
chrome-ws raw 0 '{"id":1,"method":"Network.enable","params":{}}'
|
|
438
|
+
|
|
439
|
+
# Navigate and capture traffic
|
|
440
|
+
chrome-ws navigate 0 "https://api.example.com"
|
|
441
|
+
|
|
442
|
+
# Get performance metrics
|
|
443
|
+
chrome-ws raw 0 '{"id":2,"method":"Performance.getMetrics","params":{}}'
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
**Screenshots and PDF:**
|
|
447
|
+
```bash
|
|
448
|
+
# Capture screenshot
|
|
449
|
+
chrome-ws screenshot 0 "page.png"
|
|
450
|
+
|
|
451
|
+
# Or use raw CDP for more control
|
|
452
|
+
SCREENSHOT=$(chrome-ws raw 0 '{
|
|
453
|
+
"id":1,
|
|
454
|
+
"method":"Page.captureScreenshot",
|
|
455
|
+
"params":{"format":"png","quality":80}
|
|
456
|
+
}')
|
|
457
|
+
|
|
458
|
+
# Extract base64 and save
|
|
459
|
+
echo "$SCREENSHOT" | node -pe "JSON.parse(require('fs').readFileSync(0)).result.data" | base64 -d > screenshot.png
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## Error Handling
|
|
463
|
+
|
|
464
|
+
**Check element exists:**
|
|
465
|
+
```bash
|
|
466
|
+
# Verify button exists
|
|
467
|
+
EXISTS=$(chrome-ws eval 0 "!!document.querySelector('.important-button')")
|
|
468
|
+
|
|
469
|
+
if [ "$EXISTS" = "true" ]; then
|
|
470
|
+
chrome-ws click 0 ".important-button"
|
|
471
|
+
else
|
|
472
|
+
echo "Button not found on page"
|
|
473
|
+
fi
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Verify command success:**
|
|
477
|
+
```bash
|
|
478
|
+
if ! chrome-ws navigate 0 "https://example.com"; then
|
|
479
|
+
echo "Navigation failed - Chrome not running?"
|
|
480
|
+
exit 1
|
|
481
|
+
fi
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Retry pattern:**
|
|
485
|
+
```bash
|
|
486
|
+
for attempt in {1..3}; do
|
|
487
|
+
if chrome-ws click 0 ".submit-button"; then
|
|
488
|
+
echo "Click succeeded"
|
|
489
|
+
break
|
|
490
|
+
fi
|
|
491
|
+
echo "Attempt $attempt failed, retrying..."
|
|
492
|
+
sleep 2
|
|
493
|
+
done
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
## Best Practices
|
|
497
|
+
|
|
498
|
+
**Always wait before interaction:**
|
|
499
|
+
```bash
|
|
500
|
+
# BAD - might fail if page slow to load
|
|
501
|
+
chrome-ws navigate 0 "https://example.com"
|
|
502
|
+
chrome-ws click 0 "button" # May fail!
|
|
503
|
+
|
|
504
|
+
# GOOD - wait for element first
|
|
505
|
+
chrome-ws navigate 0 "https://example.com"
|
|
506
|
+
chrome-ws wait-for 0 "button"
|
|
507
|
+
chrome-ws click 0 "button"
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Use specific selectors:**
|
|
511
|
+
```bash
|
|
512
|
+
# BAD - matches first button on page
|
|
513
|
+
chrome-ws click 0 "button"
|
|
514
|
+
|
|
515
|
+
# GOOD - specific selector
|
|
516
|
+
chrome-ws click 0 "button[type=submit]"
|
|
517
|
+
chrome-ws click 0 "button.login-button"
|
|
518
|
+
chrome-ws click 0 "#submit-form"
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
**Test selectors with html command:**
|
|
522
|
+
```bash
|
|
523
|
+
# Check page structure
|
|
524
|
+
chrome-ws html 0 | grep "submit"
|
|
525
|
+
|
|
526
|
+
# Check specific element exists
|
|
527
|
+
chrome-ws html 0 "form"
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**Escape special characters:**
|
|
531
|
+
```bash
|
|
532
|
+
# Use double quotes for variables
|
|
533
|
+
chrome-ws fill 0 "input[name=search]" "$SEARCH_TERM"
|
|
534
|
+
|
|
535
|
+
# Use single quotes for literal strings with special chars
|
|
536
|
+
chrome-ws eval 0 'document.querySelector(".item").textContent'
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## Common Pitfalls
|
|
540
|
+
|
|
541
|
+
**Don't cache tab indices** - they change when tabs close:
|
|
542
|
+
```bash
|
|
543
|
+
# BAD - index might be stale
|
|
544
|
+
TAB=2
|
|
545
|
+
# ... much later ...
|
|
546
|
+
chrome-ws click $TAB "button" # Tab 2 might not exist anymore
|
|
547
|
+
|
|
548
|
+
# GOOD - fetch fresh before use
|
|
549
|
+
chrome-ws tabs
|
|
550
|
+
chrome-ws click 2 "button"
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**Don't forget to wait for dynamic content:**
|
|
554
|
+
```bash
|
|
555
|
+
# BAD - tries to extract before content loads
|
|
556
|
+
chrome-ws navigate 0 "https://app.com"
|
|
557
|
+
chrome-ws extract 0 ".user-name" # Might be empty!
|
|
558
|
+
|
|
559
|
+
# GOOD - wait for content
|
|
560
|
+
chrome-ws navigate 0 "https://app.com"
|
|
561
|
+
chrome-ws wait-for 0 ".user-name"
|
|
562
|
+
chrome-ws extract 0 ".user-name"
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Handle element state:**
|
|
566
|
+
```bash
|
|
567
|
+
# Check if button is disabled
|
|
568
|
+
DISABLED=$(chrome-ws eval 0 "document.querySelector('button.submit').disabled")
|
|
569
|
+
|
|
570
|
+
if [ "$DISABLED" = "false" ]; then
|
|
571
|
+
chrome-ws click 0 "button.submit"
|
|
572
|
+
else
|
|
573
|
+
echo "Button is disabled"
|
|
574
|
+
fi
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
## Troubleshooting
|
|
578
|
+
|
|
579
|
+
**Connection refused:** Verify Chrome running with `curl http://127.0.0.1:9222/json`
|
|
580
|
+
|
|
581
|
+
**Element not found:** Check page structure with `chrome-ws html 0`
|
|
582
|
+
|
|
583
|
+
**Timeout:** Use `wait-for` before interaction. Chrome has 30s timeout.
|
|
584
|
+
|
|
585
|
+
**Tab index out of range:** Run `chrome-ws tabs` to get current indices.
|
|
586
|
+
|
|
587
|
+
## Protocol Reference
|
|
588
|
+
|
|
589
|
+
Full CDP documentation: https://chromedevtools.github.io/devtools-protocol/
|
|
590
|
+
|
|
591
|
+
Common methods via `raw` command:
|
|
592
|
+
- `Page.navigate`
|
|
593
|
+
- `Runtime.evaluate`
|
|
594
|
+
- `Network.enable`
|
|
595
|
+
- `Performance.getMetrics`
|