chrome-cdp-cli 1.0.0 → 1.0.1
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 -26
- package/dist/cli/CLIApplication.js +4 -2
- package/dist/cli/CLIInterface.js +38 -5
- package/dist/cli/CommandRouter.js +9 -7
- package/dist/handlers/TakeScreenshotHandler.js +169 -0
- package/dist/handlers/TakeSnapshotHandler.js +306 -0
- package/dist/handlers/index.js +2 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -72,28 +72,28 @@ chrome --remote-debugging-port=9222 --no-first-run --no-default-browser-check
|
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
# Connect and execute JavaScript
|
|
75
|
-
chrome-cli eval "document.title"
|
|
75
|
+
chrome-cdp-cli eval "document.title"
|
|
76
76
|
|
|
77
77
|
# Or use with npx (no installation needed)
|
|
78
78
|
npx chrome-cdp-cli eval "document.title"
|
|
79
79
|
|
|
80
80
|
# Navigate to a website
|
|
81
|
-
chrome-cli navigate_page "https://example.com"
|
|
81
|
+
chrome-cdp-cli navigate_page "https://example.com"
|
|
82
82
|
|
|
83
83
|
# Take a screenshot
|
|
84
|
-
chrome-cli
|
|
84
|
+
chrome-cdp-cli screenshot --filename screenshot.png
|
|
85
85
|
|
|
86
86
|
# Click an element
|
|
87
|
-
chrome-cli click "#submit-button"
|
|
87
|
+
chrome-cdp-cli click "#submit-button"
|
|
88
88
|
|
|
89
89
|
# Fill a form field
|
|
90
|
-
chrome-cli fill "#email" "user@example.com"
|
|
90
|
+
chrome-cdp-cli fill "#email" "user@example.com"
|
|
91
91
|
|
|
92
92
|
# Get help for all commands
|
|
93
|
-
chrome-cli --help
|
|
93
|
+
chrome-cdp-cli --help
|
|
94
94
|
|
|
95
95
|
# Get help for a specific command
|
|
96
|
-
chrome-cli eval --help
|
|
96
|
+
chrome-cdp-cli eval --help
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
## Command Reference
|
|
@@ -117,13 +117,13 @@ All commands support these connection options:
|
|
|
117
117
|
#### JavaScript Execution
|
|
118
118
|
```bash
|
|
119
119
|
# Execute JavaScript expression
|
|
120
|
-
chrome-cli eval "console.log('Hello World')"
|
|
120
|
+
chrome-cdp-cli eval "console.log('Hello World')"
|
|
121
121
|
|
|
122
122
|
# Execute from file
|
|
123
|
-
chrome-cli eval --file script.js
|
|
123
|
+
chrome-cdp-cli eval --file script.js
|
|
124
124
|
|
|
125
125
|
# Execute with timeout
|
|
126
|
-
chrome-cli eval "await new Promise(r => setTimeout(r, 5000))" --timeout 10000
|
|
126
|
+
chrome-cdp-cli eval "await new Promise(r => setTimeout(r, 5000))" --timeout 10000
|
|
127
127
|
|
|
128
128
|
# Using npx (no installation required)
|
|
129
129
|
npx chrome-cdp-cli eval "document.title"
|
|
@@ -133,56 +133,59 @@ npx chrome-cdp-cli eval --file script.js
|
|
|
133
133
|
#### Page Management
|
|
134
134
|
```bash
|
|
135
135
|
# Navigate to URL
|
|
136
|
-
chrome-cli navigate_page "https://example.com"
|
|
136
|
+
chrome-cdp-cli navigate_page "https://example.com"
|
|
137
137
|
|
|
138
138
|
# Create new tab
|
|
139
|
-
chrome-cli new_page
|
|
139
|
+
chrome-cdp-cli new_page
|
|
140
140
|
|
|
141
141
|
# List all pages
|
|
142
|
-
chrome-cli list_pages
|
|
142
|
+
chrome-cdp-cli list_pages
|
|
143
143
|
|
|
144
144
|
# Close current page
|
|
145
|
-
chrome-cli close_page
|
|
145
|
+
chrome-cdp-cli close_page
|
|
146
146
|
|
|
147
147
|
# Switch to specific page
|
|
148
|
-
chrome-cli select_page --id "page-id"
|
|
148
|
+
chrome-cdp-cli select_page --id "page-id"
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
#### Element Interaction
|
|
152
152
|
```bash
|
|
153
153
|
# Click element
|
|
154
|
-
chrome-cli click "#button"
|
|
154
|
+
chrome-cdp-cli click "#button"
|
|
155
155
|
|
|
156
156
|
# Fill input field
|
|
157
|
-
chrome-cli fill "#email" "user@example.com"
|
|
157
|
+
chrome-cdp-cli fill "#email" "user@example.com"
|
|
158
158
|
|
|
159
159
|
# Hover over element
|
|
160
|
-
chrome-cli hover ".menu-item"
|
|
160
|
+
chrome-cdp-cli hover ".menu-item"
|
|
161
161
|
|
|
162
162
|
# Wait for element
|
|
163
|
-
chrome-cli wait_for "#loading" --timeout 5000
|
|
163
|
+
chrome-cdp-cli wait_for "#loading" --timeout 5000
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
#### Visual Capture
|
|
167
167
|
```bash
|
|
168
168
|
# Take screenshot
|
|
169
|
-
chrome-cli
|
|
169
|
+
chrome-cdp-cli screenshot --filename screenshot.png
|
|
170
170
|
|
|
171
|
-
# Full page screenshot
|
|
172
|
-
chrome-cli
|
|
171
|
+
# Full page screenshot
|
|
172
|
+
chrome-cdp-cli screenshot --full-page --filename fullpage.png
|
|
173
|
+
|
|
174
|
+
# DOM snapshot
|
|
175
|
+
chrome-cdp-cli snapshot --filename dom-snapshot.json
|
|
173
176
|
|
|
174
177
|
# Custom dimensions
|
|
175
|
-
chrome-cli
|
|
178
|
+
chrome-cdp-cli screenshot --width 1920 --height 1080 --filename custom.png
|
|
176
179
|
|
|
177
180
|
# Get HTML content
|
|
178
|
-
chrome-cli get_html --output page.html
|
|
181
|
+
chrome-cdp-cli get_html --output page.html
|
|
179
182
|
```
|
|
180
183
|
|
|
181
184
|
## Configuration
|
|
182
185
|
|
|
183
186
|
### Configuration File
|
|
184
187
|
|
|
185
|
-
Create a `.chrome-cli.json` file in your project root or home directory:
|
|
188
|
+
Create a `.chrome-cdp-cli.json` file in your project root or home directory:
|
|
186
189
|
|
|
187
190
|
```json
|
|
188
191
|
{
|
|
@@ -347,7 +350,7 @@ console.log(result);
|
|
|
347
350
|
Enable verbose logging for troubleshooting:
|
|
348
351
|
|
|
349
352
|
```bash
|
|
350
|
-
chrome-cli --verbose eval "console.log('debug')"
|
|
353
|
+
chrome-cdp-cli --verbose eval "console.log('debug')"
|
|
351
354
|
```
|
|
352
355
|
|
|
353
356
|
### Packaging
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CLIApplication = void 0;
|
|
4
4
|
const CLIInterface_1 = require("./CLIInterface");
|
|
5
5
|
const ConnectionManager_1 = require("../connection/ConnectionManager");
|
|
6
|
-
const
|
|
6
|
+
const handlers_1 = require("../handlers");
|
|
7
7
|
const logger_1 = require("../utils/logger");
|
|
8
8
|
const CommandRouter_1 = require("./CommandRouter");
|
|
9
9
|
class CLIApplication {
|
|
@@ -14,7 +14,9 @@ class CLIApplication {
|
|
|
14
14
|
this.setupHandlers();
|
|
15
15
|
}
|
|
16
16
|
setupHandlers() {
|
|
17
|
-
this.cli.registerHandler(new
|
|
17
|
+
this.cli.registerHandler(new handlers_1.EvaluateScriptHandler());
|
|
18
|
+
this.cli.registerHandler(new handlers_1.TakeScreenshotHandler());
|
|
19
|
+
this.cli.registerHandler(new handlers_1.TakeSnapshotHandler());
|
|
18
20
|
}
|
|
19
21
|
async run(argv) {
|
|
20
22
|
try {
|
package/dist/cli/CLIInterface.js
CHANGED
|
@@ -48,7 +48,7 @@ class CLIInterface {
|
|
|
48
48
|
}
|
|
49
49
|
setupProgram() {
|
|
50
50
|
this.program
|
|
51
|
-
.name('chrome-cli')
|
|
51
|
+
.name('chrome-cdp-cli')
|
|
52
52
|
.description('Command-line tool for controlling Chrome browser via DevTools Protocol')
|
|
53
53
|
.version('1.0.0')
|
|
54
54
|
.allowUnknownOption(true)
|
|
@@ -139,9 +139,9 @@ class CLIInterface {
|
|
|
139
139
|
}
|
|
140
140
|
else {
|
|
141
141
|
const defaultPaths = [
|
|
142
|
-
'.chrome-cli.json',
|
|
143
|
-
path.join(process.env.HOME || '', '.chrome-cli.json'),
|
|
144
|
-
'/etc/chrome-cli.json'
|
|
142
|
+
'.chrome-cdp-cli.json',
|
|
143
|
+
path.join(process.env.HOME || '', '.chrome-cdp-cli.json'),
|
|
144
|
+
'/etc/chrome-cdp-cli.json'
|
|
145
145
|
];
|
|
146
146
|
for (const defaultPath of defaultPaths) {
|
|
147
147
|
try {
|
|
@@ -239,12 +239,45 @@ class CLIInterface {
|
|
|
239
239
|
commandArgs.text = args[1];
|
|
240
240
|
break;
|
|
241
241
|
case 'screenshot':
|
|
242
|
+
if (options.filename)
|
|
243
|
+
commandArgs.filename = options.filename;
|
|
242
244
|
if (options.output || options.o)
|
|
243
|
-
commandArgs.
|
|
245
|
+
commandArgs.filename = options.output || options.o;
|
|
244
246
|
if (options.width || options.w)
|
|
245
247
|
commandArgs.width = parseInt(options.width || options.w, 10);
|
|
246
248
|
if (options.height || options.h)
|
|
247
249
|
commandArgs.height = parseInt(options.height || options.h, 10);
|
|
250
|
+
if (options.format)
|
|
251
|
+
commandArgs.format = options.format;
|
|
252
|
+
if (options.quality)
|
|
253
|
+
commandArgs.quality = parseInt(options.quality, 10);
|
|
254
|
+
if (options['full-page'])
|
|
255
|
+
commandArgs.fullPage = true;
|
|
256
|
+
if (options['clip-x'] || options['clip-y'] || options['clip-width'] || options['clip-height'] || options['clip-scale']) {
|
|
257
|
+
commandArgs.clip = {
|
|
258
|
+
x: options['clip-x'] ? parseInt(options['clip-x'], 10) : 0,
|
|
259
|
+
y: options['clip-y'] ? parseInt(options['clip-y'], 10) : 0,
|
|
260
|
+
width: options['clip-width'] ? parseInt(options['clip-width'], 10) : 0,
|
|
261
|
+
height: options['clip-height'] ? parseInt(options['clip-height'], 10) : 0,
|
|
262
|
+
scale: options['clip-scale'] ? parseFloat(options['clip-scale']) : 1
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
case 'snapshot':
|
|
267
|
+
if (options.filename)
|
|
268
|
+
commandArgs.filename = options.filename;
|
|
269
|
+
if (options.output || options.o)
|
|
270
|
+
commandArgs.filename = options.output || options.o;
|
|
271
|
+
if (options.format)
|
|
272
|
+
commandArgs.format = options.format;
|
|
273
|
+
if (options['include-styles'] !== undefined)
|
|
274
|
+
commandArgs.includeStyles = options['include-styles'] !== 'false';
|
|
275
|
+
if (options['include-attributes'] !== undefined)
|
|
276
|
+
commandArgs.includeAttributes = options['include-attributes'] !== 'false';
|
|
277
|
+
if (options['include-paint-order'])
|
|
278
|
+
commandArgs.includePaintOrder = true;
|
|
279
|
+
if (options['include-text-index'])
|
|
280
|
+
commandArgs.includeTextIndex = true;
|
|
248
281
|
break;
|
|
249
282
|
case 'console_messages':
|
|
250
283
|
if (options.filter)
|
|
@@ -186,7 +186,7 @@ class CommandRouter {
|
|
|
186
186
|
return `
|
|
187
187
|
Chrome DevTools CLI - Command-line tool for controlling Chrome browser
|
|
188
188
|
|
|
189
|
-
Usage: chrome-cli [options] <command> [command-options]
|
|
189
|
+
Usage: chrome-cdp-cli [options] <command> [command-options]
|
|
190
190
|
|
|
191
191
|
Global Options:
|
|
192
192
|
-h, --host <host> Chrome host address (default: localhost)
|
|
@@ -201,12 +201,14 @@ Available Commands:
|
|
|
201
201
|
${commands.map(cmd => ` ${cmd.padEnd(20)} - ${this.getCommandDescription(cmd)}`).join('\n')}
|
|
202
202
|
|
|
203
203
|
Examples:
|
|
204
|
-
chrome-cli eval "document.title"
|
|
205
|
-
chrome-cli eval --file script.js
|
|
206
|
-
chrome-cli
|
|
204
|
+
chrome-cdp-cli eval "document.title"
|
|
205
|
+
chrome-cdp-cli eval --file script.js
|
|
206
|
+
chrome-cdp-cli screenshot --filename page.png
|
|
207
|
+
chrome-cdp-cli snapshot --format html --filename dom.html
|
|
208
|
+
chrome-cdp-cli help <command>
|
|
207
209
|
|
|
208
210
|
For more information about a specific command, use:
|
|
209
|
-
chrome-cli help <command>
|
|
211
|
+
chrome-cdp-cli help <command>
|
|
210
212
|
`;
|
|
211
213
|
}
|
|
212
214
|
getCommandDescription(commandName) {
|
|
@@ -223,8 +225,8 @@ For more information about a specific command, use:
|
|
|
223
225
|
'click': 'Click element',
|
|
224
226
|
'fill': 'Fill form field',
|
|
225
227
|
'hover': 'Hover over element',
|
|
226
|
-
'screenshot': '
|
|
227
|
-
'
|
|
228
|
+
'screenshot': 'Capture page screenshot',
|
|
229
|
+
'snapshot': 'Capture DOM snapshot with structure and styles',
|
|
228
230
|
'console-messages': 'Get console messages',
|
|
229
231
|
'network-requests': 'Get network requests',
|
|
230
232
|
'help': 'Show help information'
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TakeScreenshotHandler = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
class TakeScreenshotHandler {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.name = 'screenshot';
|
|
9
|
+
}
|
|
10
|
+
async execute(client, args) {
|
|
11
|
+
const screenshotArgs = args;
|
|
12
|
+
try {
|
|
13
|
+
await client.send('Page.enable');
|
|
14
|
+
const params = this.buildScreenshotParams(screenshotArgs);
|
|
15
|
+
const response = await client.send('Page.captureScreenshot', params);
|
|
16
|
+
if (!response || !response.data) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: 'Failed to capture screenshot: empty response'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (screenshotArgs.filename) {
|
|
23
|
+
await this.saveScreenshot(response.data, screenshotArgs.filename);
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
data: {
|
|
27
|
+
message: `Screenshot saved to ${screenshotArgs.filename}`,
|
|
28
|
+
filename: screenshotArgs.filename,
|
|
29
|
+
format: screenshotArgs.format || 'png'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
data: {
|
|
36
|
+
base64: response.data,
|
|
37
|
+
format: screenshotArgs.format || 'png'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: error instanceof Error ? error.message : String(error)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
buildScreenshotParams(args) {
|
|
49
|
+
const params = {};
|
|
50
|
+
params.format = args.format || 'png';
|
|
51
|
+
if (args.format === 'jpeg' && args.quality !== undefined) {
|
|
52
|
+
if (args.quality < 0 || args.quality > 100) {
|
|
53
|
+
throw new Error('JPEG quality must be between 0 and 100');
|
|
54
|
+
}
|
|
55
|
+
params.quality = args.quality;
|
|
56
|
+
}
|
|
57
|
+
if (args.clip) {
|
|
58
|
+
params.clip = {
|
|
59
|
+
x: args.clip.x,
|
|
60
|
+
y: args.clip.y,
|
|
61
|
+
width: args.clip.width,
|
|
62
|
+
height: args.clip.height,
|
|
63
|
+
scale: args.clip.scale || 1
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (args.width || args.height) {
|
|
67
|
+
}
|
|
68
|
+
if (args.fullPage) {
|
|
69
|
+
params.captureBeyondViewport = true;
|
|
70
|
+
}
|
|
71
|
+
return params;
|
|
72
|
+
}
|
|
73
|
+
async saveScreenshot(base64Data, filename) {
|
|
74
|
+
try {
|
|
75
|
+
const dir = (0, path_1.dirname)(filename);
|
|
76
|
+
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
77
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
78
|
+
await fs_1.promises.writeFile(filename, buffer);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new Error(`Failed to save screenshot to "${filename}": ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
validateArgs(args) {
|
|
85
|
+
if (typeof args !== 'object' || args === null) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const screenshotArgs = args;
|
|
89
|
+
if (screenshotArgs.filename !== undefined && typeof screenshotArgs.filename !== 'string') {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (screenshotArgs.width !== undefined && (typeof screenshotArgs.width !== 'number' || screenshotArgs.width <= 0)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (screenshotArgs.height !== undefined && (typeof screenshotArgs.height !== 'number' || screenshotArgs.height <= 0)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (screenshotArgs.format !== undefined && !['png', 'jpeg'].includes(screenshotArgs.format)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (screenshotArgs.quality !== undefined) {
|
|
102
|
+
if (typeof screenshotArgs.quality !== 'number' || screenshotArgs.quality < 0 || screenshotArgs.quality > 100) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (screenshotArgs.fullPage !== undefined && typeof screenshotArgs.fullPage !== 'boolean') {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (screenshotArgs.clip !== undefined) {
|
|
110
|
+
const clip = screenshotArgs.clip;
|
|
111
|
+
if (typeof clip !== 'object' || clip === null) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (typeof clip.x !== 'number' || typeof clip.y !== 'number' ||
|
|
115
|
+
typeof clip.width !== 'number' || typeof clip.height !== 'number') {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (clip.width <= 0 || clip.height <= 0) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (clip.scale !== undefined && (typeof clip.scale !== 'number' || clip.scale <= 0)) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
getHelp() {
|
|
128
|
+
return `
|
|
129
|
+
screenshot - Capture a screenshot of the current page
|
|
130
|
+
|
|
131
|
+
Usage:
|
|
132
|
+
screenshot
|
|
133
|
+
screenshot --filename screenshot.png
|
|
134
|
+
screenshot --width 1920 --height 1080 --filename full-hd.png
|
|
135
|
+
screenshot --format jpeg --quality 80 --filename compressed.jpg
|
|
136
|
+
screenshot --full-page --filename full-page.png
|
|
137
|
+
|
|
138
|
+
Arguments:
|
|
139
|
+
--filename <path> Output filename (if not provided, returns base64 data)
|
|
140
|
+
--width <pixels> Screenshot width (requires viewport adjustment)
|
|
141
|
+
--height <pixels> Screenshot height (requires viewport adjustment)
|
|
142
|
+
--format <png|jpeg> Image format (default: png)
|
|
143
|
+
--quality <1-100> JPEG quality (only for jpeg format)
|
|
144
|
+
--full-page Capture full page beyond viewport (default: false)
|
|
145
|
+
--clip-x <pixels> Clip rectangle X coordinate
|
|
146
|
+
--clip-y <pixels> Clip rectangle Y coordinate
|
|
147
|
+
--clip-width <pixels> Clip rectangle width
|
|
148
|
+
--clip-height <pixels> Clip rectangle height
|
|
149
|
+
--clip-scale <number> Clip rectangle scale factor
|
|
150
|
+
|
|
151
|
+
Examples:
|
|
152
|
+
# Basic screenshot
|
|
153
|
+
screenshot --filename page.png
|
|
154
|
+
|
|
155
|
+
# High quality JPEG
|
|
156
|
+
screenshot --format jpeg --quality 95 --filename page.jpg
|
|
157
|
+
|
|
158
|
+
# Full page screenshot
|
|
159
|
+
screenshot --full-page --filename full-page.png
|
|
160
|
+
|
|
161
|
+
# Clipped screenshot
|
|
162
|
+
screenshot --clip-x 100 --clip-y 100 --clip-width 800 --clip-height 600 --filename clipped.png
|
|
163
|
+
|
|
164
|
+
# Return base64 data (no file)
|
|
165
|
+
screenshot --format png
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
exports.TakeScreenshotHandler = TakeScreenshotHandler;
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TakeSnapshotHandler = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
class TakeSnapshotHandler {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.name = 'snapshot';
|
|
9
|
+
}
|
|
10
|
+
async execute(client, args) {
|
|
11
|
+
const snapshotArgs = args;
|
|
12
|
+
try {
|
|
13
|
+
await client.send('DOMSnapshot.enable');
|
|
14
|
+
await client.send('DOM.enable');
|
|
15
|
+
await client.send('CSS.enable');
|
|
16
|
+
const params = this.buildSnapshotParams(snapshotArgs);
|
|
17
|
+
const response = await client.send('DOMSnapshot.captureSnapshot', params);
|
|
18
|
+
if (!response || !response.documents || response.documents.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
success: false,
|
|
21
|
+
error: 'Failed to capture DOM snapshot: empty response'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const processedSnapshot = this.processSnapshot(response, snapshotArgs);
|
|
25
|
+
if (snapshotArgs.filename) {
|
|
26
|
+
await this.saveSnapshot(processedSnapshot, snapshotArgs.filename, snapshotArgs.format);
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
data: {
|
|
30
|
+
message: `DOM snapshot saved to ${snapshotArgs.filename}`,
|
|
31
|
+
filename: snapshotArgs.filename,
|
|
32
|
+
format: snapshotArgs.format || 'json',
|
|
33
|
+
documentsCount: response.documents.length,
|
|
34
|
+
nodesCount: response.documents[0]?.nodes?.nodeName?.length || 0
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
data: {
|
|
41
|
+
snapshot: processedSnapshot,
|
|
42
|
+
format: snapshotArgs.format || 'json',
|
|
43
|
+
documentsCount: response.documents.length,
|
|
44
|
+
nodesCount: response.documents[0]?.nodes?.nodeName?.length || 0
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: error instanceof Error ? error.message : String(error)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
buildSnapshotParams(args) {
|
|
56
|
+
const params = {};
|
|
57
|
+
params.computedStyles = args.includeStyles !== false;
|
|
58
|
+
params.includeDOMRects = args.includeAttributes !== false;
|
|
59
|
+
if (args.includePaintOrder) {
|
|
60
|
+
params.includePaintOrder = true;
|
|
61
|
+
}
|
|
62
|
+
if (args.includeTextIndex) {
|
|
63
|
+
params.includeTextIndex = true;
|
|
64
|
+
}
|
|
65
|
+
params.computedStyleWhitelist = [];
|
|
66
|
+
return params;
|
|
67
|
+
}
|
|
68
|
+
processSnapshot(response, args) {
|
|
69
|
+
if (args.format === 'html') {
|
|
70
|
+
return this.convertToHTML(response);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
metadata: {
|
|
74
|
+
captureTime: new Date().toISOString(),
|
|
75
|
+
documentsCount: response.documents.length,
|
|
76
|
+
stringsCount: response.strings.length
|
|
77
|
+
},
|
|
78
|
+
documents: response.documents.map(doc => ({
|
|
79
|
+
url: doc.documentURL,
|
|
80
|
+
title: doc.title,
|
|
81
|
+
baseURL: doc.baseURL,
|
|
82
|
+
language: doc.contentLanguage,
|
|
83
|
+
encoding: doc.encodingName,
|
|
84
|
+
frameId: doc.frameId,
|
|
85
|
+
domTree: this.buildDOMTree(doc, response.strings),
|
|
86
|
+
layout: this.processLayoutInfo(doc.layout, response.strings),
|
|
87
|
+
textBoxes: doc.textBoxes
|
|
88
|
+
})),
|
|
89
|
+
strings: response.strings
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
buildDOMTree(doc, strings) {
|
|
93
|
+
const nodes = doc.nodes;
|
|
94
|
+
if (!nodes.nodeName || !nodes.nodeType) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const nodeCount = nodes.nodeName.length;
|
|
98
|
+
const tree = [];
|
|
99
|
+
const nodeMap = new Map();
|
|
100
|
+
for (let i = 0; i < nodeCount; i++) {
|
|
101
|
+
const node = {
|
|
102
|
+
index: i,
|
|
103
|
+
nodeType: nodes.nodeType[i],
|
|
104
|
+
nodeName: nodes.nodeName[i],
|
|
105
|
+
nodeValue: nodes.nodeValue?.[i],
|
|
106
|
+
backendNodeId: nodes.backendNodeId?.[i],
|
|
107
|
+
children: []
|
|
108
|
+
};
|
|
109
|
+
if (nodes.attributes?.[i]) {
|
|
110
|
+
const attrs = nodes.attributes[i];
|
|
111
|
+
node.attributes = {};
|
|
112
|
+
for (let j = 0; j < attrs.length; j += 2) {
|
|
113
|
+
const name = strings[parseInt(attrs[j])];
|
|
114
|
+
const value = strings[parseInt(attrs[j + 1])];
|
|
115
|
+
node.attributes[name] = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (nodes.textValue?.index.includes(i)) {
|
|
119
|
+
const textIndex = nodes.textValue.index.indexOf(i);
|
|
120
|
+
const stringIndex = nodes.textValue.value[textIndex];
|
|
121
|
+
if (typeof stringIndex === 'number') {
|
|
122
|
+
node.textValue = strings[stringIndex];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (nodes.inputValue?.index.includes(i)) {
|
|
126
|
+
const inputIndex = nodes.inputValue.index.indexOf(i);
|
|
127
|
+
const stringIndex = nodes.inputValue.value[inputIndex];
|
|
128
|
+
if (typeof stringIndex === 'number') {
|
|
129
|
+
node.inputValue = strings[stringIndex];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (nodes.inputChecked?.index.includes(i)) {
|
|
133
|
+
node.inputChecked = true;
|
|
134
|
+
}
|
|
135
|
+
if (nodes.optionSelected?.index.includes(i)) {
|
|
136
|
+
node.optionSelected = true;
|
|
137
|
+
}
|
|
138
|
+
if (nodes.isClickable?.index.includes(i)) {
|
|
139
|
+
node.isClickable = true;
|
|
140
|
+
}
|
|
141
|
+
nodeMap.set(i, node);
|
|
142
|
+
}
|
|
143
|
+
for (let i = 0; i < nodeCount; i++) {
|
|
144
|
+
const parentIndex = nodes.parentIndex?.[i];
|
|
145
|
+
const node = nodeMap.get(i);
|
|
146
|
+
if (parentIndex !== undefined && parentIndex >= 0) {
|
|
147
|
+
const parent = nodeMap.get(parentIndex);
|
|
148
|
+
if (parent) {
|
|
149
|
+
parent.children.push(node);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
tree.push(node);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return tree;
|
|
157
|
+
}
|
|
158
|
+
processLayoutInfo(layout, strings) {
|
|
159
|
+
return {
|
|
160
|
+
nodeCount: layout.nodeIndex.length,
|
|
161
|
+
styles: layout.styles.map(styleArray => styleArray.map(styleIndex => strings[parseInt(styleIndex)])),
|
|
162
|
+
bounds: layout.bounds,
|
|
163
|
+
text: layout.text,
|
|
164
|
+
paintOrders: layout.paintOrders,
|
|
165
|
+
offsetRects: layout.offsetRects,
|
|
166
|
+
scrollRects: layout.scrollRects,
|
|
167
|
+
clientRects: layout.clientRects,
|
|
168
|
+
blendedBackgroundColors: layout.blendedBackgroundColors,
|
|
169
|
+
textColorOpacities: layout.textColorOpacities
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
convertToHTML(response) {
|
|
173
|
+
const doc = response.documents[0];
|
|
174
|
+
if (!doc)
|
|
175
|
+
return '';
|
|
176
|
+
const tree = this.buildDOMTree(doc, response.strings);
|
|
177
|
+
return this.renderNodeAsHTML(tree[0], 0);
|
|
178
|
+
}
|
|
179
|
+
renderNodeAsHTML(node, depth) {
|
|
180
|
+
if (!node)
|
|
181
|
+
return '';
|
|
182
|
+
const indent = ' '.repeat(depth);
|
|
183
|
+
if (node.nodeType === 3) {
|
|
184
|
+
const text = node.nodeValue || node.textValue || '';
|
|
185
|
+
return text.trim() ? `${indent}${text.trim()}\n` : '';
|
|
186
|
+
}
|
|
187
|
+
if (node.nodeType === 8) {
|
|
188
|
+
return `${indent}<!-- ${node.nodeValue || ''} -->\n`;
|
|
189
|
+
}
|
|
190
|
+
if (node.nodeType === 1) {
|
|
191
|
+
const tagName = node.nodeName.toLowerCase();
|
|
192
|
+
let html = `${indent}<${tagName}`;
|
|
193
|
+
if (node.attributes) {
|
|
194
|
+
for (const [name, value] of Object.entries(node.attributes)) {
|
|
195
|
+
html += ` ${name}="${value}"`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const selfClosing = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
|
|
199
|
+
if (selfClosing.includes(tagName)) {
|
|
200
|
+
html += ' />\n';
|
|
201
|
+
return html;
|
|
202
|
+
}
|
|
203
|
+
html += '>';
|
|
204
|
+
if (node.children && node.children.length > 0) {
|
|
205
|
+
html += '\n';
|
|
206
|
+
for (const child of node.children) {
|
|
207
|
+
html += this.renderNodeAsHTML(child, depth + 1);
|
|
208
|
+
}
|
|
209
|
+
html += `${indent}</${tagName}>\n`;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
html += `</${tagName}>\n`;
|
|
213
|
+
}
|
|
214
|
+
return html;
|
|
215
|
+
}
|
|
216
|
+
return '';
|
|
217
|
+
}
|
|
218
|
+
async saveSnapshot(snapshotData, filename, format) {
|
|
219
|
+
try {
|
|
220
|
+
const dir = (0, path_1.dirname)(filename);
|
|
221
|
+
await fs_1.promises.mkdir(dir, { recursive: true });
|
|
222
|
+
let content;
|
|
223
|
+
if (format === 'html') {
|
|
224
|
+
content = snapshotData;
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
content = JSON.stringify(snapshotData, null, 2);
|
|
228
|
+
}
|
|
229
|
+
await fs_1.promises.writeFile(filename, content, 'utf-8');
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
throw new Error(`Failed to save DOM snapshot to "${filename}": ${error instanceof Error ? error.message : String(error)}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
validateArgs(args) {
|
|
236
|
+
if (typeof args !== 'object' || args === null) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
const snapshotArgs = args;
|
|
240
|
+
if (snapshotArgs.filename !== undefined && typeof snapshotArgs.filename !== 'string') {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (snapshotArgs.format !== undefined && !['json', 'html'].includes(snapshotArgs.format)) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
if (snapshotArgs.includeStyles !== undefined && typeof snapshotArgs.includeStyles !== 'boolean') {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
if (snapshotArgs.includeAttributes !== undefined && typeof snapshotArgs.includeAttributes !== 'boolean') {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
if (snapshotArgs.includePaintOrder !== undefined && typeof snapshotArgs.includePaintOrder !== 'boolean') {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
if (snapshotArgs.includeTextIndex !== undefined && typeof snapshotArgs.includeTextIndex !== 'boolean') {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
getHelp() {
|
|
261
|
+
return `
|
|
262
|
+
snapshot - Capture a DOM snapshot including DOM tree structure, computed styles, and layout information
|
|
263
|
+
|
|
264
|
+
Usage:
|
|
265
|
+
snapshot
|
|
266
|
+
snapshot --filename dom-snapshot.json
|
|
267
|
+
snapshot --format html --filename page-structure.html
|
|
268
|
+
snapshot --include-paint-order --filename detailed-snapshot.json
|
|
269
|
+
|
|
270
|
+
Arguments:
|
|
271
|
+
--filename <path> Output filename (if not provided, returns data directly)
|
|
272
|
+
--format <json|html> Output format (default: json)
|
|
273
|
+
--include-styles Include computed styles (default: true)
|
|
274
|
+
--include-attributes Include DOM attributes (default: true)
|
|
275
|
+
--include-paint-order Include paint order information (default: false)
|
|
276
|
+
--include-text-index Include text node indices (default: false)
|
|
277
|
+
|
|
278
|
+
Output Formats:
|
|
279
|
+
json: Structured JSON with DOM tree, layout info, and computed styles
|
|
280
|
+
html: Reconstructed HTML representation of the DOM
|
|
281
|
+
|
|
282
|
+
Examples:
|
|
283
|
+
# Basic DOM snapshot as JSON
|
|
284
|
+
snapshot --filename dom-snapshot.json
|
|
285
|
+
|
|
286
|
+
# HTML representation
|
|
287
|
+
snapshot --format html --filename page-structure.html
|
|
288
|
+
|
|
289
|
+
# Detailed snapshot with paint order
|
|
290
|
+
snapshot --include-paint-order --filename detailed-snapshot.json
|
|
291
|
+
|
|
292
|
+
# Return data directly (no file)
|
|
293
|
+
snapshot --format json
|
|
294
|
+
|
|
295
|
+
Note:
|
|
296
|
+
DOM snapshots capture the complete DOM tree structure including:
|
|
297
|
+
- Element hierarchy and attributes
|
|
298
|
+
- Computed CSS styles for each element
|
|
299
|
+
- Layout information (bounds, positioning)
|
|
300
|
+
- Text content and form values
|
|
301
|
+
- Visibility and clickability information
|
|
302
|
+
- Paint order and rendering details (optional)
|
|
303
|
+
`;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
exports.TakeSnapshotHandler = TakeSnapshotHandler;
|
package/dist/handlers/index.js
CHANGED
|
@@ -15,3 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./EvaluateScriptHandler"), exports);
|
|
18
|
+
__exportStar(require("./TakeScreenshotHandler"), exports);
|
|
19
|
+
__exportStar(require("./TakeSnapshotHandler"), exports);
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-cdp-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Command-line tool for controlling Chrome browser instances via the Chrome DevTools Protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"chrome-cli": "dist/index.js"
|
|
9
|
-
"chrome-devtools-cli": "dist/index.js"
|
|
8
|
+
"chrome-cdp-cli": "dist/index.js"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
12
11
|
"dist/**/*",
|