electron-debug-skill 1.0.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/LICENSE +21 -0
- package/README.md +392 -0
- package/bin/cli.js +2 -0
- package/bin/daemon.js +2 -0
- package/dist/CDPClient.js +258 -0
- package/dist/daemon.js +407 -0
- package/dist/index.js +933 -0
- package/dist/types.js +2 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 electron-debug
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# electron-debug
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
[](https://skills.sh)
|
|
6
|
+
|
|
7
|
+
> Claude Code Skill for debugging Electron applications using Chrome DevTools Protocol (CDP)
|
|
8
|
+
|
|
9
|
+
[**中文文档**](README_zh.md) | [English](README.md)
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Daemon Mode** - Background process maintains CDP connection for continuous testing
|
|
14
|
+
- **Full CDP Support** - Console, Network, DOM, Screenshot, Element Click, etc.
|
|
15
|
+
- **AI-Assisted Diagnosis** - Auto-collect debugging info when describing issues
|
|
16
|
+
- **Flexible Port Configuration** - Custom debugging port support
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- Node.js 18+
|
|
21
|
+
- Claude Code
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### Option 1: via skills.sh (Recommended)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx skills add kvenLin/electron-debug@electron-debug
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Option 2: Manual Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Clone the repo
|
|
35
|
+
git clone https://github.com/kvenLin/electron-debug.git
|
|
36
|
+
cd electron-debug
|
|
37
|
+
|
|
38
|
+
# Install dependencies
|
|
39
|
+
npm install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Option 3: Development (Symlink)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ln -s ~/path/to/electron-debug ~/.claude/skills/electron-debug
|
|
46
|
+
/reload-plugins
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### 1. Start Electron App with Debug Port
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd your-electron-app
|
|
55
|
+
electron . --remote-debugging-port=9333
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Or add to `package.json`:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"scripts": {
|
|
63
|
+
"debug": "electron . --remote-debugging-port=9333"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run debug
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Connect
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
/electron-debug connect --electron-port 9333
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. Debug
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# List pages
|
|
82
|
+
/electron-debug list-pages
|
|
83
|
+
|
|
84
|
+
# Screenshot
|
|
85
|
+
/electron-debug screenshot
|
|
86
|
+
|
|
87
|
+
# Click element
|
|
88
|
+
/electron-debug click "#my-button"
|
|
89
|
+
|
|
90
|
+
# Execute JavaScript
|
|
91
|
+
/electron-debug eval "document.title"
|
|
92
|
+
|
|
93
|
+
# View console
|
|
94
|
+
/electron-debug console
|
|
95
|
+
|
|
96
|
+
# AI diagnosis
|
|
97
|
+
/electron-debug diagnose "button click not working"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 4. Disconnect
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
/electron-debug disconnect
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Command Reference
|
|
107
|
+
|
|
108
|
+
### Connection Management
|
|
109
|
+
|
|
110
|
+
| Command | Description |
|
|
111
|
+
|---------|-------------|
|
|
112
|
+
| `connect --electron-port <port>` | Start daemon and connect |
|
|
113
|
+
| `disconnect` | Disconnect and stop daemon |
|
|
114
|
+
| `status` | View connection status |
|
|
115
|
+
|
|
116
|
+
### Daemon Management
|
|
117
|
+
|
|
118
|
+
| Command | Description |
|
|
119
|
+
|---------|-------------|
|
|
120
|
+
| `daemon start --electron-port <port>` | Start daemon |
|
|
121
|
+
| `daemon stop` | Stop daemon |
|
|
122
|
+
| `daemon status` | View daemon status |
|
|
123
|
+
|
|
124
|
+
### Page Operations
|
|
125
|
+
|
|
126
|
+
| Command | Description |
|
|
127
|
+
|---------|-------------|
|
|
128
|
+
| `list-pages` | List all debuggable pages |
|
|
129
|
+
| `switch-page --id <id>` | Switch to another page |
|
|
130
|
+
| `screenshot` | Take screenshot (base64) |
|
|
131
|
+
| `screenshot --path ./screenshot.png` | Save screenshot to file |
|
|
132
|
+
|
|
133
|
+
### Element Interaction
|
|
134
|
+
|
|
135
|
+
| Command | Description |
|
|
136
|
+
|---------|-------------|
|
|
137
|
+
| `click "#selector"` | Click element |
|
|
138
|
+
| `eval "javascript"` | Execute JavaScript expression |
|
|
139
|
+
| `dom --selector "#selector"` | Query DOM element |
|
|
140
|
+
|
|
141
|
+
### Monitoring
|
|
142
|
+
|
|
143
|
+
| Command | Description |
|
|
144
|
+
|---------|-------------|
|
|
145
|
+
| `console` | View console logs |
|
|
146
|
+
| `console --watch` | Watch console messages |
|
|
147
|
+
| `network --watch` | Watch network requests |
|
|
148
|
+
|
|
149
|
+
### Diagnosis
|
|
150
|
+
|
|
151
|
+
| Command | Description |
|
|
152
|
+
|---------|-------------|
|
|
153
|
+
| `diagnose "<problem>"` | AI-assisted diagnosis |
|
|
154
|
+
|
|
155
|
+
## Daemon Mode
|
|
156
|
+
|
|
157
|
+
electron-debug uses a background daemon process to maintain CDP connection.
|
|
158
|
+
|
|
159
|
+
### Architecture
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
163
|
+
│ electron-debug-daemon (background, localhost:9229) │
|
|
164
|
+
│ - Maintains WebSocket CDP connection to Electron │
|
|
165
|
+
│ - Manages current active target/page │
|
|
166
|
+
└─────────────────────────────────────────────────────────────┘
|
|
167
|
+
↑ HTTP (localhost:9229)
|
|
168
|
+
│
|
|
169
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
170
|
+
│ electron-debug CLI / Skill │
|
|
171
|
+
│ - Command line client, sends HTTP requests to daemon │
|
|
172
|
+
└─────────────────────────────────────────────────────────────┘
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Port Reference
|
|
176
|
+
|
|
177
|
+
| Port | Purpose |
|
|
178
|
+
|------|---------|
|
|
179
|
+
| 9229 | Daemon HTTP server (default) |
|
|
180
|
+
| 9333 | Electron CDP port (configurable) |
|
|
181
|
+
|
|
182
|
+
## CLI Direct Usage
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Start daemon
|
|
186
|
+
node bin/daemon.js --electron-port 9333
|
|
187
|
+
|
|
188
|
+
# API calls
|
|
189
|
+
curl http://127.0.0.1:9229/status
|
|
190
|
+
curl -X POST http://127.0.0.1:9229/eval -d '{"expression":"document.title"}'
|
|
191
|
+
curl -X POST http://127.0.0.1:9229/click -d '{"selector":"#btn1"}'
|
|
192
|
+
curl http://127.0.0.1:9229/screenshot -o screenshot.png
|
|
193
|
+
|
|
194
|
+
# Stop daemon
|
|
195
|
+
curl -X DELETE http://127.0.0.1:9229/
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## HTTP API
|
|
199
|
+
|
|
200
|
+
| Method | Path | Description |
|
|
201
|
+
|--------|------|-------------|
|
|
202
|
+
| GET | `/status` | Get connection status |
|
|
203
|
+
| GET | `/targets` | List all pages |
|
|
204
|
+
| POST | `/connect` | Connect to Electron |
|
|
205
|
+
| POST | `/switch-target` | Switch page |
|
|
206
|
+
| POST | `/eval` | Execute JavaScript |
|
|
207
|
+
| GET | `/screenshot` | Get screenshot (PNG) |
|
|
208
|
+
| POST | `/screenshot` | Get screenshot (JSON) |
|
|
209
|
+
| POST | `/click` | Click element |
|
|
210
|
+
| GET | `/console` | Get console messages |
|
|
211
|
+
| POST | `/disconnect` | Disconnect |
|
|
212
|
+
| DELETE | `/` | Stop daemon |
|
|
213
|
+
|
|
214
|
+
## Comparison with chrome-devtools-mcp
|
|
215
|
+
|
|
216
|
+
| Feature | chrome-devtools-mcp | electron-debug |
|
|
217
|
+
|---------|---------------------|----------------|
|
|
218
|
+
| Type | MCP Server | Claude Code Skill |
|
|
219
|
+
| Stateful Connection | Not supported | Daemon mode supported |
|
|
220
|
+
| Port Configuration | Fixed 9222 | Configurable |
|
|
221
|
+
| Element Click | Not supported | Supported |
|
|
222
|
+
| Main Process Debugging | Not supported | Supported |
|
|
223
|
+
| AI Diagnosis | None | AI-assisted |
|
|
224
|
+
|
|
225
|
+
## Project Structure
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
electron-debug/
|
|
229
|
+
├── bin/
|
|
230
|
+
│ ├── cli.js # CLI client entry
|
|
231
|
+
│ └── daemon.js # Daemon service entry
|
|
232
|
+
├── dist/ # Compiled JavaScript
|
|
233
|
+
├── skills/
|
|
234
|
+
│ └── electron-debug/
|
|
235
|
+
│ └── SKILL.md # Claude Code Skill definition
|
|
236
|
+
├── node_modules/ # Dependencies
|
|
237
|
+
├── package.json
|
|
238
|
+
├── README.md # English documentation
|
|
239
|
+
└── README_zh.md # Chinese documentation
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Troubleshooting
|
|
243
|
+
|
|
244
|
+
### Connection Failed?
|
|
245
|
+
|
|
246
|
+
Make sure Electron is started with remote debugging:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
electron . --remote-debugging-port=9333
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Daemon Port Occupied?
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Check port
|
|
256
|
+
lsof -i :9229
|
|
257
|
+
|
|
258
|
+
# Kill process
|
|
259
|
+
kill <PID>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Screenshot Returns JSON Instead of Image?
|
|
263
|
+
|
|
264
|
+
Use `GET /screenshot` instead of `POST /screenshot`.
|
|
265
|
+
|
|
266
|
+
### Cannot Find Skill After Install?
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Check installed skills
|
|
270
|
+
npx skills list
|
|
271
|
+
|
|
272
|
+
# Reload plugins
|
|
273
|
+
/reload-plugins
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Permission Denied (npm install)?
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
sudo npm install
|
|
280
|
+
# or
|
|
281
|
+
npm install --prefix ~/.local
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Quick Reference
|
|
285
|
+
|
|
286
|
+
| Task | Command |
|
|
287
|
+
|------|---------|
|
|
288
|
+
| Connect | `/electron-debug connect --electron-port 9333` |
|
|
289
|
+
| Screenshot | `/electron-debug screenshot` |
|
|
290
|
+
| Click | `/electron-debug click "#btn"` |
|
|
291
|
+
| Console | `/electron-debug console` |
|
|
292
|
+
| Diagnose | `/electron-debug diagnose "issue description"` |
|
|
293
|
+
| Disconnect | `/electron-debug disconnect` |
|
|
294
|
+
|
|
295
|
+
## Everyday Examples (Colloquial)
|
|
296
|
+
|
|
297
|
+
### Scenario 1: Debug button click not working
|
|
298
|
+
|
|
299
|
+
**User asks Claude:**
|
|
300
|
+
> "I clicked the submit button in my Electron app but nothing happened. Can you help me figure out why?"
|
|
301
|
+
|
|
302
|
+
**Claude dispatches skill tools automatically:**
|
|
303
|
+
1. Connect to Electron: `/electron-debug connect --electron-port 9333`
|
|
304
|
+
2. Take screenshot to see page state
|
|
305
|
+
3. Click that button
|
|
306
|
+
4. Take screenshot to compare before/after
|
|
307
|
+
5. Check console for errors
|
|
308
|
+
6. Inspect DOM to see button state
|
|
309
|
+
7. AI diagnose possible causes
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### Scenario 2: Troubleshoot white screen
|
|
314
|
+
|
|
315
|
+
**User asks Claude:**
|
|
316
|
+
> "My Electron app shows a white screen when it opens. Can you help me troubleshoot?"
|
|
317
|
+
|
|
318
|
+
**Claude dispatches skill tools automatically:**
|
|
319
|
+
1. Connect to Electron
|
|
320
|
+
2. Take screenshot to confirm white screen
|
|
321
|
+
3. Execute JS to check `document.body.innerHTML` and see DOM tree
|
|
322
|
+
4. Check console errors
|
|
323
|
+
5. Check if network requests succeeded
|
|
324
|
+
6. Provide diagnosis
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### Scenario 3: Analyze page load performance
|
|
329
|
+
|
|
330
|
+
**User asks Claude:**
|
|
331
|
+
> "This Electron page loads really slowly. Can you help me figure out where it's getting stuck?"
|
|
332
|
+
|
|
333
|
+
**Claude dispatches skill tools automatically:**
|
|
334
|
+
1. Connect to Electron
|
|
335
|
+
2. Enable network monitoring: `/electron-debug network --watch`
|
|
336
|
+
3. Refresh the page
|
|
337
|
+
4. Analyze each request's duration
|
|
338
|
+
5. Find the slowest request
|
|
339
|
+
6. Provide optimization suggestions
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
### Scenario 4: Check form validation issues
|
|
344
|
+
|
|
345
|
+
**User asks Claude:**
|
|
346
|
+
> "I filled out the form but clicking submit does nothing. Is there a validation issue? Can you check?"
|
|
347
|
+
|
|
348
|
+
**Claude dispatches skill tools automatically:**
|
|
349
|
+
1. Connect to Electron
|
|
350
|
+
2. Execute `form.checkValidity()` to check form validation state
|
|
351
|
+
3. Check each input's validity details
|
|
352
|
+
4. Look for validation error logs in console
|
|
353
|
+
5. Tell you which field is failing validation
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Scenario 5: Capture login requests
|
|
358
|
+
|
|
359
|
+
**User asks Claude:**
|
|
360
|
+
> "I want to see what requests are sent when this Electron app logs in. Can you capture the network traffic?"
|
|
361
|
+
|
|
362
|
+
**Claude dispatches skill tools automatically:**
|
|
363
|
+
1. Connect to Electron
|
|
364
|
+
2. Enable network monitoring
|
|
365
|
+
3. User performs login operation on the page
|
|
366
|
+
4. Analyze captured requests
|
|
367
|
+
5. Display the login API's request parameters and response
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
### Scenario 6: Automated UI testing
|
|
372
|
+
|
|
373
|
+
**User asks Claude:**
|
|
374
|
+
> "Help me test the shopping cart: add the first 5 products to cart, then take a screenshot of the cart page"
|
|
375
|
+
|
|
376
|
+
**Claude dispatches skill tools automatically:**
|
|
377
|
+
1. Connect to Electron
|
|
378
|
+
2. Click "add to cart" for first product
|
|
379
|
+
3. Take screenshot
|
|
380
|
+
4. Click second product...
|
|
381
|
+
5. Until fifth
|
|
382
|
+
6. Screenshot the cart page
|
|
383
|
+
7. Check cart badge count
|
|
384
|
+
8. Disconnect
|
|
385
|
+
|
|
386
|
+
## Contributing
|
|
387
|
+
|
|
388
|
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
|
389
|
+
|
|
390
|
+
## License
|
|
391
|
+
|
|
392
|
+
MIT
|
package/bin/cli.js
ADDED
package/bin/daemon.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
export class CDPClient {
|
|
3
|
+
ws = null;
|
|
4
|
+
messageId = 0;
|
|
5
|
+
pendingRequests = new Map();
|
|
6
|
+
eventHandlers = new Map();
|
|
7
|
+
options;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = { host: '127.0.0.1', secure: false, ...options };
|
|
10
|
+
}
|
|
11
|
+
async connect() {
|
|
12
|
+
const { host, port, secure } = this.options;
|
|
13
|
+
const protocol = secure ? 'wss' : 'ws';
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
this.ws = new WebSocket(`${protocol}://${host}:${port}`);
|
|
16
|
+
this.ws.on('open', () => resolve());
|
|
17
|
+
this.ws.on('error', reject);
|
|
18
|
+
this.ws.on('message', (data) => this.handleMessage(data.toString()));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
// Connect directly to a target using its WebSocket URL
|
|
22
|
+
async connectToTarget(wsUrl) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
this.ws = new WebSocket(wsUrl);
|
|
25
|
+
this.ws.on('open', () => resolve());
|
|
26
|
+
this.ws.on('error', reject);
|
|
27
|
+
this.ws.on('message', (data) => this.handleMessage(data.toString()));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
disconnect() {
|
|
31
|
+
if (this.ws) {
|
|
32
|
+
this.ws.close();
|
|
33
|
+
this.ws = null;
|
|
34
|
+
}
|
|
35
|
+
this.pendingRequests.clear();
|
|
36
|
+
}
|
|
37
|
+
isConnected() {
|
|
38
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
39
|
+
}
|
|
40
|
+
async sendCommand(method, params) {
|
|
41
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
42
|
+
throw new Error('WebSocket not connected');
|
|
43
|
+
}
|
|
44
|
+
const id = ++this.messageId;
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
this.pendingRequests.set(id, {
|
|
47
|
+
resolve: (value) => resolve(value),
|
|
48
|
+
reject,
|
|
49
|
+
});
|
|
50
|
+
this.ws.send(JSON.stringify({ id, method, params }));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
on(event, handler) {
|
|
54
|
+
if (!this.eventHandlers.has(event)) {
|
|
55
|
+
this.eventHandlers.set(event, new Set());
|
|
56
|
+
}
|
|
57
|
+
this.eventHandlers.get(event).add(handler);
|
|
58
|
+
}
|
|
59
|
+
off(event, handler) {
|
|
60
|
+
this.eventHandlers.get(event)?.delete(handler);
|
|
61
|
+
}
|
|
62
|
+
handleMessage(data) {
|
|
63
|
+
const message = JSON.parse(data);
|
|
64
|
+
// Handle response
|
|
65
|
+
if ('id' in message) {
|
|
66
|
+
const pending = this.pendingRequests.get(message.id);
|
|
67
|
+
if (pending) {
|
|
68
|
+
this.pendingRequests.delete(message.id);
|
|
69
|
+
if (message.error) {
|
|
70
|
+
pending.reject(new Error(`${message.error.code}: ${message.error.message}`));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
pending.resolve(message.result);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Handle event
|
|
79
|
+
if ('method' in message) {
|
|
80
|
+
// Capture console messages for later retrieval
|
|
81
|
+
if (message.method === 'Console.messageAdded') {
|
|
82
|
+
// Console.messageAdded has nested "message" object: { message: { level, text, ... } }
|
|
83
|
+
const params = message.params;
|
|
84
|
+
this.consoleMessages.push({
|
|
85
|
+
type: (params.message?.level ?? params.message?.type ?? 'log'),
|
|
86
|
+
text: params.message?.text ?? '',
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Runtime.consoleAPICalled has direct params: { type, args: [{type, value}], ... }
|
|
91
|
+
if (message.method === 'Runtime.consoleAPICalled') {
|
|
92
|
+
const params = message.params;
|
|
93
|
+
const text = params.args
|
|
94
|
+
?.map((a) => a.value ?? a.description ?? String(a))
|
|
95
|
+
.join(' ') ?? '';
|
|
96
|
+
this.consoleMessages.push({
|
|
97
|
+
type: (params.type ?? 'log'),
|
|
98
|
+
text,
|
|
99
|
+
timestamp: params.timestamp ?? Date.now(),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const handlers = this.eventHandlers.get(message.method);
|
|
103
|
+
if (handlers) {
|
|
104
|
+
handlers.forEach((handler) => handler(message.params));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Target/Page management
|
|
109
|
+
async getTargets() {
|
|
110
|
+
const result = await this.sendCommand('Target.getTargets');
|
|
111
|
+
return result.targetInfos;
|
|
112
|
+
}
|
|
113
|
+
async attachToTarget(targetId) {
|
|
114
|
+
const result = await this.sendCommand('Target.attachToTarget', { targetId });
|
|
115
|
+
return result.sessionId;
|
|
116
|
+
}
|
|
117
|
+
// Page operations
|
|
118
|
+
async navigate(url) {
|
|
119
|
+
await this.sendCommand('Page.navigate', { url });
|
|
120
|
+
}
|
|
121
|
+
async captureScreenshot(format = 'png', quality) {
|
|
122
|
+
const result = await this.sendCommand('Page.captureScreenshot', {
|
|
123
|
+
format,
|
|
124
|
+
quality,
|
|
125
|
+
});
|
|
126
|
+
return { data: result.data, timestamp: Date.now() };
|
|
127
|
+
}
|
|
128
|
+
async captureFullScreenshot() {
|
|
129
|
+
const { data } = await this.sendCommand('Page.captureSnapshot');
|
|
130
|
+
return { data, timestamp: Date.now() };
|
|
131
|
+
}
|
|
132
|
+
async getPageInfo() {
|
|
133
|
+
const result = await this.sendCommand('Page.getLayoutMetrics');
|
|
134
|
+
return {
|
|
135
|
+
title: '',
|
|
136
|
+
url: '',
|
|
137
|
+
dimensions: {
|
|
138
|
+
width: result.contentSize?.width ?? 0,
|
|
139
|
+
height: result.contentSize?.height ?? 0,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// Console - store messages locally from events
|
|
144
|
+
consoleMessages = [];
|
|
145
|
+
async enableConsole() {
|
|
146
|
+
// Clear previous messages
|
|
147
|
+
this.consoleMessages = [];
|
|
148
|
+
await this.sendCommand('Console.enable');
|
|
149
|
+
}
|
|
150
|
+
async getConsoleMessages() {
|
|
151
|
+
return this.consoleMessages;
|
|
152
|
+
}
|
|
153
|
+
// Call this to retrieve buffered console messages (used internally after events)
|
|
154
|
+
addConsoleMessage(msg) {
|
|
155
|
+
this.consoleMessages.push(msg);
|
|
156
|
+
}
|
|
157
|
+
async enableRuntimeConsole() {
|
|
158
|
+
// Enable Runtime to capture console API calls
|
|
159
|
+
await this.sendCommand('Runtime.enable');
|
|
160
|
+
}
|
|
161
|
+
// Network
|
|
162
|
+
async enableNetwork() {
|
|
163
|
+
await this.sendCommand('Network.enable');
|
|
164
|
+
}
|
|
165
|
+
async disableNetwork() {
|
|
166
|
+
await this.sendCommand('Network.disable');
|
|
167
|
+
}
|
|
168
|
+
async getNetworkRequests() {
|
|
169
|
+
const result = await this.sendCommand('Network.getRequests');
|
|
170
|
+
const requests = new Map();
|
|
171
|
+
result.records.forEach((r) => requests.set(r.requestId, r));
|
|
172
|
+
return { requests, responses: new Map() };
|
|
173
|
+
}
|
|
174
|
+
async getResponseBody(requestId) {
|
|
175
|
+
const result = await this.sendCommand('Network.getResponseBody', { requestId });
|
|
176
|
+
return result.base64Encoded ? Buffer.from(result.body, 'base64').toString() : result.body;
|
|
177
|
+
}
|
|
178
|
+
// DOM
|
|
179
|
+
async getDocument() {
|
|
180
|
+
const result = await this.sendCommand('DOM.getDocument');
|
|
181
|
+
return result.root;
|
|
182
|
+
}
|
|
183
|
+
async querySelector(nodeId, selector) {
|
|
184
|
+
const result = await this.sendCommand('DOM.querySelector', {
|
|
185
|
+
nodeId,
|
|
186
|
+
selector,
|
|
187
|
+
});
|
|
188
|
+
return result ? result.nodeId : null;
|
|
189
|
+
}
|
|
190
|
+
async getOuterHTML(nodeId) {
|
|
191
|
+
const result = await this.sendCommand('DOM.getOuterHTML', { nodeId });
|
|
192
|
+
return result.outerHTML;
|
|
193
|
+
}
|
|
194
|
+
async getAttributes(nodeId) {
|
|
195
|
+
const result = await this.sendCommand('DOM.getAttributes', {
|
|
196
|
+
nodeId,
|
|
197
|
+
});
|
|
198
|
+
return result.attributes;
|
|
199
|
+
}
|
|
200
|
+
async resolveNode(nodeId) {
|
|
201
|
+
return this.sendCommand('DOM.resolveNode', { nodeId });
|
|
202
|
+
}
|
|
203
|
+
// Runtime
|
|
204
|
+
async evaluate(expression, returnByValue = true) {
|
|
205
|
+
const result = await this.sendCommand('Runtime.evaluate', {
|
|
206
|
+
expression,
|
|
207
|
+
returnByValue,
|
|
208
|
+
generatePreview: true,
|
|
209
|
+
});
|
|
210
|
+
return result.result.value ?? result.result;
|
|
211
|
+
}
|
|
212
|
+
async callFunctionOn(functionDeclaration, objectId) {
|
|
213
|
+
const result = await this.sendCommand('Runtime.callFunctionOn', {
|
|
214
|
+
functionDeclaration,
|
|
215
|
+
objectId,
|
|
216
|
+
});
|
|
217
|
+
return result.result.value;
|
|
218
|
+
}
|
|
219
|
+
async getProperties(objectId) {
|
|
220
|
+
const result = await this.sendCommand('Runtime.getProperties', { objectId });
|
|
221
|
+
return result.result.map((p) => ({ name: p.name, value: p.value.value }));
|
|
222
|
+
}
|
|
223
|
+
// Debugger
|
|
224
|
+
async enableDebugger() {
|
|
225
|
+
await this.sendCommand('Debugger.enable');
|
|
226
|
+
}
|
|
227
|
+
async setBreakpoint(url, lineNumber, columnNumber) {
|
|
228
|
+
const result = await this.sendCommand('Debugger.setBreakpointByUrl', {
|
|
229
|
+
lineNumber,
|
|
230
|
+
columnNumber,
|
|
231
|
+
url,
|
|
232
|
+
});
|
|
233
|
+
return result.breakpointId;
|
|
234
|
+
}
|
|
235
|
+
async listBreakpoints() {
|
|
236
|
+
const result = await this.sendCommand('Debugger.getBreakpoints');
|
|
237
|
+
return result.breakpoints;
|
|
238
|
+
}
|
|
239
|
+
async stepNext() {
|
|
240
|
+
await this.sendCommand('Debugger.stepNext');
|
|
241
|
+
}
|
|
242
|
+
async stepInto() {
|
|
243
|
+
await this.sendCommand('Debugger.stepInto');
|
|
244
|
+
}
|
|
245
|
+
async stepOut() {
|
|
246
|
+
await this.sendCommand('Debugger.stepOut');
|
|
247
|
+
}
|
|
248
|
+
async resume() {
|
|
249
|
+
await this.sendCommand('Debugger.resume');
|
|
250
|
+
}
|
|
251
|
+
// Log
|
|
252
|
+
async enableLog() {
|
|
253
|
+
await this.sendCommand('Log.enable');
|
|
254
|
+
}
|
|
255
|
+
async getLogEntries() {
|
|
256
|
+
return this.sendCommand('Log.getEntries');
|
|
257
|
+
}
|
|
258
|
+
}
|