browser-cdp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/cli.js +20 -3
- package/console.js +77 -0
- package/insights.js +133 -0
- package/package.json +4 -2
- package/start.js +26 -9
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# browser-cdp
|
|
2
2
|
|
|
3
|
-
Browser automation via Chrome DevTools Protocol. Control Chrome, Brave,
|
|
3
|
+
Browser automation via Chrome DevTools Protocol. Control Chrome, Brave, or Edge using your real browser - same fingerprint, real cookies, no automation detection.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -25,6 +25,12 @@ browser-cdp screenshot
|
|
|
25
25
|
|
|
26
26
|
# Interactive element picker
|
|
27
27
|
browser-cdp pick '<message>'
|
|
28
|
+
|
|
29
|
+
# Stream browser console output
|
|
30
|
+
browser-cdp console [--duration=SECONDS]
|
|
31
|
+
|
|
32
|
+
# Show page performance metrics
|
|
33
|
+
browser-cdp insights [--json]
|
|
28
34
|
```
|
|
29
35
|
|
|
30
36
|
## Environment Variables
|
|
@@ -32,7 +38,7 @@ browser-cdp pick '<message>'
|
|
|
32
38
|
| Variable | Description | Default |
|
|
33
39
|
|----------|-------------|---------|
|
|
34
40
|
| `DEBUG_PORT` | CDP debugging port | `9222` |
|
|
35
|
-
| `BROWSER` | Browser to use (chrome, brave,
|
|
41
|
+
| `BROWSER` | Browser to use (chrome, brave, edge) | `chrome` |
|
|
36
42
|
| `BROWSER_PATH` | Custom browser executable path | (auto-detect) |
|
|
37
43
|
|
|
38
44
|
## Examples
|
|
@@ -54,6 +60,16 @@ browser-cdp screenshot
|
|
|
54
60
|
|
|
55
61
|
# Pick elements interactively
|
|
56
62
|
browser-cdp pick "Select the login button"
|
|
63
|
+
|
|
64
|
+
# Stream console output for 10 seconds
|
|
65
|
+
browser-cdp console --duration=10
|
|
66
|
+
|
|
67
|
+
# Get page performance insights
|
|
68
|
+
browser-cdp insights
|
|
69
|
+
# Returns: TTFB, First Paint, FCP, DOM loaded, resources, memory
|
|
70
|
+
|
|
71
|
+
# Get insights as JSON
|
|
72
|
+
browser-cdp insights --json
|
|
57
73
|
```
|
|
58
74
|
|
|
59
75
|
## Pre-started Browser
|
|
@@ -78,7 +94,6 @@ browser-cdp nav https://example.com
|
|
|
78
94
|
|---------|---------|
|
|
79
95
|
| Chrome | `chrome` (default) |
|
|
80
96
|
| Brave | `brave` |
|
|
81
|
-
| Comet | `comet` |
|
|
82
97
|
| Edge | `edge` |
|
|
83
98
|
|
|
84
99
|
## Platform Support
|
package/cli.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8"));
|
|
9
|
+
|
|
3
10
|
const command = process.argv[2];
|
|
4
11
|
const args = process.argv.slice(3);
|
|
5
12
|
|
|
@@ -9,10 +16,12 @@ const commands = {
|
|
|
9
16
|
eval: "./eval.js",
|
|
10
17
|
screenshot: "./screenshot.js",
|
|
11
18
|
pick: "./pick.js",
|
|
19
|
+
console: "./console.js",
|
|
20
|
+
insights: "./insights.js",
|
|
12
21
|
};
|
|
13
22
|
|
|
14
23
|
function printUsage() {
|
|
15
|
-
console.log(
|
|
24
|
+
console.log(`browser-cdp v${pkg.version} - Browser automation via Chrome DevTools Protocol`);
|
|
16
25
|
console.log("");
|
|
17
26
|
console.log("Usage: browser-cdp <command> [options]");
|
|
18
27
|
console.log("");
|
|
@@ -22,17 +31,25 @@ function printUsage() {
|
|
|
22
31
|
console.log(" eval '<code>' Evaluate JavaScript in page");
|
|
23
32
|
console.log(" screenshot Take screenshot of current page");
|
|
24
33
|
console.log(" pick '<message>' Interactive element picker");
|
|
34
|
+
console.log(" console Stream browser console output");
|
|
35
|
+
console.log(" insights Show page performance metrics");
|
|
25
36
|
console.log("");
|
|
26
37
|
console.log("Environment:");
|
|
27
38
|
console.log(" DEBUG_PORT CDP port (default: 9222)");
|
|
28
|
-
console.log(" BROWSER Browser to use (chrome, brave,
|
|
39
|
+
console.log(" BROWSER Browser to use (chrome, brave, edge)");
|
|
29
40
|
console.log(" BROWSER_PATH Custom browser executable path");
|
|
30
41
|
console.log("");
|
|
31
42
|
console.log("Examples:");
|
|
32
43
|
console.log(" browser-cdp start brave");
|
|
33
44
|
console.log(" browser-cdp nav https://google.com");
|
|
34
45
|
console.log(" browser-cdp eval 'document.title'");
|
|
35
|
-
console.log(" browser-cdp
|
|
46
|
+
console.log(" browser-cdp console --duration=10");
|
|
47
|
+
console.log(" browser-cdp insights --json");
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (command === "--version" || command === "-v") {
|
|
52
|
+
console.log(pkg.version);
|
|
36
53
|
process.exit(0);
|
|
37
54
|
}
|
|
38
55
|
|
package/console.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { chromium } from "playwright";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PORT = process.env.DEBUG_PORT || 9222;
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const duration = args.find((a) => a.startsWith("--duration="));
|
|
9
|
+
const durationMs = duration ? parseInt(duration.split("=")[1]) * 1000 : null;
|
|
10
|
+
const showHelp = args.includes("--help") || args.includes("-h");
|
|
11
|
+
|
|
12
|
+
if (showHelp) {
|
|
13
|
+
console.log("Usage: console.js [--duration=SECONDS]");
|
|
14
|
+
console.log("\nCapture browser console output in real-time.");
|
|
15
|
+
console.log("\nOptions:");
|
|
16
|
+
console.log(" --duration=N Stop after N seconds (default: run until Ctrl+C)");
|
|
17
|
+
console.log("\nExamples:");
|
|
18
|
+
console.log(" console.js # Stream console logs until Ctrl+C");
|
|
19
|
+
console.log(" console.js --duration=5 # Capture for 5 seconds");
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
|
|
24
|
+
const contexts = browser.contexts();
|
|
25
|
+
const context = contexts[0];
|
|
26
|
+
|
|
27
|
+
if (!context) {
|
|
28
|
+
console.error("No browser context found");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pages = context.pages();
|
|
33
|
+
const page = pages[pages.length - 1];
|
|
34
|
+
|
|
35
|
+
if (!page) {
|
|
36
|
+
console.error("No active tab found");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const formatTime = () => new Date().toISOString().split("T")[1].slice(0, 12);
|
|
41
|
+
|
|
42
|
+
const typeColors = {
|
|
43
|
+
log: "\x1b[0m", // default
|
|
44
|
+
info: "\x1b[36m", // cyan
|
|
45
|
+
warn: "\x1b[33m", // yellow
|
|
46
|
+
error: "\x1b[31m", // red
|
|
47
|
+
debug: "\x1b[90m", // gray
|
|
48
|
+
};
|
|
49
|
+
const reset = "\x1b[0m";
|
|
50
|
+
|
|
51
|
+
page.on("console", (msg) => {
|
|
52
|
+
const type = msg.type();
|
|
53
|
+
const color = typeColors[type] || typeColors.log;
|
|
54
|
+
const text = msg.text();
|
|
55
|
+
console.log(`${color}[${formatTime()}] [${type.toUpperCase()}] ${text}${reset}`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
page.on("pageerror", (error) => {
|
|
59
|
+
console.log(`\x1b[31m[${formatTime()}] [PAGE ERROR] ${error.message}${reset}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.error(`Listening for console output... (Ctrl+C to stop)`);
|
|
63
|
+
|
|
64
|
+
if (durationMs) {
|
|
65
|
+
await new Promise((r) => setTimeout(r, durationMs));
|
|
66
|
+
await browser.close();
|
|
67
|
+
} else {
|
|
68
|
+
// Keep running until interrupted
|
|
69
|
+
process.on("SIGINT", async () => {
|
|
70
|
+
console.error("\nStopping...");
|
|
71
|
+
await browser.close();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Keep the process alive
|
|
76
|
+
await new Promise(() => {});
|
|
77
|
+
}
|
package/insights.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { chromium } from "playwright";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PORT = process.env.DEBUG_PORT || 9222;
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const showHelp = args.includes("--help") || args.includes("-h");
|
|
9
|
+
const jsonOutput = args.includes("--json");
|
|
10
|
+
|
|
11
|
+
if (showHelp) {
|
|
12
|
+
console.log("Usage: insights.js [--json]");
|
|
13
|
+
console.log("\nCollect page performance insights and Web Vitals.");
|
|
14
|
+
console.log("\nOptions:");
|
|
15
|
+
console.log(" --json Output as JSON");
|
|
16
|
+
console.log("\nMetrics collected:");
|
|
17
|
+
console.log(" - Page load timing (DOM, load, first paint)");
|
|
18
|
+
console.log(" - Web Vitals (LCP, FID, CLS, FCP, TTFB)");
|
|
19
|
+
console.log(" - Resource counts and sizes");
|
|
20
|
+
console.log(" - JavaScript heap usage");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
|
|
25
|
+
const contexts = browser.contexts();
|
|
26
|
+
const context = contexts[0];
|
|
27
|
+
|
|
28
|
+
if (!context) {
|
|
29
|
+
console.error("No browser context found");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const pages = context.pages();
|
|
34
|
+
const page = pages[pages.length - 1];
|
|
35
|
+
|
|
36
|
+
if (!page) {
|
|
37
|
+
console.error("No active tab found");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Collect performance metrics
|
|
42
|
+
const metrics = await page.evaluate(() => {
|
|
43
|
+
const perf = performance;
|
|
44
|
+
const timing = perf.timing || {};
|
|
45
|
+
const navigation = perf.getEntriesByType("navigation")[0] || {};
|
|
46
|
+
const paint = perf.getEntriesByType("paint") || [];
|
|
47
|
+
const resources = perf.getEntriesByType("resource") || [];
|
|
48
|
+
|
|
49
|
+
// Calculate timing metrics
|
|
50
|
+
const navStart = timing.navigationStart || navigation.startTime || 0;
|
|
51
|
+
const domContentLoaded = (timing.domContentLoadedEventEnd || navigation.domContentLoadedEventEnd || 0) - navStart;
|
|
52
|
+
const loadComplete = (timing.loadEventEnd || navigation.loadEventEnd || 0) - navStart;
|
|
53
|
+
const firstPaint = paint.find((p) => p.name === "first-paint")?.startTime || 0;
|
|
54
|
+
const firstContentfulPaint = paint.find((p) => p.name === "first-contentful-paint")?.startTime || 0;
|
|
55
|
+
const ttfb = (timing.responseStart || navigation.responseStart || 0) - navStart;
|
|
56
|
+
|
|
57
|
+
// Resource breakdown
|
|
58
|
+
const resourceStats = resources.reduce(
|
|
59
|
+
(acc, r) => {
|
|
60
|
+
acc.count++;
|
|
61
|
+
acc.totalSize += r.transferSize || 0;
|
|
62
|
+
const type = r.initiatorType || "other";
|
|
63
|
+
acc.byType[type] = (acc.byType[type] || 0) + 1;
|
|
64
|
+
return acc;
|
|
65
|
+
},
|
|
66
|
+
{ count: 0, totalSize: 0, byType: {} }
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Try to get LCP (requires PerformanceObserver to have recorded it)
|
|
70
|
+
let lcp = null;
|
|
71
|
+
try {
|
|
72
|
+
const lcpEntries = perf.getEntriesByType("largest-contentful-paint");
|
|
73
|
+
if (lcpEntries.length > 0) {
|
|
74
|
+
lcp = lcpEntries[lcpEntries.length - 1].startTime;
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {}
|
|
77
|
+
|
|
78
|
+
// Memory info (Chrome only)
|
|
79
|
+
let memory = null;
|
|
80
|
+
if (perf.memory) {
|
|
81
|
+
memory = {
|
|
82
|
+
usedJSHeapSize: Math.round(perf.memory.usedJSHeapSize / 1024 / 1024),
|
|
83
|
+
totalJSHeapSize: Math.round(perf.memory.totalJSHeapSize / 1024 / 1024),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
url: location.href,
|
|
89
|
+
timing: {
|
|
90
|
+
ttfb: Math.round(ttfb),
|
|
91
|
+
firstPaint: Math.round(firstPaint),
|
|
92
|
+
firstContentfulPaint: Math.round(firstContentfulPaint),
|
|
93
|
+
domContentLoaded: Math.round(domContentLoaded),
|
|
94
|
+
loadComplete: Math.round(loadComplete),
|
|
95
|
+
lcp: lcp ? Math.round(lcp) : null,
|
|
96
|
+
},
|
|
97
|
+
resources: {
|
|
98
|
+
count: resourceStats.count,
|
|
99
|
+
totalSizeKB: Math.round(resourceStats.totalSize / 1024),
|
|
100
|
+
byType: resourceStats.byType,
|
|
101
|
+
},
|
|
102
|
+
memory,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (jsonOutput) {
|
|
107
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
108
|
+
} else {
|
|
109
|
+
console.log(`Page Insights: ${metrics.url}\n`);
|
|
110
|
+
|
|
111
|
+
console.log("Timing:");
|
|
112
|
+
console.log(` TTFB: ${metrics.timing.ttfb}ms`);
|
|
113
|
+
console.log(` First Paint: ${metrics.timing.firstPaint}ms`);
|
|
114
|
+
console.log(` First Contentful Paint: ${metrics.timing.firstContentfulPaint}ms`);
|
|
115
|
+
console.log(` DOM Content Loaded: ${metrics.timing.domContentLoaded}ms`);
|
|
116
|
+
console.log(` Load Complete: ${metrics.timing.loadComplete}ms`);
|
|
117
|
+
if (metrics.timing.lcp) {
|
|
118
|
+
console.log(` Largest Contentful Paint: ${metrics.timing.lcp}ms`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log("\nResources:");
|
|
122
|
+
console.log(` Total: ${metrics.resources.count} requests`);
|
|
123
|
+
console.log(` Size: ${metrics.resources.totalSizeKB} KB`);
|
|
124
|
+
console.log(` Breakdown: ${Object.entries(metrics.resources.byType).map(([k, v]) => `${k}(${v})`).join(", ")}`);
|
|
125
|
+
|
|
126
|
+
if (metrics.memory) {
|
|
127
|
+
console.log("\nMemory:");
|
|
128
|
+
console.log(` JS Heap Used: ${metrics.memory.usedJSHeapSize} MB`);
|
|
129
|
+
console.log(` JS Heap Total: ${metrics.memory.totalJSHeapSize} MB`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await browser.close();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-cdp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Browser automation via Chrome DevTools Protocol - control Chrome, Brave,
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Browser automation via Chrome DevTools Protocol - control Chrome, Brave, Edge with real browser profiles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"browser-cdp": "./cli.js"
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"eval.js",
|
|
17
17
|
"screenshot.js",
|
|
18
18
|
"pick.js",
|
|
19
|
+
"console.js",
|
|
20
|
+
"insights.js",
|
|
19
21
|
"README.md",
|
|
20
22
|
"LICENSE"
|
|
21
23
|
],
|
package/start.js
CHANGED
|
@@ -30,12 +30,6 @@ const BROWSERS = {
|
|
|
30
30
|
? `${process.env.HOME}/Library/Application Support/BraveSoftware/Brave-Browser/`
|
|
31
31
|
: `${process.env.HOME}/.config/BraveSoftware/Brave-Browser/`,
|
|
32
32
|
},
|
|
33
|
-
comet: {
|
|
34
|
-
name: "Comet",
|
|
35
|
-
path: isMac ? "/Applications/Comet.app/Contents/MacOS/Comet" : "/usr/bin/comet",
|
|
36
|
-
process: "Comet",
|
|
37
|
-
profileSource: null,
|
|
38
|
-
},
|
|
39
33
|
edge: {
|
|
40
34
|
name: "Microsoft Edge",
|
|
41
35
|
path: isMac
|
|
@@ -55,19 +49,18 @@ function printUsage() {
|
|
|
55
49
|
console.log("\nBrowsers:");
|
|
56
50
|
console.log(" chrome - Google Chrome (default)");
|
|
57
51
|
console.log(" brave - Brave Browser");
|
|
58
|
-
console.log(" comet - Comet Browser");
|
|
59
52
|
console.log(" edge - Microsoft Edge");
|
|
60
53
|
console.log("\nOptions:");
|
|
61
54
|
console.log(" --isolated Use isolated profile (default: real profile)");
|
|
62
55
|
console.log(" --port=N Use custom debugging port (default: 9222)");
|
|
63
56
|
console.log("\nEnvironment variables:");
|
|
64
|
-
console.log(" BROWSER Default browser (chrome, brave,
|
|
57
|
+
console.log(" BROWSER Default browser (chrome, brave, edge)");
|
|
65
58
|
console.log(" BROWSER_PATH Custom browser executable path");
|
|
66
59
|
console.log(" DEBUG_PORT Custom debugging port");
|
|
67
60
|
console.log("\nExamples:");
|
|
68
61
|
console.log(" start.js # Start Chrome with real profile");
|
|
69
62
|
console.log(" start.js brave # Start Brave with real profile");
|
|
70
|
-
console.log(" start.js
|
|
63
|
+
console.log(" start.js edge --isolated # Start Edge with isolated profile");
|
|
71
64
|
console.log(" start.js --port=9333 # Start Chrome on port 9333");
|
|
72
65
|
process.exit(1);
|
|
73
66
|
}
|
|
@@ -137,6 +130,30 @@ try {
|
|
|
137
130
|
}
|
|
138
131
|
}
|
|
139
132
|
|
|
133
|
+
// Check if browser is already running without CDP (only matters for real profile)
|
|
134
|
+
if (!isolated && browserConfig.process) {
|
|
135
|
+
try {
|
|
136
|
+
const pgrepArgs = isMac
|
|
137
|
+
? ["-x", browserConfig.process]
|
|
138
|
+
: ["-f", browserConfig.path];
|
|
139
|
+
const result = execFileSync("pgrep", pgrepArgs, { encoding: "utf8" }).trim();
|
|
140
|
+
if (result) {
|
|
141
|
+
console.error(`Error: ${browserConfig.name} is already running without CDP enabled.`);
|
|
142
|
+
console.error("");
|
|
143
|
+
console.error("When a browser is already open, launching it again just opens a new");
|
|
144
|
+
console.error("window in the existing process - the CDP flag is ignored.");
|
|
145
|
+
console.error("");
|
|
146
|
+
console.error("Options:");
|
|
147
|
+
console.error(` 1. Quit ${browserConfig.name} and run this command again`);
|
|
148
|
+
console.error(` 2. Use --isolated flag: browser-cdp start ${browserName} --isolated`);
|
|
149
|
+
console.error(" (creates separate instance, but without your cookies/logins)");
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// pgrep returns non-zero if no match - browser not running, proceed
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
140
157
|
// Build browser arguments
|
|
141
158
|
const browserArgs = [`--remote-debugging-port=${port}`];
|
|
142
159
|
|