ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

miniprogram-automator 在 2026 年还能用吗?——官方 SDK 三处硬伤的排查与重建

miniprogram-automator 在 2026 年还能用吗?——官方 SDK 三处硬伤的排查与重建 目录一、起因想做测试 Skill先修的是 SDK为什么选择 miniprogram-automator原本的技术路线环境信息二、launch() 必炸spawn EINVAL而错误信息在骗你1. 表面错误cliPath 明明正确2. 从错误堆栈找到 spawn EINVAL3. 为什么是 cli.bat4. 为什么不提 PR三、Page.* 整族死亡协议死了不是会话断了1. 实际表现2. 协议族对照3. 如何排除会话断开4. 利用 evaluate() 重建 Page 能力5. evaluate() 的两个硬约束6. 为什么 tap() 必须结合 WXML 静态分析四、残留会话0.4 秒的 launch 比超时更危险1. 一个看似正常的 launch()2. 用时间线定位残留会话3. 为什么端口还在但连接却会死4. 最终启动策略5. 一个反面案例不要用裸 TCP 探 WebSocket 服务端五、最终方案miniprogram-automator-next整体架构核心代码最终测试的样子实测结果27 项自检六、哪些结论已经确定哪些还只是推断已实测当前推断尚未验证七、当前方案的边界八、和其他方案的区别九、一个意外的收获直接 setData 驱动状态测试十、总结十一、项目地址十二、参考资料 / 延伸阅读本文记录了 2026 年 Windows 环境下官方miniprogram-automator在实际使用中遇到的兼容性问题排查与重建过程。起因是想做一个 Claude Code Skill让 AI 读懂微信小程序的 WXML/JS、自动生成自动化测试脚本结果动手第一步就撞上官方 SDK 的三处硬伤launch()在我当前的 Node v24.12.0 Windows 环境下稳定触发spawn EINVAL且把错因误报成 cliPath 不对Page.*命令族整族不响应排查中又被残留会话骗了两轮——DevTools 在 cli 子进程被 kill 后不关自动化端口导致launch()秒连上上一轮的残留会话几秒后被顶掉。最终把元素层能力整个重建在evaluate之上做成适配包miniprogram-automator-next在真实项目上跑通 27 项自检。本文区分了已实测与仍属推断的证据边界供同样在微信生态里做自动化测试的同学避坑。上回刚写了《微信云托管迁移后内容含违规信息全线排查》把后端链路的坑趟平了。这次换了个方向——想给小程序前端本身做自动化测试。结果没想到官方 SDK 先给我上了一课。一、起因想做测试 Skill先修的是 SDK为什么选择 miniprogram-automator起因是看到 Qwen-UI-Agent阿里通义的 GUI Agent 方向让模型看屏幕操作手机。我想的是更轻的路线不部署视觉模型实时看截图而是让 AI 读小程序的代码静态结构生成确定性的测试脚本。脚本为主、探索为辅。调研到官方有miniprogram-automator月下载约 4.8 万——地基现成不用自己造自动化层。原本的技术路线计划很简单写个 Claude Code Skill教 AI 读 WXML/JS → 生成 automator 脚本 → 在开发者工具里跑。然后就没有然后了。接下来两天全花在让官方 SDK 先能跑起来上。环境信息项值操作系统Windows 11Node.jsv24.12.0微信开发者工具2.01.2510290基础库3.17.0官方 SDKminiprogram-automator2023-11-07 后未更新被测项目真实小程序项目WXML TS二、launch() 必炸spawn EINVAL而错误信息在骗你1. 表面错误cliPath 明明正确照官方 README 写第一行代码constautomatorrequire(miniprogram-automator)awaitautomator.launch({cliPath,projectPath})得到Failed to launch wechat web devTools, please make sure cliPath is correctly specified于是我开始查cliPath查了很久。结果路径完全没问题。网上教程都写C:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat但安装位置是用户可改的我本机装在D:\微信web开发者工具\。正确的探测方式工具开着时最准Get-Processwechatdevtools|Select-ObjectPath换成绝对正确的路径还是同一句报错。这时候才意识到错误信息本身是误导——照它去查 cliPath 是死路。2. 从错误堆栈找到 spawn EINVAL打开错误堆栈才看见真相spawn EINVALerrno-4071。这个行为与 Node.js 在 Windows 上针对.bat/.cmd的 spawn 安全修复高度吻合涉及CVE-2024-27980BatBadBut。在我的 Node v24.12.0 环境中官方Launcher对cli.bat的直接 spawn 会稳定触发EINVAL。SDK 最后更新是 2023-11-07比这个 CVE 早。更坑的是它把这次失败catch住然后统一报成cliPath 不对。这是我损失时间最多的地方。3. 为什么是 cli.batcli.bat的全部内容其实就一行%~dp0.\node.exe %~dp0.\cli.js %*它只是node.execli.js的壳。也就是说绕开这个.bat壳直接 spawn 同目录的node.execli.js就行——不需要shell: true不重新打开 CVE 修掉的那个注入面functionresolveCliRunner(cliPath){constdirpath.dirname(cliPath)constnodeExepath.join(dir,process.platformwin32?node.exe:node)constcliJspath.join(dir,cli.js)// 同目录有 node.exe cli.js 就直接用它们绕开 .batif(fs.existsSync(nodeExe)fs.existsSync(cliJs)){return{mode:nodecli.js,cmd:nodeExe,prefixArgs:[cliJs]}}return{mode:raw,cmd:cliPath,prefixArgs:[]}}4. 为什么不提 PR想提 PR 修掉它提不了。官方 SDK 的repository字段指向腾讯内网域名git.code.oa.com没有公开仓库。既不能 fork 也不能 PR物理上没有对象可提。这就是后来做成独立适配包的原因。三、Page.* 整族死亡协议死了不是会话断了1. 实际表现launch 通了能拿到miniProgram。接着照文档写constpageawaitminiProgram.currentPage()constbtnawaitpage.$(.guest-login-btn)awaitbtn.tap()全部超时。2. 协议族对照一个个试下来边界非常干净协议族状态App.*evaluate/pageStack/reLaunch/mockWxMethod/screenshot…全活Tool.*活Page.*data/setData/callMethod/$/$$/ xpath /waitFor整族超时Element.*无法正常使用element handle 只能从Page.getElement拿而它也超时也就是说官方文档首页那句page.$(.btn).tap()在我这个版本的开发者工具上跑不通。3. 如何排除会话断开这一步很关键因为会话失效也会表现成全超时只跑一遍就下结论极容易误判。做法把Page.*调用和evaluate调用交错着跑并且互换先后顺序各跑一次。时间调用结果0sPage.data()超时46.9sevaluate()13ms 返回后续Page.*持续超时后续App.*正常逻辑很简单如果整个 WS 会话已经断开Page.*和evaluate()应该一起失败。但实际是Page.data()在第 0 秒就超时evaluate()在第 46.9 秒仍然 13ms 返回——会话要是断了后面的evaluate()不可能还活着。所以是协议死了不是连接断了。4. 利用 evaluate() 重建 Page 能力evaluate能在小程序运行时里执行任意代码那元素层能力就都能重做// page.data() 的替代直接从页面栈顶读awaitmp.evaluate((){constpagesgetCurrentPages()returnpages[pages.length-1].data})setData、query走wx.createSelectorQuery().selectAll(sel).fields({...})、waitForSelector都是同样的路子。在当前环境下这些基于evaluate()的替代实现本身开销很低data()约 9ms、setData()约 16ms、query()约 3455ms、tap()约 5ms。至少从当前测试数据看重建层没有带来明显的性能负担。5. evaluate() 的两个硬约束① 函数是序列化过去执行的闭包不生效。外部值必须当参数传constn42awaitmp.evaluate(()n1)// n is not definedawaitmp.evaluate((x)x1,n)// 43② 小程序逻辑层禁用eval/new Function。所以在运行时侧还原一个函数这条路是堵死的。凡是需要函数的得在 Node 侧构造好、把结果当数据传进去。这条约束直接决定了tap的实现形状event 对象在 Node 侧拼好整个当参数传进evaluate。反过来也带来一个意外好处——waitForData的 predicate 在 Node 侧跑所以它的闭包是正常可用的。6. 为什么 tap() 必须结合 WXML 静态分析Element.tap协议不可达所以点击实际是构造 event 对象直接调页面 handler。拆成三个要素看要素运行时拿得到吗元素存在性 / 位置 / 尺寸selectorQuery.fields({rect, size})元素的dataset/idfields({dataset: true, id: true})——包会自动读出来填进 event所以读e.currentTarget.dataset.xxx的 handler 能正常工作bindtapxxx这个绑定关系拿不到。fields()不给事件绑定它只存在于 WXML 源码里所以要点一个按钮必须有人去静态读 WXML 把 handler 名找出来。这一条把我原本的定位给坐实了AI 读 WXML 在这个方案里不是包装卖点是技术必要条件。我本来担心 AI 读代码生成脚本听着像给一个普通 SDK 套壳结果协议层的缺失反过来证明了这个环节不可省。四、残留会话0.4 秒的 launch 比超时更危险1. 一个看似正常的 launch()包写完demo 跑起来——每次都死在open(/pages/home/index)上Connection closed, check if wechat web devTools is still running而工具明明开着。2. 用时间线定位残留会话先怀疑是首页onLoad里的逻辑把运行时搞崩了换一个已知好的页面explain跑对照——也死。又怀疑工具真的挂了写脚本在死掉后立刻重连——连上了evaluate正常。问题不在业务调用在启动阶段。于是写了个诊断脚本verbose: true打开 cli 全部输出、每秒探活一次、且完全不做任何导航用来区分是空闲时自己掉线还是被我的调用搞掉的。时间线一出来真相就明显了[0.4s] launch 返回成功 ← 太快了 [4.0s] cli 打印出 ✔ auto [4.0s] 探活失败Connection closed ← 就在这一刻launch只花了 0.4 秒。而干净启动实测要 6.5–12.3 秒。3. 为什么端口还在但连接却会死去查端口Get-NetTCPConnection-LocalPort 9420# 由 wechatdevtools 进程 listen而此时并没有 cli 子进程根因DevTools 在 cli 子进程被 kill 之后不会立刻关掉自动化端口。官方launch()一起来就轮询connect于是瞬间连上上一轮的残留会话并宣布启动成功几秒后新会话把它顶掉你就在随后随便哪个调用上收到Connection closed。所以报错落在哪个 API 上纯属随机——它伪装成了open()这个方法有毒。在我的测试环境中亚秒级 launch 成功是非常强的残留会话信号。正常冷启动实测为 6.512.3 秒因此 0.x 秒级的成功反而值得警惕。4. 最终启动策略等 cli 输出里那行单独的auto再开始 connect。 别硬匹配✔ auto那个对勾不同终端编码下字节不稳剥掉行首非字母数字字符再比 auto。✔ auto只是必要条件不是充分条件——实测它打出来之后端口还可能没起来遇到过打完标记然后 120s 连接全超时所以标记只用来拦住抢跑端口就绪仍靠轮询判断。连上后evaluate探活 → 等 1.5s →再探一次。残留会话就是在第二次探活被筛掉的。5. 一个反面案例不要用裸 TCP 探 WebSocket 服务端我中间写过一个portInUse()用裸 TCP 探端口占用——net.connect捅一下再立刻destroy()。在我的一次实测中它确实导致 DevTools 后续无法正常建立自动化连接✔ auto打了但端口始终不 listen硬等 120s 超时。对一个 WebSocket 服务端做裸 TCP 连接再立刻断开本来就不是个礼貌的动作。而且探不探端口处理办法完全一样都是等待信号 复探所以我把这个函数删掉了无条件走同一条路。少一个主动干扰服务端的动作也少一个风险点。五、最终方案miniprogram-automator-next整体架构把前面三处修完适配包的整体形态是这样的读 WXML / JSspawn node.exe cli.js绕开 .batevaluate 重建元素层你: 一句话需求Claude Code skill生成 *.test.jsminiprogram-automator-nextDevTools CLIautomation WS 会话模拟器里的小程序分工是skill 负责读代码 → 写脚本修复包负责让脚本真的能跑起来。两者可以单独用——修复包是普通 npm 包不装 skill 也能手写脚本调用。核心代码最终测试的样子constassertrequire(assert)const{launch}require(miniprogram-automator-next);(async(){letmptry{// 冷启动 6~13s 是正常的。秒启动反而是坏事——说明连上了残留会话mpawaitlaunch({projectPath:D:\\workspace\\coach-miniapp,port:9420})constpageawaitmp.open(/pages/home/index,{settle:2000})assert.strictEqual((awaitpage.route()).route,pages/home/index)// 断言 1empty 态下登录按钮被 wx:if 藏着不该渲染awaitpage.setData({phase:empty})awaitpage.wait(300)assert.strictEqual(awaitpage.count(.guest-login-btn),0)// 断言 2直接把页面摆到 guest 态——不必为了测一个按钮走完整前置流程awaitpage.setData({phase:guest})awaitpage.waitForSelector(.guest-login-btn,{timeout:3000})constbtnawaitpage.query(.guest-login-btn)assert.ok(btn.width0btn.height0)// 断言 3loginAndLoad 来自 WXML 的 bindtap运行时读不到只能静态抄constrawaitpage.tap(.guest-login-btn,loginAndLoad)assert.strictEqual(r.handler,loginAndLoad)console.log(✓ 全部通过)}finally{if(mp)awaitmp.teardown()// 必须收否则 cli 子进程残留下一轮又撞残留端口}})()实测结果27 项自检指标实测值自检通过27 / 27 全通过冷启动launch耗时12.3s正常范围 6.5–12.3spage.data()替代evaluate约 9mspage.setData()替代约 16mspage.query()替代约 3455mspage.tap()替代约 5ms截图存盘73864 bytes覆盖能力open/ 自动读 dataset 的tap/waitForData闭包 / 截图六、哪些结论已经确定哪些还只是推断写文档时我一度把适用范围写成 DevTools 2.01.2510290 / 基础库 3.17.0 / Node v24.12.0 / Windows 11暗示这四个变量都是嫌疑人。用户看完提了一句这个基础库好像基本没啥问题。一句话点醒——回头看自己的数据evaluate()可以在当前小程序运行环境中稳定执行 JS多次调用均能在毫秒级返回。这至少说明当前运行时以及 evaluate() 所依赖的通信链路是正常工作的因此没有证据表明基础库运行时本身是主要故障点。死掉的是按协议域切分的Page.*而协议域更接近开发者工具那一侧的实现边界。所以这里必须把实测和推断分开写不然读者会去换一个没用的变量重测。已实测当前环境下Node v24.12.0 Windows官方Launcher直接 spawncli.bat触发spawn EINVALerrno-4071当前环境下App.*正常、Tool.*正常当前环境下Page.*持续超时依赖Page.getElement获取 handle 的Element.*因而无法正常使用evaluate()可以正常执行Page.*与evaluate()交错执行后可以排除整个会话已经断开残留 DevTools 会话可能造成亚秒级 launch 假成功实测 0.4s正常冷启动耗时约 6.512.3 秒当前适配方案可以通过evaluate()重建部分 Page / Element 能力27 项自检全通过。当前推断基础库 3.17.0 不是主要故障点高可信推断evaluate在基础库运行时里跑得好好的死的是按协议域切分的Page.*那是工具侧的边界。但这个结论是间接证据推出来的不是对照实验更值得优先怀疑的是 SDK 与开发者工具之间的兼容性SDK 2023-11 后没更新工具一直在走形状更像版本错配而不是基础库运行时本身但由于目前没有进行不同 DevTools 版本的 A/B 对照因此这仍然属于推断。尚未验证Page.* 是 DevTools 版本兼容问题——尚无版本 A/B 测试待验证更换 DevTools 版本可以恢复 Page.*——未测试未知macOS / 其他 Node 版本上的表现——未测试。一张表收拢这也是全文最重要的证据边界结论当前证据状态cli.bat在当前环境触发spawn EINVAL实际复现已确认Page.*在当前 DevTools 环境不可用多 API 交错测试已确认App.*/evaluate()正常多次测试已确认残留会话导致秒启动启动时间线 二次探活已确认基础库 3.17.0 不是主要故障点间接证据高可信推断Page.*是 DevTools 版本兼容问题尚无版本 A/B待验证更换 DevTools 版本可以恢复Page.*未测试未知如果后续要验证Page.*是否会恢复优先应该进行 DevTools 版本 A/B 测试而不是单纯更换基础库版本。当前证据更支持前者但还不能把它当作已验证结论。七、当前方案的边界自定义组件是硬边界。页面级selectorQuery不跨组件边界tap也只调页面方法。组件内部的元素查不到定义在组件里的 handler 会提示页面对象上不存在。这是当前方案的已知边界没解决。只能本机跑。强依赖本地开发者工具 CLI不支持 Linux / CI / 云端。这是官方 SDK 的限制不是适配包能绕开的。selector 不是完整 CSS。createSelectorQuery只支持 id / class / 标签 /::before::after及其并集与后代组合。没有nth-child没有属性选择器。结论绑定版本。上面所有实测数据都绑在 DevTools 2.01.2510290 / 基础库 3.17.0 / Node v24.12.0 / Windows 11 这一套环境上换环境请重测别直接外推。修复包刚发首版0.1.0。只在一个真实项目上验证过27 项自检欢迎报 issue。八、和其他方案的区别这个方向不是空白市场我也没打算装作是。客观说下几条路线项目思路和本项目的关系miniprogram-automator微信官方 SDK本项目的地基不是竞品修复包以它为 peerDependencyweapp-vite/miniprogram-automator走 headless 模拟器绕过了EINVAL而不是修它路线不同miniprogram-automator-mcp/creatoria/miniapp-mcp/purea/wechat-devtools-mcp等包成 MCP server 给 AI 用形态不同MCP tool vs Claude Code skill本项目的差异在于针对当前环境下Page.*不可用的问题做了一层基于evaluate()的适配而不是继续包装已失效的 API如果只是想让 AI 能操作小程序那几个 MCP 可能更顺手。本项目更适合想要确定性、可提交进仓库、可重复跑的测试脚本并且恰好撞上了这两个坑的人。九、一个意外的收获直接 setData 驱动状态测试排障之外这套基于evaluate()的方案还带来一个意外的能力直接setData把页面摆到想测的状态。// 测登录按钮的 guest 态一句话把页面摆到位而不是先走一遍登录流程awaitpage.setData({phase:guest})awaitpage.waitForSelector(.guest-login-btn,{timeout:3000})传统测试要测一个按钮得先走完整前置流程启动页面 → 等登录状态 → 请求接口 → 等数据 → 进入 guest → 点按钮。当前方案直接定位到某个 UI 状态// 断言 1empty 态下登录按钮被 wx:if 藏着不该渲染awaitpage.setData({phase:empty})awaitpage.wait(300)assert.strictEqual(awaitpage.count(.guest-login-btn),0)// 断言 2直接把页面摆到 guest 态——不必为了测一个按钮走完整前置流程awaitpage.setData({phase:guest})awaitpage.waitForSelector(.guest-login-btn,{timeout:3000})constbtnawaitpage.query(.guest-login-btn)assert.ok(btn.width0btn.height0)这不是单纯为了绕过前置流程而是让测试可以直接定位到某个 UI 状态进行验证。empty 态验证按钮被wx:if藏住不渲染guest 态验证按钮渲染且可点击——两个分支都没走真实登录流程。这是通过evaluate换来的额外好处原生协议反而做不到这么随意。十、总结回答开头的三个问题1. miniprogram-automator 在 2026 年还能不能用能用但带条件。在我当前的 Node v24.12.0 Windows 环境下launch()会稳定触发spawn EINVAL且误报成 cliPath 不对官方Page.*/Element.*元素层无法正常工作活着的是App.*/Tool.*和evaluate()。结论绑定环境换环境请重测。2. 为什么我要重建 Page / Element 层因为官方元素层协议在当前环境不可用而测试恰恰最需要元素级能力查元素、点按钮、断言状态。evaluate()是当前环境可用的通用通道重建在它之上才能拿到这些能力——从当前测试数据看重建层开销很低data()约 9ms、tap()约 5ms。3. 为什么 WXML 静态分析最终变成了这个项目不可缺少的一部分因为事件绑定关系bindtapxxx运行时读不到只存在于 WXML 源码里而Element.tap协议不可达点击只能构造 event 直接调页面 handlerhandler 名必须从 WXML 静态找出来。这不是包装卖点是技术必要条件。这次排查最后没有停留在官方 SDK 不能用了这个结论上。因为真正需要的是AI 读取 WXML → 识别页面元素和事件 → 生成确定性测试步骤 → 通过 automator-next 执行 → 得到测试结果因此我把这套适配层和 Claude Code Skill 一起开源成了miniprogram-auto-test。如果你使用的是其他版本的微信开发者工具尤其是不同版本的 DevTools欢迎测试Page.*是否仍然可用——最好按照本文第三节的交错验证法测只跑一遍很容易把残留会话误判成协议死亡。十一、项目地址仓库https://github.com/Zi-Yi-Ming/miniprogram-auto-testnpm 适配包https://www.npmjs.com/package/miniprogram-automator-nextLicenseMIT十二、参考资料 / 延伸阅读CVE-2024-27980BatBadButhttps://nvd.nist.gov/vuln/detail/CVE-2024-27980官方 SDKminiprogram-automatorhttps://www.npmjs.com/package/miniprogram-automator微信开发者工具 CLI / 自动化接口官方文档https://developers.weixin.qq.com/miniprogram/dev/devtools/cli.html
返回列表