VS Code Copilot Chat 扩展自动化实战:基于 CDP 与 @playwright/cli 启动、交互与调试指南
本文围绕 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 窗口上:
- Build:先编译扩展;
- Launch:用扩展开发参数加远程调试端口启动 VS Code Insiders;
- Attach:
npx @playwright/cli附加到 CDP 端口; - Snapshot:快照发现可交互元素(每个元素带
ref引用); - Interact:用元素 ref 执行
fill/type/press等命令; - 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.json 的enabledApiProposals已列出 63 个 proposed API,engines.vscode要求为^1.137.0——proposed API 数量还在增长,这进一步说明 Insiders 是唯一可行的宿主。 - 扩展必须先编译:
npm run compile做一次性构建,npm run watch用于迭代开发。对应 package.json 中的脚本定义:compile为node .esbuild.mts --dev,watch为npm-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,不存在热重载。标准步骤:
- 重新编译扩展
- 杀掉使用本调试 user-data-dir 的 VS Code 实例
- 用相同参数重新启动
# 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
- macOS / Linux:
快照中看不到目标元素
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 再对照当前构建排查。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00