macOS 上 Chrome CDP 远程调试端口无法监听的排查与解决

macOS 上 Chrome CDP 远程调试端口无法监听的排查与解决

在 macOS 上使用 Playwright 通过 CDP(Chrome DevTools Protocol)连接已有 Chrome 浏览器时,发现 --remote-debugging-port=9222 参数被静默忽略,端口始终不监听。本文记录了完整的排查过程、根本原因和解决方案。

背景

在自动化测试场景中,我们经常需要复用已登录的浏览器 session,避免每次运行脚本都重新登录内网系统。常见的做法是:

  1. --remote-debugging-port=9222 参数启动 Chrome
  2. 通过 Playwright 的 chromium.connectOverCDP() 连接到已有浏览器实例
  3. 直接在已登录的页面上执行自动化操作

这种方式在大多数教程和文档中都是标准做法,理论上非常简单。然而在实际操作中,我们在 macOS + Chrome 145 的环境下遇到了一个令人困惑的问题。

问题现象

环境信息

项目 版本
操作系统 macOS (Darwin)
Chrome 145.0.7632.117
Playwright 1.51.1
Node.js v22.x

复现步骤

# 1. 关闭所有 Chrome 进程
killall "Google Chrome"

# 2. 以远程调试模式启动 Chrome
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

# 3. 检查端口是否在监听
lsof -i :9222
# 结果:无输出(端口未监听)

# 4. 尝试访问 CDP 接口
curl http://127.0.0.1:9222/json/version
# 结果:curl: (7) Failed to connect to 127.0.0.1 port 9222: Connection refused

令人困惑的地方

通过 ps aux | grep Chrome 可以确认 Chrome 主进程确实带有 --remote-debugging-port=9222 参数:

max  69962  /Applications/Google Chrome.app/Contents/MacOS/Google Chrome --remote-debugging-port=9222

进程参数正确,但端口就是不监听。没有任何报错弹窗,Chrome 正常打开,一切看起来都很正常——除了 CDP 不工作。

排查过程

排除网络和防火墙问题

# 检查端口是否被占用
lsof -i :9222
# 结果:无占用

# 检查 macOS 防火墙状态
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
# Firewall is enabled. (State = 1)

# 检查 Chrome 的防火墙规则
/usr/libexec/ApplicationFirewall/socketfilterfw --listapps | grep -A2 Chrome
# /Applications/Google Chrome.app → (Allow incoming connections)

防火墙虽然开启,但 Chrome 已被允许传入连接,排除防火墙问题。

排除启动方式问题

尝试了多种启动方式,均无法开启 CDP:

# 方式1:直接执行二进制
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

# 方式2:通过 open 命令
open -a "Google Chrome" --args --remote-debugging-port=9222

# 方式3:加上 --no-first-run
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --no-first-run

以上方式结果一致:Chrome 正常启动,但 9222 端口不监听。

关键线索:stderr 输出

将 Chrome 的 stderr 重定向到文件后,发现了关键信息:

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 2>/tmp/chrome_stderr.log &

sleep 5
cat /tmp/chrome_stderr.log

输出:

DevTools remote debugging requires a non-default data directory. Specify this using --user-data-dir.

这就是根本原因。 Chrome 将这条关键信息输出到了 stderr,没有弹窗,没有任何可见提示。如果你不特意捕获 stderr,永远不会看到这条消息。

根本原因

Chrome 的安全策略变更

从 Chrome 的某个较新版本开始(至少在 145 版本中已生效),CDP 远程调试增加了一项安全限制:

使用默认用户数据目录(Default Profile)时,--remote-debugging-port 参数会被静默忽略。

这意味着:

  • Chrome 会正常启动并恢复上次的标签页和 session
  • --remote-debugging-port=9222 参数出现在进程列表中(ps aux 可以看到)
  • 但 Chrome 不会在 9222 端口上开启 DevTools WebSocket 监听
  • 唯一的提示是 stderr 中的一行日志

为什么要做这个限制

这是一项安全防护措施。Chrome 的默认用户数据目录包含用户的完整浏览数据(密码、Cookie、历史记录等)。如果允许在默认 profile 上随意开启远程调试端口,本机的任何程序都可以通过 CDP 协议:

  • 读取所有打开页面的 DOM 和 JavaScript 上下文
  • 获取已保存的密码和 Cookie
  • 注入并执行任意 JavaScript
  • 截取页面截图

