Playwright Dashboard 开发指南:从 `playwright cli show` 的前端 UI 到后端控制器的完整链路
本篇技术指南基于 Playwright 仓库的开发手册 .claude/skills/playwright-dev/dashboard.md,系统讲解 packages/dashboard 这个"Agent 监督面板"的前后端架构、关键源码路径,以及如何使用 Playwright CLI 本身来驱动、验证和录制这个 Dashboard。读完后,你将能够定位 Dashboard 的 UI、后端控制器与 CLI 入口三处关键代码,独立复现"启动 Dashboard 服务器 → 用 CLI 打开并观察 → 截图/录屏/标注"的完整开发验证流程。
一、Dashboard 是什么
packages/dashboard 目录包含了 playwright cli show 命令背后的全部源码。它是一个用于监督 Agent 使用 Playwright CLI 过程中的仪表盘(dashboard):当 Agent(或人)通过 Playwright CLI 操作浏览器时,Dashboard 以实时画面、会话/标签页列表、API 调用日志和调试器状态的形式,把这些操作可视化地呈现出来。
开发手册明确了三个必须熟悉的代码路径:
| 角色 | 位置 | 职责 |
|---|---|---|
| UI(前端) | packages/dashboard | React 界面,渲染会话侧边栏、实时画面、调试器面板等 |
| 后端控制器 | dashboardController.ts | 实现 WebSocket 传输层,处理标签页/会话/录屏/断点等所有请求 |
| CLI 入口 | program.ts 中的 show 分支 |
解析 playwright cli show 参数,拉起 Dashboard 守护进程 |
二、前端 UI:packages/dashboard 的结构
2.1 目录布局与打包配置
前端源码位于 packages/dashboard/src,入口是 index.tsx。它做了几件典型的事:
- 从 URL 查询参数取出 WebSocket 通道标识
ws,创建DashboardClient,再基于它构建DashboardModel(数据层); - 监听
document.visibilitychange,页面不可见时调用model.setVisible(false)——这会让后端停止推送截图帧,避免无谓的 screencast 开销; - 以
SplitView布局渲染界面:左侧 320px(最小 220px)的SessionSidebar(会话侧边栏),主区域为Dashboard组件。
vite.config.ts 揭示了 Dashboard 与仓库其他包的关系——它通过路径别名直接复用三大包的源码:
resolve: {
alias: {
'@isomorphic': path.resolve(__dirname, '../isomorphic'),
'@web': path.resolve(__dirname, '../web/src'),
'@trace-viewer': path.resolve(__dirname, '../trace-viewer/src'),
},
},
build: {
outDir: path.resolve(__dirname, '../playwright-core/lib/vite/dashboard'),
},
也就是说,Dashboard 构建产物会被输出到 playwright-core/lib/vite/dashboard,作为 playwright-core 的一部分随 npm 包分发;而 package.json 中仅依赖了 @browser-logos/* 一组浏览器图标资源(chrome/firefox/safari 等),用于在侧边栏渲染浏览器徽标。
2.2 主要视图组件
从 src 目录的文件命名可以完整还原界面的功能分区(从源码结构看):
- dashboard.tsx / dashboardModel.ts / dashboardClient.ts:主视图、状态模型与 WebSocket 客户端;
- sessionSidebar.tsx:左侧会话树(按 workspace 分组,每个 context 一行);
- screencast.tsx / recording.tsx:实时画面与录像回看;
- debuggerPanel.tsx:调试器面板(API 调用流 + 源码高亮);
- annotations.tsx / annotateView.tsx:用户标注模式,把标注以画框与文字的形式提交给调用方;
- settingsView.tsx:设置页。
三、CLI 入口:playwright cli show 的解析与守护进程拉起
show 命令的分支位于 program.ts(case 'show')。其核心逻辑:
- 组装守护进程参数:入口脚本为
libPath('entry', 'dashboardApp.js'),并追加--workspaceDir; - 只有当用户显式给出会话名(
-s/--session或环境变量PLAYWRIGHT_CLI_SESSION)时才传--sessionName。裸执行playwright cli show时不带该参数——源码注释解释:此时守护进程在就绪后应立即确认,而不是等待一个从未请求过的 reveal; --port、--host、--kill、--annotate按需透传;- 前台/后台的分水岭是
--port:只要传了--port,子进程以前台方式spawn(stdio: 'inherit'),进程随 CLI 一起阻塞;否则以detached方式后台启动,父进程轮询子进程 stdout,匹配到Dashboard is running pid=(\d+)信号后打印结果并返回(超时 60 秒)。
这一行为与后端 dashboardApp.ts 完全对应:options.port 有值时打印 Listening on <url> 并在前台持续服务 HTTP;无值时则通过 launchApp('dashboard') 弹出一个 1280x800 的 Chromium --app 窗口作为有窗口模式的守护进程,然后打印 Dashboard is running pid=... 并脱离父 CLI。
3.1 守护进程的单例机制
acquireSingleton 使用一个 Unix domain socket(Windows 上为命名管道语义)实现"同一时刻只有一个 Dashboard 守护进程":
- 新进程尝试
listen(socketPath),成功即成为"赢家",继续启动完整服务; - 失败(
EADDRINUSE/EEXIST)说明已有守护进程,新进程转为客户端,把本次的DashboardOptions(JSON + 换行)写入 socket;已有守护进程在 handleConnection 中收到后执行reveal(parsed)(切到目标会话/页面)、bringToFront(),并回传自己的 pid; --kill选项则走 runKillClient,向 socket 发送{ kill: true }关闭服务并退出。
这解释了为什么测试中反复 cli('show', ...) 不会互相干扰,以及 playwright cli show --kill 如何终止已有实例。
3.2 开发热更新(HMR)
dashboardApp.ts 中有一个构建期常量 __PW_HMR__:watch 构建时 startDashboardServer 会调用 attachDashboardDevServer,把 Vite dev server 挂到 HTTP 服务器前面,于是对 packages/dashboard/src/* 的修改可以热重载生效;release 构建中该分支被 esbuild 静态消除,走 attachDashboardStaticServer 直接静态托管构建产物。设置项通过 syncLocalStorageWithSettings 与 ~/.cache/ms-playwright 下的 .settings/dashboard.json 双向同步(saveSettings 回写、init script 注入)。
四、后端控制器:DashboardConnection 的请求处理
dashboardController.ts 是后端的核心。DashboardConnection 实现了 Transport 接口(dispatch(method, params) 按方法名分发到同名 handler),每次 WebSocket 连接对应一个实例。主要能力可以归纳为四组:
会话与标签页管理:selectTab / newTab / closeTab / closeSession 通过 SessionProvider(CLI 场景为 RegistrySessionProvider,即从 CLI 会话注册表中查找浏览器/上下文/页面)定位目标对象;_aggregateTabs 会为每个页面并发抓取标题、URL 和 favicon(faviconUrl 在页面内 fetch <link rel="icon">,并有 3 秒超时兜底),然后经 emitTabs 推送给 UI。
实时画面(screencast):AttachedPage 类负责"当前贴附的页面"。它在 init 时,若连接处于可见状态就启动 page.screencast.start,帧固定按 1280x800 缩放,每帧以 base64 经 emitFrame 推送;setVisible({ visible }) 切换可见性时会同步启停 screencast——这与前端 visibilitychange 监听形成闭环,实现"窗口隐藏即省资源"。
调试器集成(ContextDebugger):每个 BrowserContext 对应一个 ContextDebugger,订阅 context.debugger 的 apicallsupdated 与 pausedstatechanged 事件(见 第 446-449 行)。apicallsupdated 推送增量 ApiCallDelta(含 title、location、newLogEntries、actionPoint、status: running|success|error),控制器将其归并进内存表后推给 UI;_updateSource 会优先取"已暂停位置",其次取"正在运行的调用位置",读取对应源码文件(带 _sourceCache 缓存),根据扩展名推断语言(.py/.java/.cs,其余按 JavaScript),生成带行高亮的 DebuggerSource 发给前端。debuggerResume / debuggerPause / debuggerStep 三个方法直接桥接到 context.debugger 的 resume / requestPause / next。
标注(annotate)与录像:emitAnnotate({ signal }) 返回一个 Promise,向 UI 发送 annotate 事件进入标注模式,UI 端提交后通过 submitAnnotation 以 { type: 'submitted', frames, feedback } 兑现;同一连接上最新一次 annotate 会取代未完成的那次,AbortSignal 支持取消。录像方面,startRecording / stopRecording 把 screencast 输出重定向到 recording-<timestamp>.webm 文件,结束后以 streamId 打开文件句柄,前端通过 readStream 分块(每次 256KB、base64)回传录像内容供回放。
revealSession(sessionName, workspaceDir) 与 revealPage(pageId) 则是"守护进程已就绪、但目标页面稍后才出现"的补偿机制:请求先暂存为 _pendingReveal,每当 SessionsChanged / TabsChanged 事件到来时重试匹配(见 _tryRevealPending),匹配成功即切换贴附页面。
五、实战流程:用 Playwright CLI 观察 Dashboard
开发手册给出的标准验证流程是"用 Playwright CLI 自己来查看 Dashboard",全部命令如下(注意本仓库内用 npx playwright cli 调用,而非 playwright-cli):
# 后台启动 Dashboard 服务器(--port=0 让系统分配端口,前台阻塞)
npx playwright cli show --port=0
# 用 Playwright CLI 打开它
npx playwright cli open --session=dashboard localhost:PORT
npx playwright cli snapshot
# 截图查看 UI 细节
npx playwright cli screenshot
# 录屏展示你的工作
npx playwright cli video-start video.webm
# 为录像添加章节(更强大的叠加层能力见 CLI 技能文档中的视频相关说明)
npx playwright cli video-chapter "Chapter Title" --description="Details" --duration=2000
npx playwright cli video-stop
# 最后可用 ffmpeg 把 webm 转成 mp4 便于分享
这套流程里各命令与 Dashboard 的对应关系:show --port=0 触发第三节描述的"前台 HTTP 服务器"模式;open --session=dashboard localhost:PORT 则新建一个名为 dashboard 的 CLI 会话并打开该 URL——由于 Dashboard 后端以 workspace 为维度聚合所有 CLI 会话(RegistrySessionProvider),这个新会话本身也会出现在 Dashboard 的会话侧边栏里,从而形成"Dashboard 观察 CLI、CLI 操作 Dashboard"的自举式验证回路。screencast.tsx 对应的实时画面、debuggerPanel.tsx 对应的 API 调用流,都可以通过 snapshot 得到的可访问性快照断言,或用 screenshot 做视觉核对。
六、测试与技能文档佐证
- 端到端测试:tests/mcp/dashboard.spec.ts 提供了完整的自动化验证,例如:
should show browser session chip(cli open后侧边栏出现一个 Session 区域)、should show current workspace sessions first(当前 workspace 的会话排在前面,并用cli('show', '--kill')重启 Dashboard 验证另一个 workspace)、should activate session when show is called with -s(show带-s=sessB后激活的会话是sessB)。这些用例覆盖了前文讲到的会话聚合、workspace 分组与 reveal 机制。 - CLI 完整参考:开发手册指向 SKILL.md 作为完整 CLI 参考,其中与 Dashboard 直接相关的用法还包括
playwright-cli show --annotate——启动标注模式,用户在 Dashboard 上圈选页面区域并提交批注后,CLI 侧会拿到"带标注的截图 + 页面快照 + 文字意见",这是让 Agent 获取 UI 设计反馈的标准路径;video-start/video-chapter/video-stop等录屏命令也在该文档中有完整说明。
七、小结:改动落点速查
| 你想做的事 | 去哪里改 |
|---|---|
| 调整界面布局/样式/组件 | packages/dashboard/src(React + CSS,配合 HMR watch 构建热更新) |
| 修改与浏览器的交互协议、screencast/标注/调试器逻辑 | dashboardController.ts |
修改服务器启动、单例 socket、--port/--kill/--annotate 行为 |
dashboardApp.ts |
修改 playwright cli show 的参数解析与守护进程拉起 |
program.ts(case 'show') |
| 补充行为验证 | tests/mcp/dashboard.spec.ts |
以上信息全部以当前仓库代码为准:前端通过 Vite 打包进 playwright-core 发布包,后端以 DashboardConnection(每连接一实例)+ SessionProvider(每提供者决定可见会话来源)的抽象,使同一个 Dashboard 既能服务于 CLI 守护进程,也能通过 openDashboardForContext 嵌入到具体的 BrowserContext 场景。
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 StartedRust0624
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