browser-cdp 0.3.0 → 0.5.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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # browser-cdp
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/browser-cdp.svg)](https://www.npmjs.com/package/browser-cdp)
4
+
3
5
  Browser automation via Chrome DevTools Protocol. Control Chrome, Brave, or Edge using your real browser - same fingerprint, real cookies, no automation detection.
4
6
 
5
7
  ## Install
@@ -12,7 +14,10 @@ npm install -g browser-cdp
12
14
 
13
15
  ```bash
14
16
  # Start browser with CDP enabled
15
- browser-cdp start [browser] [--isolated] [--port=PORT]
17
+ browser-cdp start [browser] [--profile=NAME] [--isolated] [--port=PORT]
18
+
19
+ # Close the browser
20
+ browser-cdp close
16
21
 
17
22
  # Navigate to URL
18
23
  browser-cdp nav <url> [--new]
@@ -26,11 +31,14 @@ browser-cdp screenshot
26
31
  # Interactive element picker
27
32
  browser-cdp pick '<message>'
28
33
 
29
- # Stream browser console output
34
+ # Stream browser console output (network errors, exceptions, logs)
30
35
  browser-cdp console [--duration=SECONDS]
31
36
 
32
37
  # Show page performance metrics
33
38
  browser-cdp insights [--json]
39
+
40
+ # Run Lighthouse audit (Chrome only)
41
+ browser-cdp lighthouse [--json] [--category=NAME]
34
42
  ```
35
43
 
36
44
  ## Environment Variables
@@ -47,6 +55,9 @@ browser-cdp insights [--json]
47
55
  # Start Brave with real profile
48
56
  browser-cdp start brave
49
57
 
58
+ # Start Brave with specific profile (by name)
59
+ browser-cdp start brave --profile=Work
60
+
50
61
  # Start Chrome on custom port
51
62
  DEBUG_PORT=9333 browser-cdp start
52
63
 
@@ -61,15 +72,25 @@ browser-cdp screenshot
61
72
  # Pick elements interactively
62
73
  browser-cdp pick "Select the login button"
63
74
 
64
- # Stream console output for 10 seconds
75
+ # Stream console output (captures network errors, exceptions, console.log)
76
+ browser-cdp console
77
+ # Then refresh the page to see errors
78
+
79
+ # Stream console for 10 seconds
65
80
  browser-cdp console --duration=10
66
81
 
67
82
  # Get page performance insights
68
83
  browser-cdp insights
69
84
  # Returns: TTFB, First Paint, FCP, DOM loaded, resources, memory
70
85
 
71
- # Get insights as JSON
72
- browser-cdp insights --json
86
+ # Run Lighthouse audit (Chrome only - Brave blocks CDP debugger)
87
+ browser-cdp start chrome --isolated
88
+ browser-cdp nav https://example.com
89
+ browser-cdp lighthouse
90
+ # Returns: Performance, Accessibility, Best Practices, SEO scores
91
+
92
+ # Close browser when done
93
+ browser-cdp close
73
94
  ```
74
95
 
75
96
  ## Pre-started Browser
@@ -116,6 +137,10 @@ Use `BROWSER_PATH` env var to override if your browser is installed elsewhere.
116
137
  | Detection | Not detectable as automation | Automation flags present |
117
138
  | Use case | Real-world testing, scraping | Isolated E2E tests |
118
139
 
140
+ ## See Also
141
+
142
+ - [dev-browser](https://github.com/SawyerHood/dev-browser) - Browser automation plugin for Claude Code with LLM-optimized DOM snapshots
143
+
119
144
  ## License
120
145
 
121
146
  MIT
package/cli.js CHANGED
@@ -12,12 +12,15 @@ const args = process.argv.slice(3);
12
12
 
13
13
  const commands = {
14
14
  start: "./start.js",
15
+ close: "./close.js",
15
16
  nav: "./nav.js",
16
17
  eval: "./eval.js",
18
+ dom: "./dom.js",
17
19
  screenshot: "./screenshot.js",
18
20
  pick: "./pick.js",
19
21
  console: "./console.js",
20
22
  insights: "./insights.js",
23
+ lighthouse: "./lighthouse.js",
21
24
  };
22
25
 
23
26
  function printUsage() {
@@ -27,12 +30,15 @@ function printUsage() {
27
30
  console.log("");
28
31
  console.log("Commands:");
29
32
  console.log(" start [browser] Start browser with CDP (uses real profile)");
30
- console.log(" nav <url> Navigate to URL");
31
- console.log(" eval '<code>' Evaluate JavaScript in page");
33
+ console.log(" close Close the browser");
34
+ console.log(" nav <url> Navigate to URL (--console to capture logs)");
35
+ console.log(" eval '<code>' Evaluate JS in page (--console to capture logs)");
36
+ console.log(" dom Capture full page DOM/HTML");
32
37
  console.log(" screenshot Take screenshot of current page");
33
38
  console.log(" pick '<message>' Interactive element picker");
34
39
  console.log(" console Stream browser console output");
35
40
  console.log(" insights Show page performance metrics");
41
+ console.log(" lighthouse Run Lighthouse audit");
36
42
  console.log("");
37
43
  console.log("Environment:");
38
44
  console.log(" DEBUG_PORT CDP port (default: 9222)");
@@ -43,6 +49,7 @@ function printUsage() {
43
49
  console.log(" browser-cdp start brave");
44
50
  console.log(" browser-cdp nav https://google.com");
45
51
  console.log(" browser-cdp eval 'document.title'");
52
+ console.log(" browser-cdp dom > page.html");
46
53
  console.log(" browser-cdp console --duration=10");
47
54
  console.log(" browser-cdp insights --json");
48
55
  process.exit(0);
package/console.js CHANGED
@@ -7,16 +7,19 @@ const DEFAULT_PORT = process.env.DEBUG_PORT || 9222;
7
7
  const args = process.argv.slice(2);
8
8
  const duration = args.find((a) => a.startsWith("--duration="));
9
9
  const durationMs = duration ? parseInt(duration.split("=")[1]) * 1000 : null;
10
+ const shouldReload = args.includes("--reload") || args.includes("-r");
10
11
  const showHelp = args.includes("--help") || args.includes("-h");
11
12
 
12
13
  if (showHelp) {
13
- console.log("Usage: console.js [--duration=SECONDS]");
14
+ console.log("Usage: console.js [options]");
14
15
  console.log("\nCapture browser console output in real-time.");
15
16
  console.log("\nOptions:");
16
17
  console.log(" --duration=N Stop after N seconds (default: run until Ctrl+C)");
18
+ console.log(" --reload, -r Reload the page before capturing");
17
19
  console.log("\nExamples:");
18
20
  console.log(" console.js # Stream console logs until Ctrl+C");
19
21
  console.log(" console.js --duration=5 # Capture for 5 seconds");
22
+ console.log(" console.js --reload # Reload page and capture logs");
20
23
  process.exit(0);
21
24
  }
22
25
 
@@ -30,35 +33,74 @@ if (!context) {
30
33
  }
31
34
 
32
35
  const pages = context.pages();
33
- const page = pages[pages.length - 1];
36
+ // Filter out devtools pages and pick a real page
37
+ const realPages = pages.filter(p => {
38
+ const url = p.url();
39
+ return url.startsWith("http://") || url.startsWith("https://");
40
+ });
41
+ const page = realPages[realPages.length - 1] || pages[pages.length - 1];
34
42
 
35
43
  if (!page) {
36
44
  console.error("No active tab found");
37
45
  process.exit(1);
38
46
  }
39
47
 
48
+ console.error(`Connected to: ${page.url()}`);
49
+
40
50
  const formatTime = () => new Date().toISOString().split("T")[1].slice(0, 12);
41
51
 
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
52
+ const levelColors = {
53
+ verbose: "\x1b[90m", // gray
54
+ info: "\x1b[36m", // cyan
55
+ warning: "\x1b[33m", // yellow
56
+ error: "\x1b[31m", // red
48
57
  };
49
58
  const reset = "\x1b[0m";
50
59
 
60
+ // Use CDP directly for Log domain (captures network errors, etc.)
61
+ const cdp = await page.context().newCDPSession(page);
62
+ await cdp.send("Log.enable");
63
+
64
+ cdp.on("Log.entryAdded", ({ entry }) => {
65
+ const color = levelColors[entry.level] || levelColors.info;
66
+ const source = entry.source ? `[${entry.source}]` : "";
67
+ console.log(`${color}[${formatTime()}] [${entry.level.toUpperCase()}]${source} ${entry.text}${reset}`);
68
+ if (entry.url) {
69
+ console.log(`${color} URL: ${entry.url}${reset}`);
70
+ }
71
+ });
72
+
73
+ // Also capture runtime exceptions
74
+ await cdp.send("Runtime.enable");
75
+ cdp.on("Runtime.exceptionThrown", ({ exceptionDetails }) => {
76
+ const text = exceptionDetails.exception?.description || exceptionDetails.text;
77
+ console.log(`\x1b[31m[${formatTime()}] [EXCEPTION] ${text}${reset}`);
78
+ });
79
+
80
+ // Capture network failures (ERR_BLOCKED_BY_CLIENT, etc.)
81
+ await cdp.send("Network.enable");
82
+ cdp.on("Network.loadingFailed", ({ requestId, errorText, blockedReason }) => {
83
+ const reason = blockedReason ? ` (${blockedReason})` : "";
84
+ console.log(`\x1b[31m[${formatTime()}] [NETWORK ERROR] ${errorText}${reason}${reset}`);
85
+ });
86
+
87
+ // Keep Playwright listeners for console.log() calls
51
88
  page.on("console", (msg) => {
52
89
  const type = msg.type();
53
- const color = typeColors[type] || typeColors.log;
90
+ const color = levelColors[type] || levelColors.info;
54
91
  const text = msg.text();
55
- console.log(`${color}[${formatTime()}] [${type.toUpperCase()}] ${text}${reset}`);
92
+ console.log(`${color}[${formatTime()}] [CONSOLE.${type.toUpperCase()}] ${text}${reset}`);
56
93
  });
57
94
 
58
95
  page.on("pageerror", (error) => {
59
96
  console.log(`\x1b[31m[${formatTime()}] [PAGE ERROR] ${error.message}${reset}`);
60
97
  });
61
98
 
99
+ if (shouldReload) {
100
+ console.error("Reloading page...");
101
+ await page.reload();
102
+ }
103
+
62
104
  console.error(`Listening for console output... (Ctrl+C to stop)`);
63
105
 
64
106
  if (durationMs) {
package/eval.js CHANGED
@@ -4,13 +4,25 @@ import { chromium } from "playwright";
4
4
 
5
5
  const DEFAULT_PORT = process.env.DEBUG_PORT || 9222;
6
6
 
7
- const code = process.argv.slice(2).join(" ");
8
- if (!code) {
9
- console.log("Usage: eval.js 'code'");
7
+ const args = process.argv.slice(2);
8
+ const showHelp = args.includes("--help") || args.includes("-h");
9
+ const captureConsole = args.includes("--console");
10
+ const durationArg = args.find((a) => a.startsWith("--duration="));
11
+ const durationMs = durationArg ? parseInt(durationArg.split("=")[1]) * 1000 : 3000;
12
+
13
+ // Get code (everything that's not a flag)
14
+ const code = args.filter((a) => !a.startsWith("--")).join(" ");
15
+
16
+ if (showHelp || !code) {
17
+ console.log("Usage: eval.js '<code>' [options]");
18
+ console.log("\nOptions:");
19
+ console.log(" --console Capture console output during evaluation");
20
+ console.log(" --duration=N With --console, capture for N seconds (default: 3)");
10
21
  console.log("\nExamples:");
11
22
  console.log(' eval.js "document.title"');
12
23
  console.log(" eval.js \"document.querySelectorAll('a').length\"");
13
- process.exit(1);
24
+ console.log(" eval.js \"fetch('/api/data')\" --console");
25
+ process.exit(showHelp ? 0 : 1);
14
26
  }
15
27
 
16
28
  const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
@@ -23,13 +35,64 @@ if (!context) {
23
35
  }
24
36
 
25
37
  const pages = context.pages();
26
- const page = pages[pages.length - 1];
38
+ // Filter out devtools pages and pick a real page
39
+ const realPages = pages.filter(p => {
40
+ const url = p.url();
41
+ return url.startsWith("http://") || url.startsWith("https://");
42
+ });
43
+ const page = realPages[realPages.length - 1] || pages[pages.length - 1];
27
44
 
28
45
  if (!page) {
29
46
  console.error("No active tab found");
30
47
  process.exit(1);
31
48
  }
32
49
 
50
+ // Set up console capture BEFORE evaluation
51
+ if (captureConsole) {
52
+ const formatTime = () => new Date().toISOString().split("T")[1].slice(0, 12);
53
+ const levelColors = {
54
+ verbose: "\x1b[90m",
55
+ info: "\x1b[36m",
56
+ warning: "\x1b[33m",
57
+ error: "\x1b[31m",
58
+ };
59
+ const reset = "\x1b[0m";
60
+
61
+ const cdp = await page.context().newCDPSession(page);
62
+ await cdp.send("Log.enable");
63
+ await cdp.send("Runtime.enable");
64
+ await cdp.send("Network.enable");
65
+
66
+ cdp.on("Log.entryAdded", ({ entry }) => {
67
+ const color = levelColors[entry.level] || levelColors.info;
68
+ const source = entry.source ? `[${entry.source}]` : "";
69
+ console.log(`${color}[${formatTime()}] [${entry.level.toUpperCase()}]${source} ${entry.text}${reset}`);
70
+ if (entry.url) {
71
+ console.log(`${color} URL: ${entry.url}${reset}`);
72
+ }
73
+ });
74
+
75
+ cdp.on("Runtime.exceptionThrown", ({ exceptionDetails }) => {
76
+ const text = exceptionDetails.exception?.description || exceptionDetails.text;
77
+ console.log(`\x1b[31m[${formatTime()}] [EXCEPTION] ${text}${reset}`);
78
+ });
79
+
80
+ cdp.on("Network.loadingFailed", ({ requestId, errorText, blockedReason }) => {
81
+ const reason = blockedReason ? ` (${blockedReason})` : "";
82
+ console.log(`\x1b[31m[${formatTime()}] [NETWORK ERROR] ${errorText}${reason}${reset}`);
83
+ });
84
+
85
+ page.on("console", (msg) => {
86
+ const type = msg.type();
87
+ const color = levelColors[type] || levelColors.info;
88
+ console.log(`${color}[${formatTime()}] [CONSOLE.${type.toUpperCase()}] ${msg.text()}${reset}`);
89
+ });
90
+
91
+ page.on("pageerror", (error) => {
92
+ console.log(`\x1b[31m[${formatTime()}] [PAGE ERROR] ${error.message}${reset}`);
93
+ });
94
+ }
95
+
33
96
  let result;
34
97
 
35
98
  try {
@@ -44,6 +107,7 @@ try {
44
107
  process.exit(1);
45
108
  }
46
109
 
110
+ // Print result
47
111
  if (Array.isArray(result)) {
48
112
  for (let i = 0; i < result.length; i++) {
49
113
  if (i > 0) console.log("");
@@ -59,4 +123,10 @@ if (Array.isArray(result)) {
59
123
  console.log(result);
60
124
  }
61
125
 
126
+ // Wait for async console output
127
+ if (captureConsole) {
128
+ console.error(`\nListening for ${durationMs / 1000}s...`);
129
+ await new Promise((r) => setTimeout(r, durationMs));
130
+ }
131
+
62
132
  await browser.close();
package/insights.js CHANGED
@@ -31,7 +31,12 @@ if (!context) {
31
31
  }
32
32
 
33
33
  const pages = context.pages();
34
- const page = pages[pages.length - 1];
34
+ // Filter out devtools pages and pick a real page
35
+ const realPages = pages.filter(p => {
36
+ const url = p.url();
37
+ return url.startsWith("http://") || url.startsWith("https://");
38
+ });
39
+ const page = realPages[realPages.length - 1] || pages[pages.length - 1];
35
40
 
36
41
  if (!page) {
37
42
  console.error("No active tab found");
package/nav.js CHANGED
@@ -4,35 +4,106 @@ import { chromium } from "playwright";
4
4
 
5
5
  const DEFAULT_PORT = process.env.DEBUG_PORT || 9222;
6
6
 
7
- let url = process.argv[2];
8
- const newTab = process.argv[3] === "--new";
7
+ const args = process.argv.slice(2);
8
+ const showHelp = args.includes("--help") || args.includes("-h");
9
+ const newTab = args.includes("--new");
10
+ const captureConsole = args.includes("--console");
11
+ const durationArg = args.find((a) => a.startsWith("--duration="));
12
+ const durationMs = durationArg ? parseInt(durationArg.split("=")[1]) * 1000 : 5000;
9
13
 
10
- // Add protocol if missing
11
- if (url && !url.match(/^https?:\/\//i)) {
12
- url = "https://" + url;
13
- }
14
+ // Get URL (first arg that doesn't start with --)
15
+ let url = args.find((a) => !a.startsWith("--"));
14
16
 
15
- if (!url) {
16
- console.log("Usage: nav.js <url> [--new]");
17
+ if (showHelp || !url) {
18
+ console.log("Usage: nav.js <url> [options]");
19
+ console.log("\nOptions:");
20
+ console.log(" --new Open in new tab");
21
+ console.log(" --console Capture console output during navigation");
22
+ console.log(" --duration=N With --console, capture for N seconds (default: 5)");
17
23
  console.log("\nExamples:");
18
- console.log(" nav.js example.com # Navigate current tab");
19
- console.log(" nav.js example.com --new # Open in new tab");
20
- process.exit(1);
24
+ console.log(" nav.js example.com # Navigate current tab");
25
+ console.log(" nav.js example.com --new # Open in new tab");
26
+ console.log(" nav.js example.com --console # Navigate and capture console");
27
+ process.exit(showHelp ? 0 : 1);
28
+ }
29
+
30
+ // Add protocol if missing
31
+ if (!url.match(/^https?:\/\//i)) {
32
+ url = "https://" + url;
21
33
  }
22
34
 
23
35
  const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
24
36
  const contexts = browser.contexts();
25
37
  const context = contexts[0] || await browser.newContext();
26
38
 
39
+ let page;
27
40
  if (newTab) {
28
- const page = await context.newPage();
29
- await page.goto(url, { waitUntil: "domcontentloaded" });
30
- console.log("Opened:", url);
41
+ page = await context.newPage();
31
42
  } else {
32
43
  const pages = context.pages();
33
- const page = pages[pages.length - 1] || await context.newPage();
34
- await page.goto(url, { waitUntil: "domcontentloaded" });
35
- console.log("Navigated to:", url);
44
+ const realPages = pages.filter(p => {
45
+ const u = p.url();
46
+ return u.startsWith("http://") || u.startsWith("https://") || u === "about:blank";
47
+ });
48
+ page = realPages[realPages.length - 1] || pages[pages.length - 1] || await context.newPage();
49
+ }
50
+
51
+ // Set up console capture BEFORE navigation
52
+ if (captureConsole) {
53
+ const formatTime = () => new Date().toISOString().split("T")[1].slice(0, 12);
54
+ const levelColors = {
55
+ verbose: "\x1b[90m",
56
+ info: "\x1b[36m",
57
+ warning: "\x1b[33m",
58
+ error: "\x1b[31m",
59
+ };
60
+ const reset = "\x1b[0m";
61
+
62
+ const cdp = await page.context().newCDPSession(page);
63
+ await cdp.send("Log.enable");
64
+ await cdp.send("Runtime.enable");
65
+ await cdp.send("Network.enable");
66
+
67
+ cdp.on("Log.entryAdded", ({ entry }) => {
68
+ const color = levelColors[entry.level] || levelColors.info;
69
+ const source = entry.source ? `[${entry.source}]` : "";
70
+ console.log(`${color}[${formatTime()}] [${entry.level.toUpperCase()}]${source} ${entry.text}${reset}`);
71
+ if (entry.url) {
72
+ console.log(`${color} URL: ${entry.url}${reset}`);
73
+ }
74
+ });
75
+
76
+ cdp.on("Runtime.exceptionThrown", ({ exceptionDetails }) => {
77
+ const text = exceptionDetails.exception?.description || exceptionDetails.text;
78
+ console.log(`\x1b[31m[${formatTime()}] [EXCEPTION] ${text}${reset}`);
79
+ });
80
+
81
+ cdp.on("Network.loadingFailed", ({ requestId, errorText, blockedReason }) => {
82
+ const reason = blockedReason ? ` (${blockedReason})` : "";
83
+ console.log(`\x1b[31m[${formatTime()}] [NETWORK ERROR] ${errorText}${reason}${reset}`);
84
+ });
85
+
86
+ page.on("console", (msg) => {
87
+ const type = msg.type();
88
+ const color = levelColors[type] || levelColors.info;
89
+ console.log(`${color}[${formatTime()}] [CONSOLE.${type.toUpperCase()}] ${msg.text()}${reset}`);
90
+ });
91
+
92
+ page.on("pageerror", (error) => {
93
+ console.log(`\x1b[31m[${formatTime()}] [PAGE ERROR] ${error.message}${reset}`);
94
+ });
95
+
96
+ console.error(`Navigating to ${url} (capturing console for ${durationMs / 1000}s)...`);
97
+ }
98
+
99
+ await page.goto(url, { waitUntil: "domcontentloaded" });
100
+
101
+ if (captureConsole) {
102
+ console.error(`Loaded: ${url}`);
103
+ await new Promise((r) => setTimeout(r, durationMs));
104
+ console.error("Done.");
105
+ } else {
106
+ console.log(newTab ? "Opened:" : "Navigated to:", url);
36
107
  }
37
108
 
38
109
  await browser.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-cdp",
3
- "version": "0.3.0",
3
+ "version": "0.5.1",
4
4
  "description": "Browser automation via Chrome DevTools Protocol - control Chrome, Brave, Edge with real browser profiles",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,6 +33,7 @@
33
33
  "url": "https://github.com/dpaluy/browser-cdp/issues"
34
34
  },
35
35
  "dependencies": {
36
+ "lighthouse": "^13.0.1",
36
37
  "playwright": "^1.49.0"
37
38
  },
38
39
  "keywords": [
package/pick.js CHANGED
@@ -22,7 +22,12 @@ if (!context) {
22
22
  }
23
23
 
24
24
  const pages = context.pages();
25
- const page = pages[pages.length - 1];
25
+ // Filter out devtools pages and pick a real page
26
+ const realPages = pages.filter(p => {
27
+ const url = p.url();
28
+ return url.startsWith("http://") || url.startsWith("https://");
29
+ });
30
+ const page = realPages[realPages.length - 1] || pages[pages.length - 1];
26
31
 
27
32
  if (!page) {
28
33
  console.error("No active tab found");
package/screenshot.js CHANGED
@@ -16,7 +16,12 @@ if (!context) {
16
16
  }
17
17
 
18
18
  const pages = context.pages();
19
- const page = pages[pages.length - 1];
19
+ // Filter out devtools pages and pick a real page
20
+ const realPages = pages.filter(p => {
21
+ const url = p.url();
22
+ return url.startsWith("http://") || url.startsWith("https://");
23
+ });
24
+ const page = realPages[realPages.length - 1] || pages[pages.length - 1];
20
25
 
21
26
  if (!page) {
22
27
  console.error("No active tab found");
package/start.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn, execFileSync } from "node:child_process";
4
- import { existsSync } from "node:fs";
4
+ import { existsSync, readFileSync } from "node:fs";
5
5
  import { platform } from "node:os";
6
6
  import { chromium } from "playwright";
7
7
 
@@ -44,24 +44,53 @@ const BROWSERS = {
44
44
 
45
45
  const DEFAULT_PORT = 9222;
46
46
 
47
+ // Resolve profile name to directory (supports both "Profile 1" and "Suppli" style names)
48
+ function resolveProfileDir(profileSource, profileName) {
49
+ // If it looks like a directory name already, use it
50
+ if (profileName === "Default" || profileName.startsWith("Profile ")) {
51
+ return profileName;
52
+ }
53
+
54
+ // Try to find profile by name in Local State
55
+ if (profileSource) {
56
+ try {
57
+ const localStatePath = `${profileSource}Local State`;
58
+ const localState = JSON.parse(readFileSync(localStatePath, "utf8"));
59
+ const profiles = localState.profile?.info_cache || {};
60
+
61
+ for (const [dir, info] of Object.entries(profiles)) {
62
+ if (info.name?.toLowerCase() === profileName.toLowerCase()) {
63
+ return dir;
64
+ }
65
+ }
66
+ } catch {
67
+ // Fall through to return original name
68
+ }
69
+ }
70
+
71
+ return profileName;
72
+ }
73
+
47
74
  function printUsage() {
48
- console.log("Usage: start.js [browser] [--isolated] [--port=PORT]");
75
+ console.log("Usage: start.js [browser] [--profile=NAME] [--isolated] [--port=PORT]");
49
76
  console.log("\nBrowsers:");
50
77
  console.log(" chrome - Google Chrome (default)");
51
78
  console.log(" brave - Brave Browser");
52
79
  console.log(" edge - Microsoft Edge");
53
80
  console.log("\nOptions:");
54
- console.log(" --isolated Use isolated profile (default: real profile)");
55
- console.log(" --port=N Use custom debugging port (default: 9222)");
81
+ console.log(" --profile=NAME Use specific profile by name or directory");
82
+ console.log(" --isolated Use isolated profile (default: real profile)");
83
+ console.log(" --port=N Use custom debugging port (default: 9222)");
56
84
  console.log("\nEnvironment variables:");
57
85
  console.log(" BROWSER Default browser (chrome, brave, edge)");
58
86
  console.log(" BROWSER_PATH Custom browser executable path");
59
87
  console.log(" DEBUG_PORT Custom debugging port");
60
88
  console.log("\nExamples:");
61
- console.log(" start.js # Start Chrome with real profile");
62
- console.log(" start.js brave # Start Brave with real profile");
63
- console.log(" start.js edge --isolated # Start Edge with isolated profile");
64
- console.log(" start.js --port=9333 # Start Chrome on port 9333");
89
+ console.log(" start.js # Start Chrome with default profile");
90
+ console.log(" start.js brave # Start Brave with default profile");
91
+ console.log(" start.js brave --profile=Work # Start Brave with 'Work' profile");
92
+ console.log(" start.js edge --isolated # Start Edge with isolated profile");
93
+ console.log(" start.js --port=9333 # Start Chrome on port 9333");
65
94
  process.exit(1);
66
95
  }
67
96
 
@@ -69,6 +98,7 @@ function printUsage() {
69
98
  const args = process.argv.slice(2);
70
99
  let browserName = process.env.BROWSER || "chrome";
71
100
  let isolated = false;
101
+ let profile = null;
72
102
  let port = parseInt(process.env.DEBUG_PORT) || DEFAULT_PORT;
73
103
 
74
104
  for (const arg of args) {
@@ -76,6 +106,8 @@ for (const arg of args) {
76
106
  printUsage();
77
107
  } else if (arg === "--isolated") {
78
108
  isolated = true;
109
+ } else if (arg.startsWith("--profile=")) {
110
+ profile = arg.split("=")[1];
79
111
  } else if (arg.startsWith("--port=")) {
80
112
  port = parseInt(arg.split("=")[1]);
81
113
  } else if (BROWSERS[arg]) {
@@ -155,7 +187,11 @@ if (!isolated && browserConfig.process) {
155
187
  }
156
188
 
157
189
  // Build browser arguments
158
- const browserArgs = [`--remote-debugging-port=${port}`];
190
+ const browserArgs = [
191
+ `--remote-debugging-port=${port}`,
192
+ // Required for Lighthouse/CDP debugger access (prevents bfcache blocking)
193
+ "--disable-features=ProcessPerSiteUpToMainFrameThreshold",
194
+ ];
159
195
 
160
196
  if (isolated) {
161
197
  const cacheBase = isMac
@@ -164,10 +200,15 @@ if (isolated) {
164
200
  const profileDir = `${cacheBase}/browser-cdp/${browserName}`;
165
201
  execFileSync("mkdir", ["-p", profileDir], { stdio: "ignore" });
166
202
  browserArgs.push(`--user-data-dir=${profileDir}`);
203
+ } else if (profile) {
204
+ // Resolve profile name to directory if needed
205
+ const profileDir = resolveProfileDir(browserConfig.profileSource, profile);
206
+ browserArgs.push(`--profile-directory=${profileDir}`);
167
207
  }
168
208
 
169
209
  // Start browser
170
- console.log(`Starting ${browserConfig.name} on port ${port}${isolated ? " (isolated)" : ""}...`);
210
+ const profileInfo = isolated ? " (isolated)" : profile ? ` (${profile})` : "";
211
+ console.log(`Starting ${browserConfig.name} on port ${port}${profileInfo}...`);
171
212
 
172
213
  spawn(browserPath, browserArgs, {
173
214
  detached: true,
@@ -192,4 +233,4 @@ if (!connected) {
192
233
  process.exit(1);
193
234
  }
194
235
 
195
- console.log(`${browserConfig.name} started on :${port}${isolated ? " (isolated)" : ""}`);
236
+ console.log(`${browserConfig.name} started on :${port}${profileInfo}`);