要求使用非默认数据目录,相当于强制开发者创建一个隔离的调试环境,降低了默认 profile 被意外暴露的风险。

相关 Chromium 源码

在 Chromium 源码中,这个检查位于浏览器启动流程中。当 Chrome 检测到以下条件同时满足时,会跳过 CDP 端口绑定:

  1. 命令行包含 --remote-debugging-port
  2. 未指定 --user-data-dir(即使用默认数据目录)

相关逻辑可参考 Chromium 源码中的 chrome/browser/devtools/remote_debugging_server.cc

解决方案

方案一:指定独立的 user-data-dir(推荐)

# 关闭所有 Chrome 进程
killall "Google Chrome"

# 以 CDP 模式启动,指定独立的数据目录
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=/tmp/chrome-debug-profile

验证 CDP 是否正常:

curl -s http://127.0.0.1:9222/json/version

成功输出:

{
   "Browser": "Chrome/145.0.7632.117",
   "Protocol-Version": "1.3",
   "V8-Version": "14.5.201.12",
   "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

注意事项:

  • 使用新的 user-data-dir 意味着全新的浏览器 profile,首次启动需要重新登录各网站
  • Session 数据会保存在指定目录中,后续再用相同目录启动即可复用登录状态
  • 建议使用固定路径(如 ~/.chrome-debug-profile)而非 /tmp 目录,避免系统清理后丢失 session
# 推荐使用固定路径
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=$HOME/.chrome-debug-profile

如果希望避免重新登录,可以将默认 Profile 的关键文件复制到新的数据目录:

# Chrome 默认数据目录(macOS)
CHROME_DEFAULT="$HOME/Library/Application Support/Google/Chrome"
DEBUG_PROFILE="/tmp/chrome-debug-profile"

# 创建目录
mkdir -p "$DEBUG_PROFILE/Default"

# 复制关键 session 文件
for f in Cookies "Login Data" "Web Data" Preferences "Secure Preferences"; do
  cp "$CHROME_DEFAULT/Default/$f" "$DEBUG_PROFILE/Default/$f" 2>/dev/null
done
cp "$CHROME_DEFAULT/Local State" "$DEBUG_PROFILE/Local State" 2>/dev/null

# 启动 Chrome
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir="$DEBUG_PROFILE"

⚠️ 此方式不保证 100% 有效。Chrome 的 Cookie 文件使用了操作系统级的加密(macOS Keychain),复制后可能无法正常解密。

方案三:Playwright launchPersistentContext 降级方案

如果 CDP 方案不可行,可以使用 Playwright 的 launchPersistentContext API,指定 Chrome 的用户数据目录来复用 Cookie:

import { chromium } from 'playwright';

const context = await chromium.launchPersistentContext(
  '/tmp/playwright-chrome-profile',
  {
    headless: false,
    channel: 'chrome', // 使用系统安装的 Chrome
    viewport: { width: 1280, height: 900 },
  }
);

const page = context.pages()[0] || await context.newPage();

这种方式由 Playwright 自行管理浏览器生命周期,不依赖 CDP,但同样需要在该 profile 中先完成一次登录。

快速诊断清单

当你遇到 CDP 连接不上时,按以下步骤排查:

# 检查项 命令
1 Chrome 进程是否携带 CDP 参数 ps aux | grep Chrome | grep remote-debugging
2 端口是否在监听 lsof -i :9222
3 是否有其他程序占用端口 lsof -i :9222
4 查看 Chrome stderr 输出 启动时重定向 2>/tmp/chrome.log
5 防火墙是否阻止 socketfilterfw --getglobalstate
6 是否指定了 --user-data-dir 检查启动命令

总结

内容
问题 Chrome 的 --remote-debugging-port 参数被静默忽略,CDP 端口不监听
原因 Chrome 145+ 要求 CDP 必须配合 --user-data-dir 使用非默认数据目录
表现 进程参数正确但端口不 LISTEN,仅 stderr 有一行提示
解决 启动时加上 --user-data-dir=<路径> 参数

参考资料

posted @ 2026-03-04 10:40  愤怒的企鹅  阅读(16)  评论(0)    收藏  举报