cdp-skill 1.0.17 → 1.0.19
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/EXAMPLES.md +1041 -0
- package/SKILL.md +2 -2
- package/install.js +1 -1
- package/package.json +2 -2
- package/scripts/cdp-skill.js +3 -3
- package/scripts/runner/execute-dynamic.js +6 -5
- package/scripts/runner/step-executors.js +5 -1
- package/scripts/tests/ExecuteDynamic.test.js +1 -1
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,1041 @@
|
|
|
1
|
+
# CDP Skill — Examples
|
|
2
|
+
|
|
3
|
+
Worked examples and JSON code blocks for every step type. See SKILL.md for the compact reference.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### Open a Tab (Chrome auto-launches)
|
|
10
|
+
```json
|
|
11
|
+
{"steps":[{"newTab":"https://google.com"}]}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Non-default Chrome (rare):
|
|
15
|
+
```json
|
|
16
|
+
{"steps":[{"newTab":{"url":"https://google.com","port":9333,"headless":true}}]}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Separate open and navigate:
|
|
20
|
+
```json
|
|
21
|
+
{"steps":[{"newTab":true},{"goto":"https://google.com"}]}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Stdin pipe:
|
|
25
|
+
```bash
|
|
26
|
+
echo '{"steps":[{"newTab":"https://google.com"}]}' | node scripts/cdp-skill.js
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Tab Lifecycle
|
|
30
|
+
```json
|
|
31
|
+
{"steps":[{"newTab":"https://google.com"}]}
|
|
32
|
+
```
|
|
33
|
+
Response: `{"tab": "t1", "steps": [{"action": "newTab", "status": "ok", ...}]}`
|
|
34
|
+
|
|
35
|
+
Subsequent calls:
|
|
36
|
+
```json
|
|
37
|
+
{"tab":"t1","steps":[{"click":"#btn"}]}
|
|
38
|
+
```
|
|
39
|
+
```json
|
|
40
|
+
{"tab":"t1","steps":[{"snapshot":true}]}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Optional timeout:
|
|
44
|
+
```json
|
|
45
|
+
{"tab":"t1","timeout":60000,"steps":[{"goto":"https://slow-site.com"}]}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Close when done:
|
|
49
|
+
```json
|
|
50
|
+
{"steps":[{"closeTab":"t1"}]}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Input / Output Schema
|
|
56
|
+
|
|
57
|
+
### Full Output Example
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"status": "ok",
|
|
61
|
+
"tab": "t1",
|
|
62
|
+
"navigated": true,
|
|
63
|
+
"context": {
|
|
64
|
+
"url": "https://example.com/page",
|
|
65
|
+
"title": "Page Title",
|
|
66
|
+
"scroll": {"y": 0, "percent": 0},
|
|
67
|
+
"viewport": {"width": 1189, "height": 739}
|
|
68
|
+
},
|
|
69
|
+
"screenshot": "/tmp/cdp-skill/t1.after.png",
|
|
70
|
+
"fullSnapshot": "/tmp/cdp-skill/t1.after.yaml",
|
|
71
|
+
"viewportSnapshot": "/tmp/cdp-skill/t1.viewport.yaml",
|
|
72
|
+
"steps": [{"action": "goto", "status": "ok"}]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Console Output
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"console": {
|
|
80
|
+
"errors": 1,
|
|
81
|
+
"warnings": 2,
|
|
82
|
+
"messages": [{"level": "error", "text": "TypeError: x is undefined", "source": "app.js:142"}]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Failure Diagnostics
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"steps": [{
|
|
91
|
+
"action": "click",
|
|
92
|
+
"status": "error",
|
|
93
|
+
"params": {"text": "Nonexistent"},
|
|
94
|
+
"error": "Element not found",
|
|
95
|
+
"context": {
|
|
96
|
+
"url": "https://example.com/page",
|
|
97
|
+
"title": "Example Page",
|
|
98
|
+
"scrollPosition": {"x": 0, "y": 1200, "maxY": 5000, "percentY": 24},
|
|
99
|
+
"visibleButtons": [
|
|
100
|
+
{"text": "Submit", "selector": "#submit-btn", "ref": "f0s1e4"},
|
|
101
|
+
{"text": "Cancel", "selector": "button.cancel", "ref": "f0s1e5"}
|
|
102
|
+
],
|
|
103
|
+
"visibleLinks": [{"text": "Home", "href": "..."}],
|
|
104
|
+
"visibleErrors": ["Please fill in all required fields"],
|
|
105
|
+
"nearMatches": [
|
|
106
|
+
{"text": "Submit Form", "selector": "button.submit-form", "ref": "f0s1e12", "score": 70},
|
|
107
|
+
{"text": "Submit Feedback", "selector": "#feedback-submit", "ref": "f0s1e15", "score": 50}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}],
|
|
111
|
+
"errors": [{"step": 1, "action": "click", "error": "Element not found"}]
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Auto-Snapshot Diff
|
|
118
|
+
|
|
119
|
+
### Navigation (URL changed)
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"status": "ok",
|
|
123
|
+
"tab": "t1",
|
|
124
|
+
"navigated": true,
|
|
125
|
+
"context": {
|
|
126
|
+
"url": "https://example.com/new-page",
|
|
127
|
+
"title": "New Page Title",
|
|
128
|
+
"scroll": {"y": 0, "percent": 0},
|
|
129
|
+
"viewport": {"width": 1189, "height": 739}
|
|
130
|
+
},
|
|
131
|
+
"screenshot": "/tmp/cdp-skill/t1.after.png",
|
|
132
|
+
"fullSnapshot": "/tmp/cdp-skill/t1.after.yaml",
|
|
133
|
+
"viewportSnapshot": "/tmp/cdp-skill/t1.viewport.yaml",
|
|
134
|
+
"steps": [{"action": "click", "status": "ok"}]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Same-Page Interaction (scroll, expand, toggle)
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"status": "ok",
|
|
142
|
+
"tab": "t1",
|
|
143
|
+
"navigated": false,
|
|
144
|
+
"context": {
|
|
145
|
+
"url": "https://example.com/page",
|
|
146
|
+
"scroll": {"y": 2400, "percent": 65},
|
|
147
|
+
"activeElement": {"tag": "INPUT", "type": "text", "selector": "#search", "value": "", "editable": true, "box": {"x": 100, "y": 50, "width": 200, "height": 32}}
|
|
148
|
+
},
|
|
149
|
+
"screenshot": "/tmp/cdp-skill/t1.after.png",
|
|
150
|
+
"fullSnapshot": "/tmp/cdp-skill/t1.after.yaml",
|
|
151
|
+
"changes": {
|
|
152
|
+
"summary": "Clicked. 3 added (f0s1e120, f0s1e121, f0s1e122), 1 removed (f0s1e1).",
|
|
153
|
+
"added": ["- link \"New Link\" [ref=f0s1e120]"],
|
|
154
|
+
"removed": ["- link \"Old Link\" [ref=f0s1e1]"],
|
|
155
|
+
"changed": [{"ref": "f0s1e5", "field": "expanded", "from": false, "to": true}]
|
|
156
|
+
},
|
|
157
|
+
"steps": [{"action": "click", "status": "ok"}]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Navigation
|
|
164
|
+
|
|
165
|
+
### goto
|
|
166
|
+
```json
|
|
167
|
+
{"goto": "https://google.com"}
|
|
168
|
+
{"goto": {"url": "https://google.com", "waitUntil": "networkidle"}}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### back / forward
|
|
172
|
+
```json
|
|
173
|
+
{"back": true}
|
|
174
|
+
{"forward": true}
|
|
175
|
+
```
|
|
176
|
+
Response:
|
|
177
|
+
```json
|
|
178
|
+
{"url": "https://example.com/previous", "title": "Previous Page"}
|
|
179
|
+
```
|
|
180
|
+
Or when no history:
|
|
181
|
+
```json
|
|
182
|
+
{"noHistory": true}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### reload
|
|
186
|
+
```json
|
|
187
|
+
{"reload": true}
|
|
188
|
+
{"reload": {"waitUntil": "networkidle"}}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### waitForNavigation
|
|
192
|
+
```json
|
|
193
|
+
{"waitForNavigation": true}
|
|
194
|
+
{"waitForNavigation": {"timeout": 5000, "waitUntil": "networkidle"}}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### switchTab — Connect to Existing Tab
|
|
198
|
+
By alias:
|
|
199
|
+
```json
|
|
200
|
+
{"steps":[{"switchTab":"t2"},{"snapshot":true}]}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
By URL regex:
|
|
204
|
+
```json
|
|
205
|
+
{"steps":[{"switchTab":{"url":"github\\.com"}},{"snapshot":true}]}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
By targetId:
|
|
209
|
+
```json
|
|
210
|
+
{"steps":[{"switchTab":{"targetId":"ABC123..."}},{"snapshot":true}]}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### New Tab Handling — click -> detect -> switchTab
|
|
214
|
+
When a click opens a new tab (e.g. `target="_blank"`), the response includes `newTabs`:
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"steps": [{"action": "click", "status": "ok", "output": {
|
|
218
|
+
"method": "cdp",
|
|
219
|
+
"newTabs": [{"targetId": "DEF456", "url": "https://other.com", "title": "Other"}]
|
|
220
|
+
}}]
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Then connect to the new tab:
|
|
225
|
+
```json
|
|
226
|
+
{"steps":[{"switchTab":{"url":"other\\.com"}},{"snapshot":true}]}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Interaction
|
|
232
|
+
|
|
233
|
+
### click — Basic
|
|
234
|
+
```json
|
|
235
|
+
{"click": "#submit"}
|
|
236
|
+
{"click": {"ref": "f0s1e4"}}
|
|
237
|
+
{"click": {"x": 450, "y": 200}}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### click — By visible text
|
|
241
|
+
```json
|
|
242
|
+
{"click": {"text": "Submit"}}
|
|
243
|
+
{"click": {"text": "Learn more", "exact": true}}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### click — Force (bypass actionability checks)
|
|
247
|
+
```json
|
|
248
|
+
{"click": {"selector": "#submit", "force": true}}
|
|
249
|
+
{"click": {"ref": "f0s1e4", "force": true}}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### click — Multi-selector fallback
|
|
253
|
+
```json
|
|
254
|
+
{"click": {"selectors": ["#submit", "button.primary", "[type=submit]"]}}
|
|
255
|
+
```
|
|
256
|
+
Response: `{clicked: true, matchedSelector: "#submit"}`
|
|
257
|
+
|
|
258
|
+
### click — Diagnostics (element covered)
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
"error": "Element is covered by another element",
|
|
262
|
+
"interceptedBy": {
|
|
263
|
+
"tagName": "div",
|
|
264
|
+
"id": "modal-overlay",
|
|
265
|
+
"className": "overlay active",
|
|
266
|
+
"textContent": "Loading..."
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### fill — Targeted
|
|
272
|
+
```json
|
|
273
|
+
{"fill": {"selector": "#email", "value": "user@example.com"}}
|
|
274
|
+
{"fill": {"ref": "f0s1e3", "value": "text"}}
|
|
275
|
+
{"fill": {"label": "Email address", "value": "test@example.com"}}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### fill — Currently focused element
|
|
279
|
+
```json
|
|
280
|
+
{"fill": "search query"}
|
|
281
|
+
{"fill": {"value": "text", "clear": false}}
|
|
282
|
+
```
|
|
283
|
+
Response:
|
|
284
|
+
```json
|
|
285
|
+
{"filled": true, "tag": "INPUT", "type": "text", "selector": "#search", "valueBefore": "", "valueAfter": "search query", "mode": "focused"}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### fill — Batch (multiple fields)
|
|
289
|
+
```json
|
|
290
|
+
{"fill": {"#firstName": "John", "#lastName": "Doe"}}
|
|
291
|
+
{"fill": {"fields": {"#firstName": "John"}, "react": true}}
|
|
292
|
+
```
|
|
293
|
+
Response:
|
|
294
|
+
```json
|
|
295
|
+
{"total": 2, "filled": 2, "failed": 0, "results": [{"selector": "#firstName", "status": "ok", "value": "John"}], "mode": "batch"}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### fill — Autocomplete pattern
|
|
299
|
+
```json
|
|
300
|
+
{"steps": [
|
|
301
|
+
{"fill": {"selector": "#city", "value": "San Fra"}},
|
|
302
|
+
{"wait": ".autocomplete-dropdown"},
|
|
303
|
+
{"click": {"text": "San Francisco"}}
|
|
304
|
+
]}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### press
|
|
308
|
+
```json
|
|
309
|
+
{"press": "Enter"}
|
|
310
|
+
{"press": "Control+a"}
|
|
311
|
+
{"press": "Meta+Shift+Enter"}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### hover — Basic
|
|
315
|
+
```json
|
|
316
|
+
{"hover": "#menu"}
|
|
317
|
+
{"hover": {"selector": "#tooltip", "duration": 500}}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### hover — With result capture
|
|
321
|
+
```json
|
|
322
|
+
{"hover": {"selector": "#menu", "captureResult": true}}
|
|
323
|
+
```
|
|
324
|
+
Response:
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"hovered": true,
|
|
328
|
+
"capturedResult": {
|
|
329
|
+
"visibleElements": [
|
|
330
|
+
{"selector": ".tooltip", "text": "Click to edit", "visible": true},
|
|
331
|
+
{"selector": ".dropdown-menu", "text": "Menu items", "visible": true}
|
|
332
|
+
]
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### drag
|
|
338
|
+
```json
|
|
339
|
+
{"drag": {"source": "#draggable", "target": "#dropzone"}}
|
|
340
|
+
{"drag": {"source": {"ref": "f0s1e1"}, "target": {"ref": "f0s1e5"}}}
|
|
341
|
+
{"drag": {"source": {"ref": "f0s1e1", "offsetX": 20}, "target": {"ref": "f0s1e5", "offsetY": -10}}}
|
|
342
|
+
{"drag": {"source": {"x": 100, "y": 100}, "target": {"x": 300, "y": 200}}}
|
|
343
|
+
{"drag": {"source": "#item", "target": "#container", "steps": 20, "delay": 10}}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### drag — With method
|
|
347
|
+
```json
|
|
348
|
+
{"drag": {"source": "#item", "target": "#zone", "method": "mouse"}}
|
|
349
|
+
{"drag": {"source": "#item", "target": "#zone", "method": "html5"}}
|
|
350
|
+
```
|
|
351
|
+
Response:
|
|
352
|
+
```json
|
|
353
|
+
{"dragged": true, "method": "mouse-events", "source": {"x": 100, "y": 100}, "target": {"x": 300, "y": 200}, "steps": 10}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### selectOption
|
|
357
|
+
```json
|
|
358
|
+
{"selectOption": {"selector": "#country", "value": "US"}}
|
|
359
|
+
{"selectOption": {"selector": "#country", "label": "United States"}}
|
|
360
|
+
{"selectOption": {"selector": "#country", "index": 2}}
|
|
361
|
+
{"selectOption": {"selector": "#colors", "values": ["red", "blue"]}}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### selectText
|
|
365
|
+
```json
|
|
366
|
+
{"selectText": "#input"}
|
|
367
|
+
{"selectText": {"selector": "#input", "start": 0, "end": 5}}
|
|
368
|
+
```
|
|
369
|
+
Response:
|
|
370
|
+
```json
|
|
371
|
+
{"selected": true, "selector": "#input"}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### upload
|
|
375
|
+
```json
|
|
376
|
+
{"upload": "/path/to/file.pdf"}
|
|
377
|
+
{"upload": ["/path/to/a.txt", "/path/to/b.png"]}
|
|
378
|
+
{"upload": {"selector": "#file-input", "file": "/path/to/doc.pdf"}}
|
|
379
|
+
{"upload": {"selector": "#file-input", "files": ["/path/to/a.txt", "/path/to/b.png"]}}
|
|
380
|
+
{"upload": {"ref": "f0s1e3", "files": ["/path/to/photo.jpg"]}}
|
|
381
|
+
```
|
|
382
|
+
Response:
|
|
383
|
+
```json
|
|
384
|
+
{"uploaded": true, "files": ["/path/to/file.pdf"], "accept": ".pdf,.doc", "multiple": false, "target": "input[type=\"file\"]"}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### submit
|
|
388
|
+
```json
|
|
389
|
+
{"submit": "form"}
|
|
390
|
+
{"submit": {"selector": "#login-form", "reportValidity": true}}
|
|
391
|
+
```
|
|
392
|
+
Response:
|
|
393
|
+
```json
|
|
394
|
+
{"submitted": true, "valid": true, "errors": []}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Query & Extraction
|
|
400
|
+
|
|
401
|
+
### snapshot
|
|
402
|
+
```json
|
|
403
|
+
{"snapshot": true}
|
|
404
|
+
{"snapshot": {"root": "#container", "maxElements": 500}}
|
|
405
|
+
{"snapshot": {"root": "role=main", "includeText": true}}
|
|
406
|
+
{"snapshot": {"includeFrames": true}}
|
|
407
|
+
{"snapshot": {"pierceShadow": true}}
|
|
408
|
+
{"snapshot": {"detail": "interactive"}}
|
|
409
|
+
{"snapshot": {"inlineLimit": 28000}}
|
|
410
|
+
{"snapshot": {"since": "f0s1"}}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
YAML output example:
|
|
414
|
+
```yaml
|
|
415
|
+
- navigation:
|
|
416
|
+
- link "Home" [ref=f0s1e1]
|
|
417
|
+
- main:
|
|
418
|
+
- heading "Welcome" [level=1]
|
|
419
|
+
- textbox "Email" [required] [invalid] [name=email] [ref=f0s1e3]
|
|
420
|
+
- button "Submit" [ref=f0s1e4]
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Snapshot Caching (since)
|
|
424
|
+
```json
|
|
425
|
+
{"snapshot": {"since": "f0s1"}}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Unchanged:
|
|
429
|
+
```json
|
|
430
|
+
{"unchanged": true, "snapshotId": "f0s1", "message": "Page unchanged since f0s1"}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Changed:
|
|
434
|
+
```json
|
|
435
|
+
{"snapshotId": "f0s2", "yaml": "- button \"Login\" [ref=f0s2e1]\n...", "refs": {}}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Detail: summary
|
|
439
|
+
```json
|
|
440
|
+
{"snapshot": {"detail": "summary"}}
|
|
441
|
+
```
|
|
442
|
+
```yaml
|
|
443
|
+
# Snapshot Summary
|
|
444
|
+
# Total elements: 1847
|
|
445
|
+
# Interactive elements: 67
|
|
446
|
+
# Viewport elements: 23
|
|
447
|
+
|
|
448
|
+
landmarks:
|
|
449
|
+
- role: main
|
|
450
|
+
interactiveCount: 47
|
|
451
|
+
children: [form, navigation, article]
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Large snapshot (auto-file)
|
|
455
|
+
```json
|
|
456
|
+
{
|
|
457
|
+
"yaml": null,
|
|
458
|
+
"artifacts": {"snapshot": "/tmp/cdp-skill/t1.snapshot.yaml"},
|
|
459
|
+
"snapshotSize": 125000,
|
|
460
|
+
"truncatedInline": true
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### snapshotSearch
|
|
465
|
+
```json
|
|
466
|
+
{"snapshotSearch": {"text": "Submit"}}
|
|
467
|
+
{"snapshotSearch": {"text": "Submit", "role": "button"}}
|
|
468
|
+
{"snapshotSearch": {"pattern": "^Save.*draft$", "role": "button"}}
|
|
469
|
+
{"snapshotSearch": {"role": "button", "limit": 20}}
|
|
470
|
+
{"snapshotSearch": {"text": "Edit", "near": {"x": 500, "y": 300, "radius": 100}}}
|
|
471
|
+
```
|
|
472
|
+
Response:
|
|
473
|
+
```json
|
|
474
|
+
{
|
|
475
|
+
"matches": [
|
|
476
|
+
{"path": "main > form > button", "ref": "f0s1e47", "name": "Submit Form", "role": "button"},
|
|
477
|
+
{"path": "dialog > button", "ref": "f0s1e89", "name": "Submit Feedback", "role": "button"}
|
|
478
|
+
],
|
|
479
|
+
"matchCount": 2,
|
|
480
|
+
"searchedElements": 1847
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### query — By CSS
|
|
485
|
+
```json
|
|
486
|
+
{"query": "h1"}
|
|
487
|
+
{"query": {"selector": "a", "limit": 5, "output": "href"}}
|
|
488
|
+
{"query": {"selector": "div", "output": ["text", "href"]}}
|
|
489
|
+
{"query": {"selector": "button", "output": {"attribute": "data-id"}}}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### query — By ARIA role
|
|
493
|
+
```json
|
|
494
|
+
{"query": {"role": "button"}}
|
|
495
|
+
{"query": {"role": "button", "name": "Submit"}}
|
|
496
|
+
{"query": {"role": "heading", "level": 2}}
|
|
497
|
+
{"query": {"role": ["button", "link"]}}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### query — Count only
|
|
501
|
+
```json
|
|
502
|
+
{"query": {"selector": "li.item", "count": true}}
|
|
503
|
+
```
|
|
504
|
+
Response:
|
|
505
|
+
```json
|
|
506
|
+
{"selector": "li.item", "total": 42}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### queryAll
|
|
510
|
+
```json
|
|
511
|
+
{"queryAll": {"title": "h1", "links": "a", "buttons": {"role": "button"}}}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### get — Text extraction (default)
|
|
515
|
+
```json
|
|
516
|
+
{"get": "#content"}
|
|
517
|
+
{"get": {"selector": "#content"}}
|
|
518
|
+
{"get": {"selector": "#content", "mode": "text"}}
|
|
519
|
+
```
|
|
520
|
+
Response:
|
|
521
|
+
```json
|
|
522
|
+
{"text": "Extracted content text", "mode": "text"}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### get — HTML extraction
|
|
526
|
+
```json
|
|
527
|
+
{"get": {"selector": "#content", "mode": "html"}}
|
|
528
|
+
```
|
|
529
|
+
Response:
|
|
530
|
+
```json
|
|
531
|
+
{"html": "<div>...</div>", "tagName": "DIV", "length": 1245, "mode": "html"}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### get — Form value extraction
|
|
535
|
+
```json
|
|
536
|
+
{"get": {"selector": "#form", "mode": "value"}}
|
|
537
|
+
```
|
|
538
|
+
Response:
|
|
539
|
+
```json
|
|
540
|
+
{
|
|
541
|
+
"selector": "#checkout-form",
|
|
542
|
+
"action": "/api/checkout",
|
|
543
|
+
"method": "POST",
|
|
544
|
+
"fields": [
|
|
545
|
+
{"name": "email", "type": "email", "value": "user@example.com", "label": "Email Address", "required": true, "valid": true}
|
|
546
|
+
],
|
|
547
|
+
"valid": true,
|
|
548
|
+
"fieldCount": 3,
|
|
549
|
+
"mode": "value"
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### get — Bounding box extraction
|
|
554
|
+
```json
|
|
555
|
+
{"get": {"selector": "#element", "mode": "box"}}
|
|
556
|
+
{"get": {"ref": "f0s1e1", "mode": "box"}}
|
|
557
|
+
```
|
|
558
|
+
Response:
|
|
559
|
+
```json
|
|
560
|
+
{"x": 100, "y": 200, "width": 150, "height": 40, "center": {"x": 175, "y": 220}, "mode": "box"}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### get — Attributes extraction
|
|
564
|
+
```json
|
|
565
|
+
{"get": {"selector": "#link", "mode": "attributes"}}
|
|
566
|
+
```
|
|
567
|
+
Response:
|
|
568
|
+
```json
|
|
569
|
+
{"attributes": {"href": "/page", "class": "link", "id": "main-link"}, "mode": "attributes"}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### get — Table extraction (auto-detected)
|
|
573
|
+
```json
|
|
574
|
+
{"get": "table.results"}
|
|
575
|
+
{"get": {"selector": "#data-grid", "type": "table"}}
|
|
576
|
+
```
|
|
577
|
+
Response:
|
|
578
|
+
```json
|
|
579
|
+
{
|
|
580
|
+
"type": "table",
|
|
581
|
+
"headers": ["Name", "Email", "Status"],
|
|
582
|
+
"rows": [
|
|
583
|
+
["John Doe", "john@example.com", "Active"],
|
|
584
|
+
["Jane Smith", "jane@example.com", "Pending"]
|
|
585
|
+
],
|
|
586
|
+
"rowCount": 2,
|
|
587
|
+
"mode": "text"
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### get — List extraction (auto-detected)
|
|
592
|
+
```json
|
|
593
|
+
{"get": "ul.items"}
|
|
594
|
+
{"get": {"selector": "#nav-links", "type": "list"}}
|
|
595
|
+
```
|
|
596
|
+
Response:
|
|
597
|
+
```json
|
|
598
|
+
{
|
|
599
|
+
"type": "list",
|
|
600
|
+
"items": ["Home", "About", "Contact"],
|
|
601
|
+
"itemCount": 3,
|
|
602
|
+
"mode": "text"
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### getUrl
|
|
607
|
+
```json
|
|
608
|
+
{"getUrl": true}
|
|
609
|
+
```
|
|
610
|
+
Response:
|
|
611
|
+
```json
|
|
612
|
+
{"url": "https://example.com/page"}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### getTitle
|
|
616
|
+
```json
|
|
617
|
+
{"getTitle": true}
|
|
618
|
+
```
|
|
619
|
+
Response:
|
|
620
|
+
```json
|
|
621
|
+
{"title": "Page Title"}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### inspect
|
|
625
|
+
```json
|
|
626
|
+
{"inspect": true}
|
|
627
|
+
{"inspect": {"selectors": [".item"], "limit": 3}}
|
|
628
|
+
```
|
|
629
|
+
Response:
|
|
630
|
+
```json
|
|
631
|
+
{
|
|
632
|
+
"title": "Page Title",
|
|
633
|
+
"url": "https://example.com",
|
|
634
|
+
"elements": {
|
|
635
|
+
"a": 12,
|
|
636
|
+
"button": 5,
|
|
637
|
+
"input": 3,
|
|
638
|
+
"textarea": 1,
|
|
639
|
+
"select": 0,
|
|
640
|
+
"h1": 1,
|
|
641
|
+
"h2": 3,
|
|
642
|
+
"h3": 0,
|
|
643
|
+
"img": 8,
|
|
644
|
+
"form": 1
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### elementsAt — Single point
|
|
650
|
+
```json
|
|
651
|
+
{"elementsAt": {"x": 600, "y": 200}}
|
|
652
|
+
```
|
|
653
|
+
Response:
|
|
654
|
+
```json
|
|
655
|
+
{
|
|
656
|
+
"ref": "f0s1e5",
|
|
657
|
+
"existing": false,
|
|
658
|
+
"tag": "BUTTON",
|
|
659
|
+
"selector": "#submit-btn",
|
|
660
|
+
"clickable": true,
|
|
661
|
+
"role": "button",
|
|
662
|
+
"name": "Submit",
|
|
663
|
+
"box": {"x": 580, "y": 190, "width": 100, "height": 40}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### elementsAt — Batch
|
|
668
|
+
```json
|
|
669
|
+
{"elementsAt": [{"x": 100, "y": 200}, {"x": 300, "y": 400}, {"x": 500, "y": 150}]}
|
|
670
|
+
```
|
|
671
|
+
Response:
|
|
672
|
+
```json
|
|
673
|
+
{
|
|
674
|
+
"count": 3,
|
|
675
|
+
"elements": [
|
|
676
|
+
{"x": 100, "y": 200, "ref": "f0s1e1", "tag": "BUTTON", "selector": "#btn1", "clickable": true},
|
|
677
|
+
{"x": 300, "y": 400, "ref": "f0s1e2", "tag": "DIV", "selector": "div.card", "clickable": false},
|
|
678
|
+
{"x": 500, "y": 150, "error": "No element at this coordinate"}
|
|
679
|
+
]
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### elementsAt — Nearby search
|
|
684
|
+
```json
|
|
685
|
+
{"elementsAt": {"x": 400, "y": 300, "radius": 100}}
|
|
686
|
+
{"elementsAt": {"x": 400, "y": 300, "radius": 75, "limit": 10}}
|
|
687
|
+
```
|
|
688
|
+
Response:
|
|
689
|
+
```json
|
|
690
|
+
{
|
|
691
|
+
"center": {"x": 400, "y": 300},
|
|
692
|
+
"radius": 100,
|
|
693
|
+
"count": 5,
|
|
694
|
+
"elements": [
|
|
695
|
+
{"ref": "f0s1e1", "tag": "BUTTON", "selector": "#nearby-btn", "clickable": true, "distance": 12},
|
|
696
|
+
{"ref": "f0s1e2", "tag": "SPAN", "selector": "span.label", "clickable": false, "distance": 28}
|
|
697
|
+
]
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## Waiting & Polling
|
|
704
|
+
|
|
705
|
+
### wait — Element
|
|
706
|
+
```json
|
|
707
|
+
{"wait": "#content"}
|
|
708
|
+
{"wait": {"selector": "#loading", "hidden": true}}
|
|
709
|
+
{"wait": {"selector": ".item", "minCount": 10}}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### wait — Text
|
|
713
|
+
```json
|
|
714
|
+
{"wait": {"text": "Welcome"}}
|
|
715
|
+
{"wait": {"text": "welcome", "caseSensitive": false}}
|
|
716
|
+
{"wait": {"textRegex": "Order #[A-Z0-9]+"}}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### wait — URL
|
|
720
|
+
```json
|
|
721
|
+
{"wait": {"urlContains": "/success"}}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
### sleep
|
|
725
|
+
```json
|
|
726
|
+
{"sleep": 2000}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### poll
|
|
730
|
+
```json
|
|
731
|
+
{"poll": "() => document.querySelector('.loaded') !== null"}
|
|
732
|
+
{"poll": "() => document.readyState === 'complete'"}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
Object form:
|
|
736
|
+
```json
|
|
737
|
+
{"poll": {"fn": "() => !document.querySelector('.spinner') && document.querySelector('.results')?.children.length > 0", "interval": 100, "timeout": 10000}}
|
|
738
|
+
```
|
|
739
|
+
Response:
|
|
740
|
+
```json
|
|
741
|
+
{"resolved": true, "value": true, "elapsed": 2340}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
## Scripting
|
|
747
|
+
|
|
748
|
+
### pageFunction
|
|
749
|
+
```json
|
|
750
|
+
{"pageFunction": "() => document.title"}
|
|
751
|
+
{"pageFunction": "document.title"}
|
|
752
|
+
{"pageFunction": "(document) => [...document.querySelectorAll('.item')].map(i => ({text: i.textContent, href: i.href}))"}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Object form:
|
|
756
|
+
```json
|
|
757
|
+
{"pageFunction": {"fn": "(refs) => refs.size", "refs": true}}
|
|
758
|
+
{"pageFunction": {"fn": "() => document.querySelectorAll('button').length", "timeout": 5000}}
|
|
759
|
+
{"pageFunction": {"expression": "fetch('/api').then(r=>r.json())"}}
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
### Typed Return Values
|
|
763
|
+
- Numbers: `{type: "number", repr: "Infinity|NaN|-Infinity"}`
|
|
764
|
+
- Date: `{type: "Date", value: "ISO string", timestamp: N}`
|
|
765
|
+
- Map: `{type: "Map", size: N, entries: [...]}`
|
|
766
|
+
- Set: `{type: "Set", size: N, values: [...]}`
|
|
767
|
+
- Element: `{type: "Element", tagName, id, className, textContent, isConnected}`
|
|
768
|
+
- NodeList: `{type: "NodeList", length: N, items: [...]}`
|
|
769
|
+
|
|
770
|
+
### assert
|
|
771
|
+
```json
|
|
772
|
+
{"assert": {"url": {"contains": "/success"}}}
|
|
773
|
+
{"assert": {"url": {"equals": "https://example.com/done"}}}
|
|
774
|
+
{"assert": {"url": {"startsWith": "https://"}}}
|
|
775
|
+
{"assert": {"url": {"matches": "^https://.*\\.example\\.com"}}}
|
|
776
|
+
{"assert": {"text": "Welcome"}}
|
|
777
|
+
{"assert": {"selector": "h1", "text": "Title", "caseSensitive": false}}
|
|
778
|
+
```
|
|
779
|
+
Response:
|
|
780
|
+
```json
|
|
781
|
+
{
|
|
782
|
+
"passed": true,
|
|
783
|
+
"assertions": [
|
|
784
|
+
{"type": "url", "actual": "https://example.com/success", "expected": {"contains": "/success"}, "passed": true}
|
|
785
|
+
]
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
## Page Control
|
|
792
|
+
|
|
793
|
+
### scroll
|
|
794
|
+
```json
|
|
795
|
+
{"scroll": "top"}
|
|
796
|
+
{"scroll": "bottom"}
|
|
797
|
+
{"scroll": "up"}
|
|
798
|
+
{"scroll": "down"}
|
|
799
|
+
{"scroll": "#element"}
|
|
800
|
+
{"scroll": "f0s1e4"}
|
|
801
|
+
{"scroll": {"deltaY": 500}}
|
|
802
|
+
{"scroll": {"x": 0, "y": 1000}}
|
|
803
|
+
```
|
|
804
|
+
Response:
|
|
805
|
+
```json
|
|
806
|
+
{"scrollX": 0, "scrollY": 1000}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### frame — List frames
|
|
810
|
+
```json
|
|
811
|
+
{"frame": {"list": true}}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### frame — Switch to frame
|
|
815
|
+
```json
|
|
816
|
+
{"frame": "iframe#content"}
|
|
817
|
+
{"frame": 0}
|
|
818
|
+
{"frame": {"name": "myFrame"}}
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
### frame — Return to main frame
|
|
822
|
+
```json
|
|
823
|
+
{"frame": "top"}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### frame — Workflow (switch -> interact -> return)
|
|
827
|
+
```json
|
|
828
|
+
{"steps": [
|
|
829
|
+
{"frame": "iframe#editor"},
|
|
830
|
+
{"fill": {"selector": "#input", "value": "Hello"}},
|
|
831
|
+
{"click": "#save"},
|
|
832
|
+
{"frame": "top"}
|
|
833
|
+
]}
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### viewport
|
|
837
|
+
```json
|
|
838
|
+
{"viewport": "iphone-14"}
|
|
839
|
+
{"viewport": "pixel-7"}
|
|
840
|
+
{"viewport": "macbook-pro-14"}
|
|
841
|
+
{"viewport": "desktop-hd"}
|
|
842
|
+
{"viewport": {"width": 1280, "height": 720}}
|
|
843
|
+
{"viewport": {"width": 375, "height": 667, "mobile": true, "hasTouch": true, "isLandscape": true}}
|
|
844
|
+
```
|
|
845
|
+
Response:
|
|
846
|
+
```json
|
|
847
|
+
{"width": 390, "height": 844, "deviceScaleFactor": 3}
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## Browser & Tabs
|
|
853
|
+
|
|
854
|
+
### chromeStatus
|
|
855
|
+
```json
|
|
856
|
+
{"steps":[{"chromeStatus":true}]}
|
|
857
|
+
{"steps":[{"chromeStatus":{"port":9333,"headless":true}}]}
|
|
858
|
+
{"steps":[{"chromeStatus":{"autoLaunch":false}}]}
|
|
859
|
+
```
|
|
860
|
+
Response:
|
|
861
|
+
```json
|
|
862
|
+
{
|
|
863
|
+
"status": "ok",
|
|
864
|
+
"chrome": {
|
|
865
|
+
"running": true,
|
|
866
|
+
"launched": true,
|
|
867
|
+
"version": "Chrome/120.0.6099.109",
|
|
868
|
+
"port": 9222,
|
|
869
|
+
"tabs": [{"targetId": "ABC123", "url": "about:blank", "title": ""}]
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### listTabs
|
|
875
|
+
```json
|
|
876
|
+
{"steps":[{"listTabs":true}]}
|
|
877
|
+
```
|
|
878
|
+
Response:
|
|
879
|
+
```json
|
|
880
|
+
{"count": 2, "tabs": [{"targetId": "ABC123", "url": "https://google.com", "title": "Google", "alias": "t1"}]}
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
### closeTab
|
|
884
|
+
```json
|
|
885
|
+
{"closeTab": "t1"}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
### cookies
|
|
889
|
+
```json
|
|
890
|
+
{"cookies": {"get": true}}
|
|
891
|
+
{"cookies": {"get": ["https://other-domain.com"], "name": "session_id"}}
|
|
892
|
+
{"cookies": {"set": [{"name": "token", "value": "abc", "domain": "example.com", "expires": "7d"}]}}
|
|
893
|
+
{"cookies": {"delete": "session_id"}}
|
|
894
|
+
{"cookies": {"delete": "session_id", "domain": "example.com"}}
|
|
895
|
+
{"cookies": {"clear": true}}
|
|
896
|
+
{"cookies": {"clear": true, "domain": "example.com"}}
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
### console
|
|
900
|
+
```json
|
|
901
|
+
{"console": true}
|
|
902
|
+
{"console": {"level": "error", "limit": 20, "stackTrace": true}}
|
|
903
|
+
{"console": {"type": "exception", "limit": 10}}
|
|
904
|
+
{"console": {"clear": true}}
|
|
905
|
+
```
|
|
906
|
+
Response:
|
|
907
|
+
```json
|
|
908
|
+
{
|
|
909
|
+
"total": 5,
|
|
910
|
+
"showing": 5,
|
|
911
|
+
"messages": [
|
|
912
|
+
{"level": "error", "text": "TypeError: x is undefined", "type": "console", "url": "app.js", "line": 42, "timestamp": 1700000000}
|
|
913
|
+
]
|
|
914
|
+
}
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
### pdf
|
|
918
|
+
```json
|
|
919
|
+
{"pdf": "report.pdf"}
|
|
920
|
+
{"pdf": {"path": "/absolute/path/report.pdf", "landscape": true, "printBackground": true}}
|
|
921
|
+
{"pdf": {"path": "element.pdf", "selector": "#chart"}}
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## Action Hooks
|
|
927
|
+
|
|
928
|
+
### readyWhen
|
|
929
|
+
```json
|
|
930
|
+
{"click": {"ref": "f0s1e5", "readyWhen": "() => !document.querySelector('.loading')"}}
|
|
931
|
+
{"fill": {"selector": "#email", "value": "test@test.com", "readyWhen": "() => document.querySelector('#email').offsetHeight > 0"}}
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
### settledWhen
|
|
935
|
+
```json
|
|
936
|
+
{"click": {"ref": "f0s1e5", "settledWhen": "() => document.querySelector('.results')?.children.length > 0"}}
|
|
937
|
+
{"click": {"selector": "#nav-link", "settledWhen": "() => location.href.includes('/results')"}}
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
### observe
|
|
941
|
+
```json
|
|
942
|
+
{"click": {"ref": "f0s1e5", "observe": "() => ({url: location.href, count: document.querySelectorAll('.item').length})"}}
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
### Combined hooks
|
|
946
|
+
```json
|
|
947
|
+
{"click": {
|
|
948
|
+
"ref": "f0s1e5",
|
|
949
|
+
"readyWhen": "() => !document.querySelector('.loading')",
|
|
950
|
+
"settledWhen": "() => document.querySelectorAll('.result').length > 0",
|
|
951
|
+
"observe": "() => document.querySelectorAll('.result').length"
|
|
952
|
+
}}
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Site Profiles
|
|
958
|
+
|
|
959
|
+
### writeSiteProfile
|
|
960
|
+
```json
|
|
961
|
+
{"writeSiteProfile": {"domain": "github.com", "content": "# github.com\nUpdated: 2025-01-15\n\n## Environment\n- React SPA\n..."}}
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### readSiteProfile
|
|
965
|
+
```json
|
|
966
|
+
{"readSiteProfile": "github.com"}
|
|
967
|
+
{"readSiteProfile": {"domain": "github.com"}}
|
|
968
|
+
```
|
|
969
|
+
Response (found):
|
|
970
|
+
```json
|
|
971
|
+
{"found": true, "domain": "github.com", "content": "# github.com\n..."}
|
|
972
|
+
```
|
|
973
|
+
Response (not found):
|
|
974
|
+
```json
|
|
975
|
+
{"found": false, "domain": "github.com"}
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
### Full Profile Template
|
|
979
|
+
```markdown
|
|
980
|
+
# example.com
|
|
981
|
+
Updated: 2025-01-15 | Fingerprint: React 18, Next.js
|
|
982
|
+
|
|
983
|
+
## Environment
|
|
984
|
+
- React 18.x, Next.js (SSR)
|
|
985
|
+
- SPA with pushState navigation
|
|
986
|
+
- Has <main> element: #__next > main
|
|
987
|
+
|
|
988
|
+
## Quirks
|
|
989
|
+
- Turbo intercepts link clicks — use settledWhen with URL check
|
|
990
|
+
- File tree uses virtualization — only visible rows in DOM
|
|
991
|
+
|
|
992
|
+
## Strategies
|
|
993
|
+
### fill (React controlled inputs)
|
|
994
|
+
Use `react: true` option or settledWhen to verify input accepted.
|
|
995
|
+
|
|
996
|
+
## Regions
|
|
997
|
+
- mainContent: `main, [role="main"]`
|
|
998
|
+
- navigation: `.nav-bar`
|
|
999
|
+
|
|
1000
|
+
## Recipes
|
|
1001
|
+
### Login
|
|
1002
|
+
{"steps": [
|
|
1003
|
+
{"fill": {"selector": "#username", "value": "{{user}}"}},
|
|
1004
|
+
{"fill": {"selector": "#password", "value": "{{pass}}"}},
|
|
1005
|
+
{"click": "#login"},
|
|
1006
|
+
{"wait": {"urlContains": "/dashboard"}}
|
|
1007
|
+
]}
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
1012
|
+
## Optional Steps
|
|
1013
|
+
|
|
1014
|
+
```json
|
|
1015
|
+
{"click": "#maybe-exists", "optional": true}
|
|
1016
|
+
```
|
|
1017
|
+
Response when element not found:
|
|
1018
|
+
```json
|
|
1019
|
+
{"action": "click", "status": "skipped", "error": "Element not found"}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## Shell Tips
|
|
1025
|
+
|
|
1026
|
+
### Heredoc (avoids quote escaping)
|
|
1027
|
+
```bash
|
|
1028
|
+
node scripts/cdp-skill.js <<'EOF'
|
|
1029
|
+
{"steps":[{"pageFunction":"document.querySelectorAll('button').length"}]}
|
|
1030
|
+
EOF
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
### Pipe from file
|
|
1034
|
+
```bash
|
|
1035
|
+
cat steps.json | node scripts/cdp-skill.js
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### Debug logging
|
|
1039
|
+
```bash
|
|
1040
|
+
node scripts/cdp-skill.js --debug '{"steps":[{"goto":"https://google.com"}]}'
|
|
1041
|
+
```
|
package/SKILL.md
CHANGED
|
@@ -33,12 +33,12 @@ Tab IDs (t1, t2, ...) persist across CLI invocations. Chrome auto-launches if no
|
|
|
33
33
|
**Output fields:**
|
|
34
34
|
- `status`: "ok" or "error"
|
|
35
35
|
- `tab`: short tab ID (e.g. "t1")
|
|
36
|
-
- `siteProfile`:
|
|
36
|
+
- `siteProfile`: path to site profile file (after goto/newTab to known site)
|
|
37
37
|
- `actionRequired`: `{action, domain, message}` — **MUST be handled immediately** before continuing (see Site Profiles)
|
|
38
38
|
- `context`: `{url, title, scroll: {y, percent}, viewport: {width, height}, activeElement?, modal?}`
|
|
39
39
|
- `screenshot`: path to after-screenshot (auto-captured on every visual action)
|
|
40
40
|
- `fullSnapshot`: path to full-page accessibility snapshot file
|
|
41
|
-
- `viewportSnapshot`:
|
|
41
|
+
- `viewportSnapshot`: path to viewport-only accessibility snapshot file
|
|
42
42
|
- `changes`: `{summary, added[], removed[], changed[]}` — viewport diff on same-page interactions
|
|
43
43
|
- `navigated`: true when URL pathname changed
|
|
44
44
|
- `console`: `{errors, warnings, messages[]}` — captured errors/warnings
|
package/install.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdp-skill",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.19",
|
|
4
4
|
"description": "Browser automation skill using Chrome DevTools Protocol for Claude Code and AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "scripts/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"install.js",
|
|
25
25
|
"uninstall.js",
|
|
26
26
|
"SKILL.md",
|
|
27
|
-
"
|
|
27
|
+
"EXAMPLES.md",
|
|
28
28
|
"scripts/"
|
|
29
29
|
],
|
|
30
30
|
"repository": {
|
package/scripts/cdp-skill.js
CHANGED
|
@@ -785,15 +785,15 @@ async function main() {
|
|
|
785
785
|
truncated: result.truncated
|
|
786
786
|
};
|
|
787
787
|
|
|
788
|
-
// Remove null/undefined fields for compactness
|
|
788
|
+
// Remove null/undefined/false-y fields for compactness
|
|
789
789
|
if (!output.siteProfile) delete output.siteProfile;
|
|
790
790
|
if (!output.actionRequired) delete output.actionRequired;
|
|
791
|
-
if (output.navigated
|
|
791
|
+
if (!output.navigated) delete output.navigated;
|
|
792
792
|
if (!output.fullSnapshot) delete output.fullSnapshot;
|
|
793
793
|
if (!output.context) delete output.context;
|
|
794
794
|
if (!output.changes) delete output.changes;
|
|
795
795
|
if (!output.viewportSnapshot) delete output.viewportSnapshot;
|
|
796
|
-
if (output.truncated
|
|
796
|
+
if (!output.truncated) delete output.truncated;
|
|
797
797
|
if (!output.screenshot) delete output.screenshot;
|
|
798
798
|
if (!output.console) delete output.console;
|
|
799
799
|
if (output.errors.length === 0) delete output.errors;
|
|
@@ -408,13 +408,14 @@ function sanitizeDomain(domain) {
|
|
|
408
408
|
* Load a site profile for the given domain.
|
|
409
409
|
*
|
|
410
410
|
* @param {string} domain - hostname (e.g. "github.com")
|
|
411
|
-
* @returns {Promise<string|null>} profile
|
|
411
|
+
* @returns {Promise<string|null>} profile file path or null
|
|
412
412
|
*/
|
|
413
413
|
export async function loadSiteProfile(domain) {
|
|
414
414
|
const clean = sanitizeDomain(domain);
|
|
415
415
|
const profilePath = path.join(SITES_DIR, `${clean}.md`);
|
|
416
416
|
try {
|
|
417
|
-
|
|
417
|
+
await fs.access(profilePath);
|
|
418
|
+
return profilePath;
|
|
418
419
|
} catch {
|
|
419
420
|
return null;
|
|
420
421
|
}
|
|
@@ -452,9 +453,9 @@ export async function executeReadSiteProfile(params) {
|
|
|
452
453
|
throw new Error('readSiteProfile requires a domain string');
|
|
453
454
|
}
|
|
454
455
|
|
|
455
|
-
const
|
|
456
|
-
if (
|
|
457
|
-
return { found: true, domain,
|
|
456
|
+
const profilePath = await loadSiteProfile(domain);
|
|
457
|
+
if (profilePath) {
|
|
458
|
+
return { found: true, domain, path: profilePath };
|
|
458
459
|
}
|
|
459
460
|
return { found: false, domain };
|
|
460
461
|
}
|
|
@@ -668,7 +668,11 @@ export async function runSteps(deps, steps, options = {}) {
|
|
|
668
668
|
result.navigated = navigated;
|
|
669
669
|
result.fullSnapshot = fullSnapshotPath;
|
|
670
670
|
result.context = afterContext;
|
|
671
|
-
|
|
671
|
+
|
|
672
|
+
// Write viewport snapshot to file to keep response concise
|
|
673
|
+
const viewportPath = await resolveTempPath(`${options.tabAlias || 'command'}.viewport.yaml`, '.yaml');
|
|
674
|
+
await fs.writeFile(viewportPath, afterViewport.yaml || '', 'utf8');
|
|
675
|
+
result.viewportSnapshot = viewportPath;
|
|
672
676
|
result.truncated = afterViewport.truncated || false;
|
|
673
677
|
|
|
674
678
|
if (!navigated && beforeViewport?.yaml) {
|
|
@@ -680,7 +680,7 @@ describe('step-executors goto profile integration', () => {
|
|
|
680
680
|
assert.strictEqual(result.action, 'goto');
|
|
681
681
|
assert.strictEqual(result.status, 'ok');
|
|
682
682
|
assert.ok(result.siteProfile);
|
|
683
|
-
assert.ok(result.siteProfile.
|
|
683
|
+
assert.ok(result.siteProfile.endsWith('.md'), 'siteProfile should be a file path');
|
|
684
684
|
});
|
|
685
685
|
|
|
686
686
|
it('should return profileAvailable false on goto for unknown domain', async () => {
|