首页
/ VS Code Copilot Chat 扩展自动化实战:基于 CDP 与 @playwright/cli 启动、交互与调试指南

VS Code Copilot Chat 扩展自动化实战:基于 CDP 与 @playwright/cli 启动、交互与调试指南

2026-09-05 09:40:23作者:范靓好Udolf

本文围绕 Copilot Chat 扩展仓库中的自动化技能文档 SKILL.md,完整讲解如何通过 @playwright/cli 挂载到 VS Code Insiders 暴露的 Chrome DevTools Protocol(CDP)端口,实现对 Copilot Chat 扩展的 UI 自动化:从带调试端口启动实例、attach 附加连接、用 snapshot/ref 工作流操作 Monaco 编辑器输入框,到代码修改后的重启与清理。读完你可以独立搭建一套可复现的"启动—交互—截图取证—排障"自动化链路,用于测试扩展 UI、自动操作聊天面板或调试扩展行为。

原理:为什么可以用 Web 自动化工具驱动 VS Code

VS Code 构建于 Electron/Chromium 之上,启动时可以通过 --remote-debugging-port 参数暴露一个 CDP 端口。@playwright/cli 能够直接 attach 到这个端口,从而把操作网页的那套 snapshot(快照)—interact(交互)—re-snapshot(再快照) 工作流原样用到 VS Code 窗口上:

  1. Build:先编译扩展;
  2. Launch:用扩展开发参数加远程调试端口启动 VS Code Insiders;
  3. Attachnpx @playwright/cli 附加到 CDP 端口;
  4. Snapshot:快照发现可交互元素(每个元素带 ref 引用);
  5. Interact:用元素 ref 执行 fill/type/press 等命令;
  6. Re-snapshot:导航或状态变化后重新快照获取新 ref。

需要注意:CSS 选择器是内部实现细节。像 .interactive-input-part.monaco-editor.view-line 这类选择器属于 VS Code 内部结构,可能随版本变化。自动化在 VS Code 更新后失效时,应重新 snapshot 并检查选择器是否改变。

前置条件

技能文档明确列出四项前提,结合当前仓库可以逐条核实:

  • @playwright/cli 可用性:在仓库根目录执行 npm install 后即可通过 npx @playwright/cli 调用命令(该工具通过 devDependencies 提供),也可以 npm install -g @playwright/cli 全局安装。从源码结构看,extensions/copilot/package.json 的 devDependencies 中声明了 playwright: ^1.61.1,与文档描述一致。
  • 必须使用 code-insiders:该扩展依赖大量 proposed VS Code API,Stable 版 VS Code 不会激活它,必须用 VS Code Insiders。文档写作时扩展使用 58 个 proposed API;而当前仓库中 package.jsonenabledApiProposals 已列出 63 个 proposed API,engines.vscode 要求为 ^1.137.0——proposed API 数量还在增长,这进一步说明 Insiders 是唯一可行的宿主。
  • 扩展必须先编译npm run compile 做一次性构建,npm run watch 用于迭代开发。对应 package.json 中的脚本定义:compilenode .esbuild.mts --devwatchnpm-run-all -lp watch:esbuild watch:typecheck
  • CSS 选择器是内部实现细节(见上文原理部分)。

启动:命令、关键参数与两个必踩的坑

启动命令

# 构建并用扩展开发模式启动
npm run compile
# 使用持久化的 user-data-dir,保证认证状态跨会话保留。
# .vscode-ext-debug 相对于项目根目录 —— 在 worktree 中同样可用,且已被 gitignore。
code-insiders --extensionDevelopmentPath="$PWD" --remote-debugging-port=9223 --user-data-dir="$PWD/.vscode-ext-debug"
# Windows (PowerShell):
# code-insiders --extensionDevelopmentPath="$PWD" --remote-debugging-port=9223 --user-data-dir="$PWD\.vscode-ext-debug"

# 等待 VS Code 启动完成,重试直到附加成功
for i in 1 2 3 4 5; do npx @playwright/cli attach --cdp=http://127.0.0.1:9223 2>/dev/null && break || sleep 3; done

