虚拟机里的 Electron 应用点不动,改用 CDP 做双端验收
2023 字约 7 分钟
摘要在 Mac 上验收 Windows 虚拟机里的 Electron 应用,截图能用,鼠标点击和按键却落不进去。原来 Parallels 的按键码不是 Windows 的键码,最终改用应用自带的 CDP 调试端口操作界面,并把 Mac 和 Windows 的验收固化成一套本机 Skill。
给我参与的那个开源 Electron 应用做开发版验收,要在 Mac 和 Windows 两个平台上各跑一遍。Mac 就是我的电脑,Windows 在 Parallels 虚拟机里。
开始之前我对助手交代了两件事。一是 Parallels 里用 Computer Use 点击,我记得可能点不进去,最好用命令行去控制虚拟机。二是 macOS 弹出钥匙串密码框时,它是没办法输入的,遇到时应该停下来等我。
后来这两件事都应验了。
看得见,点不动
Windows 虚拟机里,应用启动后停在一个登录选择框上。助手用 prlctl capture 截图完全没问题,画面看得一清二楚。但是从 Mac 发过去的点击,对框里的按钮没有任何反应,发送 Tab 键也没有效果。
先排查的是键盘这一侧,查出了一个很具体的原因:prlctl send-key-event 用的是 Parallels 自己的键码表,不是 Windows 的虚拟键码。 它之前发的 9、13、18,在 Parallels 的表里分别是 Escape、数字 4 和数字 9,根本不是想发的按键。查官方键码表之后,Tab 是 23,Enter 是 36,Escape 是 9,空格是 65。
但改用正确的键码后,界面依然没有可见的焦点变化。助手猜测可能和窗口焦点有关,但这很难从外面确认,而这时已经有了更好的办法,它就没有再继续往下试。还有一个麻烦:prlctl exec 每次执行都会带出一个 Windows Terminal 窗口,遮住画面。助手有一次把它强行结束,结果连带 EduWork 也被关掉了,后来只能重新启动。
不点了,直接连进去
我问助手,有没有不需要点击就能操作虚拟机里这个应用的办法。
它的回答是有的。Electron 本质上是一个 Chromium,开发包支持 CDP,也就是 Chrome DevTools Protocol 的调试端口。给应用加上启动参数,让它监听虚拟机内部回环地址上的一个端口,一个外部程序就能连上这个正在运行的真实界面,按元素去点击、输入和读取内容,不依赖屏幕坐标和键盘焦点。
通俗比喻:别隔着玻璃比划,直接递一张字条之前的做法像是隔着玻璃窗对屋里的人打手势,能看见他,但他不一定看得懂手势,也不一定朝你这边看。
CDP 则是屋里专门留了一个递条子的小窗口。条子上写“点击标有‘保存’的按钮”,屋里的人照做,并把结果写在纸条上递回来。
具体怎么做:应用用 --remote-debugging-port=9333 --remote-debugging-address=127.0.0.1 启动。虚拟机里放了一个 Node 脚本,用发行包自带的 playwright-core 连上这个端口。Mac 这一侧通过 prlctl exec --current-user 在虚拟机里运行这个脚本,参数是 inspect、click、fill-chat、tab-click 之类的动作,脚本放在 Parallels 的共享目录里,两边都能访问。
这样之后,进度一下子顺了。需要我亲自处理的只有两种:网页登录和系统弹窗,比如单点登录,我登录完告诉它即可。助手随后用同一个办法完成了:
- 真实模型的一轮对话,拿一道很简单的合成问题试,返回了正确答案。
- 上传文本附件,模型从文件里读出了合成的日期和编号。
- 同时发两张图片,模型分辨出红色的 42 和蓝色的 73,原图预览也能打开。
- 内置的搜索工具,在调用轨迹里确认真正调用了对应的工具。
- 系统自带的语音合成,生成的 WAV 在应用里播放到了结尾,没有媒体错误。
- Studio 里生成一份简报文档,预览、下载都正常,下载文件和工作区里的最终文件哈希一致。
也有 CDP 够不着的地方,比如应用顶部原生的菜单。助手想点“应用 → 退出”,CDP 读不到、点不了原生菜单,模拟按键也没反应。最后是强制结束进程再重新启动,来检查数据是否恢复,并在记录里写明“使用了强制结束”,没有拿它冒充“正常退出通过”。
固化成一套流程
做完这一轮,我对助手说,Mac 加 Windows 的双端验收以后还会反复做,既然知道了可以用 CDP,是不是应该把流程固化下来,并且想办法缩短耗时,再做成 Skill。同时强调,这些东西是我们自己的,不应该提交到上游仓库。
它把流程拆成了三层:
- 先检查安装包。 核对压缩包的 SHA-256 和 CRC,再对照期望的文件清单。这一层不需要启动应用。事后试了一下,两个平台的压缩包里同一个媒体模板文件都不存在,这个问题在启动应用之前就能发现,而我们当时是等到界面里生成音频失败才发现的。
- 用同一套 CDP 脚本跑界面冒烟。 一次连接,依次检查版本、技能和插件、账号连接、历史会话、Studio 成果、内置浏览器。Mac 和 Windows 都用同一份脚本,实际跑出来,检查本身的耗时 Mac 约 2 秒,Windows 约 4 秒,不含启动、登录和模型生成。
- 真实模型、系统登录、UAC、钥匙串和通知,单独做专项验收。 这些不适合自动化,保留人工配合,并保留证据。
这套东西放在了本机的 Skill 目录里,不进上游仓库。
脚本也会过期
我很快想到一个问题:CDP 脚本依赖应用的界面结构,软件更新之后界面一改,脚本可能就失效了,到时候助手怎么记得去更新?
它同意这是个真问题,处理的办法是不靠记忆,把提醒写进流程和输出里:
- Skill 里加了一条规则,每拿到一个新的候选版本,先核对脚本覆盖的界面有没有变化。
- 脚本的输出回执里会带上产品版本和底层框架的版本,并和一份“已在两端真实验证过的版本记录”对比。对不上就标成“需要复核”,即使场景全部跑通,也不会被当成新版本已完成验收。
- 选择器失败时,先看真实界面,判断是产品坏了还是脚本过时,不能为了让测试变绿而直接放宽断言。
这条规则当场就用上了。重新跑一遍脚本,浏览器场景失败了,原因是虚拟机里已经开着两个标题一样的“Example Domain”标签页,脚本匹配到了旧的。助手把脚本改成只检查当前选中的新标签页,再重新验证,并且没有把这次失败记成产品故障。
适用边界这套办法依赖应用本身开放了 CDP 调试端口,发行版通常不会开放,只能用于开发包。验证用的是 Windows 11 ARM64 虚拟机和管理员环境,不能代替原生 x64 和普通用户的验收。
Mac 上的钥匙串
最后补一件 Mac 这边的事。每次换一个新的候选包,macOS 都会弹出钥匙串授权,打断验收。助手解释了原因:候选包是临时的 ad hoc 签名,每次重签,系统都当它是一个新的访问者;而且点“允许”只授权这一次。
我让它以后改用固定的本地签名身份。它在本机钥匙串里建了一个只用于测试的签名身份,私钥不进仓库,并把“固定签名、签完不再改包”写进了 Skill。不过它也坦率地说,首次请求仍然可能要点一次“始终允许”,能不能跨版本免提示,要等下一个完整的候选包实测,不能保证系统永远不再问。