# 确认连接到的是正确目标(而不是 about:blank)
# 如果 tab-list 显示的目标不对,先 npx @playwright/cli close 再重新 attach
npx @playwright/cli tab-list
npx @playwright/cli snapshot

$PWD 在仓库根目录(即 extensions/copilot)下运行时指当前工作目录;仓库自带的调试配置 extensions/copilot/.vscode/launch.json 中同样使用 --extensionDevelopmentPath=${workspaceFolder} 的模式来启动扩展开发宿主。

关键参数说明

参数 作用 要点
--extensionDevelopmentPath=<path> 从源码加载扩展(必须先编译) 在仓库根目录运行时使用 $PWD
--remote-debugging-port=9223 开启 CDP 选 9223 是为了避开其他应用常用 9222 的端口冲突
--user-data-dir=<path> 使用独立 profile,启动新进程 必须用持久路径(如 $PWD/.vscode-ext-debug),不要用 /tmp/...

其中 --user-data-dir 目录被 extensions/copilot/.gitignore 第 45 行的 .vscode-ext-debug/ 规则忽略,不会污染版本库。

坑 1:缺少 --user-data-dir 时进程直接退出

没有 --user-data-dir 时,VS Code 会检测到已运行的实例、把参数转发过去然后立即退出——你会看到 "Sent env to running instance. Terminating..." 且 CDP 永远不会启动。使用独立 user-data-dir 才能保证启动一个真正的新进程并监听 9223 端口。

坑 2:临时目录会丢失认证

Copilot Chat 扩展需要已认证的 GitHub 会话才能工作。用临时目录(如 /tmp/...)会创建一个全新 profile,没有认证状态——你会撞上 "Sign in to use Copilot" 登录墙,模型解析随之失败并报 "Language model unavailable"。必须始终使用持久化的 --user-data-dir(如 $PWD/.vscode-ext-debug):首次启动时手动登录一次,后续启动就会复用该认证会话,认证、设置和扩展状态都会跨会话保留。

Attach 与多 Webview 的 Tab 管理

# 附加到指定 CDP 端口
npx @playwright/cli attach --cdp=http://127.0.0.1:9223

attach 之后,后续所有命令都作用于已连接的应用,无需重复附加。

VS Code 内部使用多个 webview(主窗口、侧边栏、各个 webview 面板等)。当快照中看不到目标元素时,用 tab 命令列出并切换:

# 列出所有可用目标(窗口、webview 等)
npx @playwright/cli tab-list

# 按索引切换到指定 tab
npx @playwright/cli tab-select 2

截图取证:建立可视化的"证据链"

文档强调在每个关键节点截图——启动后、交互前后、出错时。截图能直观记录 UI 状态,对调试失败和记录成果价值很大。推荐把截图保存进 .vscode-ext-debug/screenshots/(已被 gitignore),并用带时间戳的子目录隔离每次运行,避免互相覆盖:

# 为本次运行创建带时间戳的截图目录
SCREENSHOT_DIR=".vscode-ext-debug/screenshots/$(date +%Y-%m-%dT%H-%M-%S)"
mkdir -p "$SCREENSHOT_DIR"

# Windows (PowerShell):
# $screenshotDir = ".vscode-ext-debug\screenshots\$(Get-Date -Format 'yyyy-MM-ddTHH-mm-ss')"
# New-Item -ItemType Directory -Force -Path $screenshotDir

# 保存一张截图
npx @playwright/cli screenshot --filename="$SCREENSHOT_DIR/after-launch.png"

在 macOS 上若截图报 "Permission denied",需要给终端授予屏幕录制权限(System Settings → Privacy & Security → Screen Recording);作为兜底,可以用后文的 eval 校验片段确认文本是否已输入,它不依赖屏幕权限。

操作 Monaco 编辑器:兼容性矩阵与推荐姿势

VS Code 中所有文本输入——包括 Copilot Chat 输入框——都由 Monaco Editor 承载。Monaco 编辑器在可访问性快照中表现为 textbox,但要用特定命令才能正确交互。

fill <ref> —— 首选方案

fill 命令带快照 ref,一步完成聚焦和输入:

# 快照找到聊天输入框的 ref
npx @playwright/cli snapshot
# 寻找形如: textbox "The editor is not accessible..." [ref=e51]

# 直接用 ref 填入 —— 自动处理聚焦
npx @playwright/cli fill e51 "Hello from George!"

# 发送消息
npx @playwright/cli press Enter

# 重新快照前等待响应生成完毕。
# 轮询直到 "Stop generating" 按钮消失:
for i in $(seq 1 30); do
  npx @playwright/cli snapshot 2>/dev/null | grep -q "Stop generating" || break
  sleep 1
done
npx @playwright/cli snapshot

这是最简单、最可靠的方式,对主编辑器的聊天输入框和侧边栏聊天面板都有效。

提示:如果 fill 静默丢字(编辑器保持为空),ref 可能已过期或编辑器尚未就绪。重新 snapshot 拿到新 ref 再试;可以用下文"校验 Monaco 中文本"的片段确认文本是否已输入。

type —— 聚焦之后可用

如果焦点已在 Monaco 编辑器上,type 可以工作:

# 先聚焦(可用空字符串 fill,或 JS 鼠标事件聚焦)
npx @playwright/cli fill e51 ""
# 随后 type 可输入后续文本
npx @playwright/cli type "More text here"

press —— 单键按压,万能兜底

只要焦点在 Monaco 编辑器上,press 永远可用,适合特殊键、快捷键,以及逐字符输入文本:

# 逐字符输入(所有构建都可用)
npx @playwright/cli press H
npx @playwright/cli press e
npx @playwright/cli press l
npx @playwright/cli press l
npx @playwright/cli press o
npx @playwright/cli press Space  # 空格用 "Space"

# 全选
# macOS:
npx @playwright/cli press Meta+a
# Linux / Windows:
npx @playwright/cli press Control+a

npx @playwright/cli press Backspace    # 删除选中内容
npx @playwright/cli press Enter        # 发送消息 / 换行

# 发送到新会话
# macOS:
npx @playwright/cli press Meta+Shift+Enter
# Linux / Windows:
npx @playwright/cli press Control+Shift+Enter

不可行的方式

方式 结果 原因
对编辑器 click <ref> "Element blocked by another element" Monaco 在 textarea 之上覆盖了一个透明 div
通过 eval 设置 textarea.value 并派发 input 事件 无效果 Monaco 不读取 textarea 的 value 属性

兜底:用 JavaScript 鼠标事件聚焦

fill 不生效时(例如 ref 过期),可以用 JS 事件聚焦编辑器:

npx @playwright/cli eval '
(() => {
  const inputPart = document.querySelector(".interactive-input-part");
  const editor = inputPart.querySelector(".monaco-editor");
  const rect = editor.getBoundingClientRect();
  const x = rect.x + rect.width / 2;
  const y = rect.y + rect.height / 2;
  editor.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, clientX: x, clientY: y }));
  editor.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, clientX: x, clientY: y }));
  editor.dispatchEvent(new MouseEvent("click", { bubbles: true, clientX: x, clientY: y }));
  return "activeElement: " + document.activeElement?.className;
})()'

# JS 聚焦之后,type 和 press 即可工作
npx @playwright/cli type "Text after JS focus"

JS 鼠标事件触发后,document.activeElement 会成为带 native-edit-context 类的 DIV——这正是 VS Code 的原生文本编辑表面。

校验 Monaco 中文本是否输入成功

Monaco 把文本渲染在 .view-line 元素里,而不是 textarea:

npx @playwright/cli eval '
(() => {
  const inputPart = document.querySelector(".interactive-input-part");
  return Array.from(inputPart.querySelectorAll(".view-line")).map(vl => vl.textContent).join("|");
})()'

清空 Monaco 输入

# macOS:
npx @playwright/cli press Meta+a
# Linux / Windows:
npx @playwright/cli press Control+a

npx @playwright/cli press Backspace

代码修改后的重启工作流

修改扩展源码后必须重启 VS Code 才能加载新构建——extension host 只在启动时加载编译后的 bundle,不存在热重载。标准步骤:

  1. 重新编译扩展
  2. 杀掉使用本调试 user-data-dir 的 VS Code 实例
  3. 用相同参数重新启动
# 1. 重新编译
npm run compile

# 2. 杀掉绑定本调试 profile 的 VS Code 实例,再重新启动
# macOS / Linux:
kill $(ps ax -ww -o pid,command | grep "$PWD/.vscode-ext-debug" | grep -v grep | awk '{print $1}' | head -1)

# Windows (PowerShell):
# Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like "*$PWD\.vscode-ext-debug*" } | ForEach-Object { Stop-Process -Id $_.ProcessId }

# 3. 重新启动
code-insiders \
  --extensionDevelopmentPath="$PWD" \
  --remote-debugging-port=9223 \
  --user-data-dir="$PWD/.vscode-ext-debug"

# 4. 重新附加 npx @playwright/cli
for i in 1 2 3 4 5; do npx @playwright/cli attach --cdp=http://127.0.0.1:9223 2>/dev/null && break || sleep 3; done
npx @playwright/cli snapshot

提示:高频迭代时,可在另一个终端运行 npm run watch 让编译自动进行,但仍需杀掉并重启 VS Code 才能加载新 bundle。

排障清单

"Connection refused" 或 "Cannot connect"

  • 确认 VS Code Insiders 是用 --remote-debugging-port=9223 启动的;
  • 若 VS Code 此前已在运行,退出后带该参数重启;
  • 检查端口是否被其他进程占用:
    • macOS / Linux:lsof -i :9223
    • Windows:netstat -ano | findstr 9223

快照中看不到目标元素

VS Code 使用多个 webview。用 npx @playwright/cli tab-list 列出目标,再用 npx @playwright/cli tab-select <index> 切换到正确的 tab。

Monaco 输入框无法输入

标准 click 对 Monaco 编辑器无效(见上文兼容性矩阵);fill <ref> 是首选,press 逐键输入在所有构建上可用,type 在建立焦点后可用。

macOS 截图 "Permission denied"

npx @playwright/cli screenshot 报权限错误时,终端需要 Screen Recording 权限(System Settings → Privacy & Security → Screen Recording)。兜底方案:用 eval 校验片段确认文本已输入,不需要屏幕权限。

收尾清理

任务完成后务必杀掉调试 VS Code 实例——留着它不仅浪费资源,还会一直占用 CDP 端口:

# 断开 npx @playwright/cli
npx @playwright/cli close

# 杀掉调试 VS Code 实例
# macOS / Linux:
kill $(ps ax -ww -o pid,command | grep "$PWD/.vscode-ext-debug" | grep -v grep | awk '{print $1}' | head -1)

# Windows (PowerShell):
# Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -like "*$PWD\.vscode-ext-debug*" } | ForEach-Object { Stop-Process -Id $_.ProcessId }

小结

这套自动化方案的核心可以归纳为三句话:用 --extensionDevelopmentPath + --remote-debugging-port=9223 + 持久化 --user-data-dir 三件套启动可附加、可认证的 Insiders 实例;用 snapshot-ref 工作流加 fill/press 命令安全地驱动 Monaco 输入框;用时间戳截图目录保留每次运行的视觉证据链。完整可执行的命令与兼容性细节均以 extensions/copilot/.agents/skills/launch/SKILL.md 为准;相关仓库佐证材料包括 extensions/copilot/package.json(proposed API 列表、compile/watch 脚本、playwright devDependency)、extensions/copilot/.vscode/launch.json--extensionDevelopmentPath 调试配置)与 extensions/copilot/.gitignore.vscode-ext-debug/ 忽略规则)。需要留意的是,文中的 CSS 选择器与 API 计数均会随版本漂移,自动化失效时先重新 snapshot 再对照当前构建排查。

登录后查看全文
热门项目推荐
相关项目推荐