miniprogram-automation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese微信小程序自动化
WeChat Mini-Program Automation
概述
Overview
使用 驱动微信开发者工具,完成页面跳转、元素查询、交互、Mock、运行时监听、截图和回归验证。
miniprogram-automator这个 skill 默认偏向实战参考型输出:
- 优先给出可直接运行的独立 Node.js 脚本模板
- 用户明确要求接入测试框架时,再输出 Jest 版
- 先确认方案和前置条件,再落地到脚本文件
Use to drive the WeChat DevTools to complete page navigation, element querying, interaction, Mocking, runtime monitoring, screenshots, and regression verification.
miniprogram-automatorThis skill defaults to a practical reference-style output:
- Prioritize providing directly runnable standalone Node.js script templates
- Output Jest versions only when users explicitly request integration with a testing framework
- Confirm the solution and prerequisites first, then proceed to script implementation
默认输出结构
Default Output Structure
被触发时,优先按这个结构回答:
- 先说明你准备怎么做:会生成哪类脚本、依赖哪些输入
- 列出前置检查项:项目目录、CLI 路径、安全设置、页面路径、选择器
- 给完整脚本:优先独立脚本,必要时补 Jest 版
- 给运行命令:安装依赖、执行命令、可选参数
- 给验证点和注意事项:等待策略、Mock 恢复、关闭连接、截图限制
如果用户已经给全了路径、页面、选择器和目标行为,就不要反复盘问,直接产出脚本。
When triggered, answer following this structure first:
- Explain the plan first: What type of script will be generated, which inputs are required
- List pre-check items: Project directory, CLI path, security settings, page path, selectors
- Provide complete scripts: Prioritize standalone scripts, supplement with Jest version if necessary
- Provide run commands: Install dependencies, execution commands, optional parameters
- Provide verification points and notes: Waiting strategies, Mock restoration, connection closure, screenshot limitations
If the user has provided complete path, page, selector, and target behavior, do not repeatedly ask questions and directly deliver the script.
Step 1:先收集关键输入
Step 1: Collect Key Inputs First
如果用户没提供完整信息,先补齐这些字段:
If the user hasn't provided complete information, first fill in these fields:
1.1 可被开发者工具打开的项目目录
1.1 Project directory openable by DevTools
automator.launch({ projectPath })projectPath不要机械地把它理解成“源码仓库根目录”:
- 原生小程序:通常是包含 /
project.config.json的目录app.json - Taro / uni-app / 自定义构建链:通常是开发者工具真正打开的编译产物目录,例如 、
dist/、build/miniprogram/
拿不准时,先问用户:
- “你平时在微信开发者工具里打开的是源码根目录,还是编译后的 dist/build 目录?”
The in should be the actual directory opened by DevTools.
projectPathautomator.launch({ projectPath })Do not mechanically interpret it as the "source code repository root directory":
- Native mini-programs: Usually the directory containing /
project.config.jsonapp.json - Taro / uni-app / custom build chains: Usually the compiled output directory actually opened by DevTools, such as ,
dist/,build/miniprogram/
If unsure, ask the user first:
- "Do you usually open the source code root directory or the compiled dist/build directory in WeChat DevTools?"
1.2 微信开发者工具 CLI 路径
1.2 WeChat DevTools CLI Path
常见默认路径:
- macOS:
/Applications/wechatwebdevtools.app/Contents/MacOS/cli - Windows:
C:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat
如果开发者工具安装在标准位置, 可以省略,SDK 会按默认路径自动查找。
cliPath如果用户环境不标准,先让用户确认实际路径。
Common default paths:
- macOS:
/Applications/wechatwebdevtools.app/Contents/MacOS/cli - Windows:
C:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat
If DevTools is installed in a standard location, can be omitted, and the SDK will automatically find it by the default path.
cliPathIf the user's environment is non-standard, ask the user to confirm the actual path first.
1.3 miniprogram-automator 版本
1.3 miniprogram-automator Version
必须使用 及以上版本,否则会触发 CLI 命令格式兼容问题。
0.12.0旧版( 等)使用已废弃的 语法,当前微信开发者工具(3.x)已不支持,表现为:
0.5.xcli --auto <path>[error] { code: 31, message: "Error: Missing param 'project / appid'" }升级方法:
bash
npm install miniprogram-automator@latest --save-dev新版()使用正确的 语法。
0.12.xcli auto --project <path> --auto-port <port>Must use version or above, otherwise CLI command format compatibility issues will be triggered.
0.12.0Older versions (such as ) use the deprecated syntax, which is no longer supported by the current WeChat DevTools (3.x), manifesting as:
0.5.xcli --auto <path>[error] { code: 31, message: "Error: Missing param 'project / appid'" }Upgrade method:
bash
npm install miniprogram-automator@latest --save-devNew versions () use the correct syntax.
0.12.xcli auto --project <path> --auto-port <port>1.4 安全设置
1.4 Security Settings
使用 前,必须提醒用户检查:
automator.launch()- 微信开发者工具 → 设置 → 安全设置 → 开启 服务端口
没有这一步,脚本常见表现是:
- launch 失败
- 连接超时
- CLI 可执行但 WebSocket 建不起来
Before using , must remind the user to check:
automator.launch()- WeChat DevTools → Settings → Security Settings → Enable Service Port
Without this step, common script behaviors include:
- Launch failure
- Connection timeout
- CLI executable but WebSocket cannot be established
1.5 目标页面和断言目标
1.5 Target Page and Assertion Targets
至少确认:
- 页面路径,如
/pages/home/index - 是否要 /
reLaunch/navigateToswitchTab - 要验证什么:文本、类名、数据、截图、日志、异常、Mock 请求结果
- 关键选择器是否稳定
At least confirm:
- Page path, such as
/pages/home/index - Whether to use /
reLaunch/navigateToswitchTab - What to verify: Text, class names, data, screenshots, logs, exceptions, Mock request results
- Whether key selectors are stable
1.6 输出形态
Step 2: Choose the Correct Workflow
—
2.1 launch
or connect
launchconnect默认先给独立脚本。只有在这些场景下优先 Jest:
- 用户明确提到 Jest / 单测 / 回归套件 / CI
- 任务需要 、多 case 组织、批量断言
beforeAll/afterAll
Three workflows, choose according to scenarios:
| Scenario | Recommended Method |
|---|---|
| Fully automated: Script starts DevTools itself | |
| DevTools is already running, connect directly | CLI v2 + |
| DevTools is running and automation port is ready | Direct |
Method 1: (Fully Automated Launch)
automator.launch()The SDK internally calls the CLI to start DevTools, no manual operation required. Two hard prerequisites:
- DevTools must be completely closed (Cmd+Q), otherwise it will report — launch cannot coexist with running instances.
Failed to launch wechat web devTools - "Trust Project" confirmation box pops up for the first run, requiring manual click by the user, so is recommended to be
timeout(2 minutes).120000
js
miniProgram = await automator.launch({
cliPath: CLI_PATH,
projectPath: PROJECT_PATH,
timeout: 120000,
})Method 2: CLI v2 + (DevTools Already Running)
automator.connect()When DevTools has already opened the project (and you don't want to close and restart it), use the CLI v2 command to enable the automation WebSocket port, then connect with .
connect()actually still uses the old CLI format (automator.launch()) internally, which may fail on some new versions of DevTools. Ifcli --auto <path>reports an error, switching to this method is usually more stable.launch()
js
const { spawnSync } = require('node:child_process')
const CLI_PATH = process.env.WECHAT_DEVTOOLS_CLI ||
'/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
const PROJECT_PATH = process.env.MINIPROGRAM_PROJECT_PATH || '/path/to/project'
const AUTO_PORT = Number(process.env.WECHAT_AUTO_PORT || 9420)
// Step 1: Enable automation ws port with CLI v2
// If the DevTools HTTP service port is not the default value, pass it via --port
function enableAutomation(httpPort) {
const args = ['auto', '--project', PROJECT_PATH, '--auto-port', String(AUTO_PORT)]
if (httpPort) args.push('--port', String(httpPort))
const result = spawnSync(CLI_PATH, args, { encoding: 'utf8', timeout: 20000 })
return { success: result.status === 0, output: (result.stdout || '') + (result.stderr || '') }
}
const { success, output } = enableAutomation()
if (!success) throw new Error(`CLI auto command failed:\n${output}`)
// Step 2: Connect to the automation ws endpoint
const miniProgram = await automator.connect({
wsEndpoint: `ws://127.0.0.1:${AUTO_PORT}`,
})Port Conflict Issue: If the DevTools HTTP service port is not the default value (e.g., due to multiple instances), adding to the CLI v2 command will cause a conflict error. To detect the actual port: call the CLI once with a placeholder port, then parse the actual port from the output. See Template E for a complete example.
--portMethod 3: Direct (Port Already Ready)
automator.connect()DevTools has manually enabled the automation port (via tool menu or previous CLI v2 call), connect directly:
js
const miniProgram = await automator.connect({
wsEndpoint: 'ws://localhost:9420',
})Note: obtained via only disconnects the WebSocket connection and does not close DevTools.
miniProgram.close()connect()Step 2:选择正确的工作流
2.2 Standalone Script or Jest
2.1 launch
还是 connect
launchconnect—
三种工作流,按场景选择:
| 场景 | 推荐方式 |
|---|---|
| 全自动:脚本自己启动开发者工具 | |
| 开发者工具已开着,直接连 | CLI v2 + |
| 开发者工具已开着且自动化端口已就绪 | 直接 |
方式一:(全自动启动)
automator.launch()SDK 内部调用 CLI 启动开发者工具,无需手动操作。两个硬性前提:
- 开发者工具必须完全退出(Cmd+Q),否则报 ——launch 不能与已运行实例共存。
Failed to launch wechat web devTools - 首次运行弹出「信任项目」确认框,需用户手动点击,因此 建议
timeout(2 分钟)。120000
js
miniProgram = await automator.launch({
cliPath: CLI_PATH,
projectPath: PROJECT_PATH,
timeout: 120000,
})方式二:CLI v2 + (开发者工具已在运行)
automator.connect()当开发者工具已打开项目(不想关掉重开),用 CLI v2 命令开启自动化 WebSocket 端口,再用 连入。
connect()内部其实仍在使用旧版 CLI 格式(automator.launch()),在某些新版开发者工具上可能失败。如果遇到cli --auto <path>报错,改用这个方式通常更稳。launch()
js
const { spawnSync } = require('node:child_process')
const CLI_PATH = process.env.WECHAT_DEVTOOLS_CLI ||
'/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
const PROJECT_PATH = process.env.MINIPROGRAM_PROJECT_PATH || '/path/to/project'
const AUTO_PORT = Number(process.env.WECHAT_AUTO_PORT || 9420)
// Step 1:用 CLI v2 开启自动化 ws 端口
// 若开发者工具 HTTP 服务端口不是默认值,需通过 --port 传入
function enableAutomation(httpPort) {
const args = ['auto', '--project', PROJECT_PATH, '--auto-port', String(AUTO_PORT)]
if (httpPort) args.push('--port', String(httpPort))
const result = spawnSync(CLI_PATH, args, { encoding: 'utf8', timeout: 20000 })
return { success: result.status === 0, output: (result.stdout || '') + (result.stderr || '') }
}
const { success, output } = enableAutomation()
if (!success) throw new Error(`CLI auto 命令失败:\n${output}`)
// Step 2:connect 到自动化 ws 端点
const miniProgram = await automator.connect({
wsEndpoint: `ws://127.0.0.1:${AUTO_PORT}`,
})端口冲突问题:如果开发者工具 HTTP 服务端口不是默认值(例如因为多实例),CLI v2 命令加 时会冲突报错。探测实际端口的方法:先用一个占位端口调用一次 CLI,从输出里解析实际端口。完整示例见模板 E。
--port方式三:直接 (端口已就绪)
automator.connect()开发者工具已手动开启自动化端口(工具菜单或之前已调用 CLI v2),直接连:
js
const miniProgram = await automator.connect({
wsEndpoint: 'ws://localhost:9420',
})注意: 拿到的 的 只断开 WebSocket 连接,不会关闭开发者工具。
connect()miniProgramclose()| Scenario | Recommendation |
|---|---|
| Temporary verification, single-page debugging, script tooling | Standalone Node.js script |
| Regression testing, multiple cases, team collaboration, CI | Jest |
2.2 独立脚本还是 Jest
2.3 How to Choose Waiting Strategy
| 场景 | 建议 |
|---|---|
| 临时验证、单页面调试、做脚本工具 | 独立 Node.js 脚本 |
| 回归测试、多个 case、团队协作、CI | Jest |
Priority is as follows:
await page.waitFor('stable selector')await page.waitFor(async () => condition is met)- as a fallback
await page.waitFor(number of milliseconds)
Do not rely entirely on fixed from the start. Fixed waiting can only be used as a fallback, not the main synchronization method.
sleep(2000)2.3 等待策略怎么选
Step 3: Core Rules
—
3.1 Prioritize waitFor
for Page Waiting
waitFor优先级如下:
await page.waitFor('稳定选择器')await page.waitFor(async () => 条件成立)- 作为兜底
await page.waitFor(数字毫秒)
不要一上来就全靠固定 。固定等待只能兜底,不能当主同步手段。
sleep(2000)page.waitFor()js
await page.waitFor('.page-title')
await page.waitFor(async () => (await page.$$('.loaded-item')).length > 0)
await page.waitFor(500)Recommended writing: Use selector or real condition assertion waiting first, then add a short fallback waiting. Do not write condition functions that always return .
trueStep 3:核心规则
3.2 Page-level Selectors Cannot Penetrate Custom Components
3.1 页面等待优先用 waitFor
waitFor—
page.waitFor()js
await page.waitFor('.page-title')
await page.waitFor(async () => (await page.$$('.loaded-item')).length > 0)
await page.waitFor(500)推荐写法:先用选择器或真正的条件断言等待,再补一个很短的兜底等待。不要写永远返回 的条件函数。
trueThis is the most common mistake.
Wrong Approach:
js
const input = await page.$('form-panel input')Correct Approach:
js
const panel = await page.$('form-panel')
const input = await panel.$('input')Reason: The query scope of / is the page root node, and cannot directly select elements inside custom components; even using descendant selectors like won't work. To select elements inside a component, first get the component host element, then use / to continue querying within the component scope.
page.$page.$$form-panel inputelement.$element.$$3.2 页面级选择器不能穿透自定义组件
3.3 Prioritize value()
or property('value')
When Reading Input Values
value()property('value')这是最容易答错的地方。
错误思路:
js
const input = await page.$('form-panel input')正确思路:
js
const panel = await page.$('form-panel')
const input = await panel.$('input')原因: / 的查询作用域是页面根节点,无法直接选取自定义组件内部的元素;即使用 这种后代选择器也不行。要选组件内部元素,先拿到组件宿主元素,再用 / 在组件作用域内继续查。
page.$page.$$form-panel inputelement.$element.$$If the target is a native / :
inputtextareajs
const value = await input.value()
// or
const value2 = await input.property('value')If the target is a custom component instance, then consider:
js
const data = await panel.data()Do not confuse "reading native input values" with "reading component internal data". is more suitable for component instances, not a general method for reading input boxes.
data()3.3 读输入值时,优先 value()
或 property('value')
value()property('value')3.4 Prioritize Official mockWxMethod
for Mocking WeChat APIs
mockWxMethod如果目标是原生 / :
inputtextareajs
const value = await input.value()
// 或
const value2 = await input.property('value')如果目标是自定义组件实例,再考虑:
js
const data = await panel.data()不要把“读原生输入框值”和“读组件内部 data”混为一谈。 更适合组件实例,不是通用的输入框读取方法。
data()If the task is to mock , , etc., prioritize using:
wx.requestwx.getLocationjs
await miniProgram.mockWxMethod('request', (options = {}) => {
const res = {
data: { code: 0, list: [{ id: 1, title: 'Mock Item A' }] },
statusCode: 200,
header: { 'content-type': 'application/json' },
cookies: [],
errMsg: 'request:ok',
}
Promise.resolve().then(() => {
if (typeof options.success === 'function') options.success(res)
if (typeof options.complete === 'function') options.complete(res)
})
return {
abort() {},
onHeadersReceived() {},
offHeadersReceived() {},
onChunkReceived() {},
offChunkReceived() {},
}
})Restore when finished:
js
await miniProgram.restoreWxMethod('request')Additional Notes:
- also supports directly passing a fixed result object; no need to always write functions for simple scenarios
mockWxMethod - If a function is passed, the function body will be serialized and executed, do not rely on closure references to external variables
- If you need to call the original wx method, you can use inside the function
this.origin
Only consider when the user explicitly needs deeper runtime injection, or when cannot cover the target scenario.
evaluate()mockWxMethod3.4 Mock 微信 API 时优先官方 mockWxMethod
mockWxMethod3.5 Use miniProgram.screenshot()
for Screenshots, and Only for DevTools Simulator
miniProgram.screenshot()如果任务是 mock 、 等,优先用:
wx.requestwx.getLocationjs
await miniProgram.mockWxMethod('request', (options = {}) => {
const res = {
data: { code: 0, list: [{ id: 1, title: 'Mock Item A' }] },
statusCode: 200,
header: { 'content-type': 'application/json' },
cookies: [],
errMsg: 'request:ok',
}
Promise.resolve().then(() => {
if (typeof options.success === 'function') options.success(res)
if (typeof options.complete === 'function') options.complete(res)
})
return {
abort() {},
onHeadersReceived() {},
offHeadersReceived() {},
onChunkReceived() {},
offChunkReceived() {},
}
})结束时恢复:
js
await miniProgram.restoreWxMethod('request')补充说明:
- 也支持直接传固定结果对象;简单场景不必总写函数
mockWxMethod - 如果传入函数,函数体会被序列化执行,不要依赖闭包引用外部变量
- 如果需要调用原始 wx 方法,可在函数内部使用
this.origin
只有当用户明确需要做更深层运行时注入,或者 无法覆盖目标场景时,再考虑 。
mockWxMethodevaluate()js
await miniProgram.screenshot({ path: '/abs/path/to/file.png' })Additional Notes:
- When passing , the screenshot will be saved to a file
{ path } - When no parameters are passed, it returns the base64 encoding of the image data, suitable for in-memory comparison or upload
Must remind:
- Screenshots are only applicable to the DevTools Simulator
- Not applicable to real machine debugging screens
- Ensure the page has rendered stably before taking screenshots
3.5 截图使用 miniProgram.screenshot()
,且仅限开发者工具模拟器
miniProgram.screenshot()3.6 Cleanup Actions Must Be Put in finally
finallyjs
await miniProgram.screenshot({ path: '/abs/path/to/file.png' })补充说明:
- 传 时会把截图保存到文件
{ path } - 不传参数时会返回图片数据的 base64 编码,适合做内存中的比对或上传
必须提醒:
- 截图只适用于开发者工具模拟器
- 不适用于真机调试画面
- 截图前要确保页面已经稳定渲染
Whether it's a standalone script or Jest, ensure:
- Restore mocked wx methods
- Unbind event listeners (if used)
- Close the mini-program instance
js
try {
// Execute test logic
} finally {
await miniProgram.restoreWxMethod('request').catch(() => {})
await miniProgram.close().catch(() => {})
}3.6 清理动作必须放进 finally
finallyTemplate A: General Skeleton for Standalone Scripts
无论是独立脚本还是 Jest,都要确保:
- 恢复被 mock 的 wx 方法
- 解绑事件监听(如果用了)
- 关闭小程序实例
js
try {
// 执行测试逻辑
} finally {
await miniProgram.restoreWxMethod('request').catch(() => {})
await miniProgram.close().catch(() => {})
}When the user says "give me a script", "don't change the testing framework yet", "want to run it quickly", start with this template first.
js
const automator = require('miniprogram-automator')
const path = require('node:path')
const fs = require('node:fs/promises')
const CLI_PATH = process.env.WECHAT_DEVTOOLS_CLI || '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
const PROJECT_PATH = process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/devtools-project'
const TARGET_PAGE = '/pages/home/index' // Must start with /
const OUTPUT_DIR = path.resolve(process.cwd(), 'outputs')
async function main() {
let miniProgram
try {
await fs.mkdir(OUTPUT_DIR, { recursive: true })
// Prerequisite: DevTools is completely closed (Cmd+Q); first run requires confirming trust project in the interface
miniProgram = await automator.launch({
cliPath: CLI_PATH,
projectPath: PROJECT_PATH,
timeout: 120000, // Leave enough time for first launch + user trust confirmation
})
const page = await miniProgram.reLaunch(TARGET_PAGE)
// Prioritize stable selectors for main synchronization, fixed waiting only for short fallback
await page.waitFor('.page-title')
await page.waitFor(100)
const title = await page.$('.page-title')
const titleText = await title.text()
console.log('Current title: ', titleText)
await miniProgram.screenshot({
path: path.join(OUTPUT_DIR, 'current-page.png'),
})
console.log('Screenshot completed')
} finally {
if (miniProgram) {
await miniProgram.close()
}
}
}
main().catch((error) => {
console.error(error)
process.exit(1)
})模板 A:独立脚本通用骨架
Template B: Input and Validation Inside Custom Components
当用户说“给我一个脚本”“先别改测试框架”“想快速跑一下”,优先从这个模板起步。
js
const automator = require('miniprogram-automator')
const path = require('node:path')
const fs = require('node:fs/promises')
const CLI_PATH = process.env.WECHAT_DEVTOOLS_CLI || '/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
const PROJECT_PATH = process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/devtools-project'
const TARGET_PAGE = '/pages/home/index' // 必须以 / 开头
const OUTPUT_DIR = path.resolve(process.cwd(), 'outputs')
async function main() {
let miniProgram
try {
await fs.mkdir(OUTPUT_DIR, { recursive: true })
// 前提:开发者工具已完全退出(Cmd+Q);首次运行需在界面确认信任项目
miniProgram = await automator.launch({
cliPath: CLI_PATH,
projectPath: PROJECT_PATH,
timeout: 120000, // 给首次启动 + 用户确认信任留足时间
})
const page = await miniProgram.reLaunch(TARGET_PAGE)
// 主同步手段优先用稳定选择器,固定等待只做短兜底
await page.waitFor('.page-title')
await page.waitFor(100)
const title = await page.$('.page-title')
const titleText = await title.text()
console.log('当前标题:', titleText)
await miniProgram.screenshot({
path: path.join(OUTPUT_DIR, 'current-page.png'),
})
console.log('截图完成')
} finally {
if (miniProgram) {
await miniProgram.close()
}
}
}
main().catch((error) => {
console.error(error)
process.exit(1)
})When the user mentions "there's an input in the component", "why can't page.$ find it", "need to read value or data", use this template.
js
const automator = require('miniprogram-automator')
async function run() {
let miniProgram
try {
miniProgram = await automator.launch({
cliPath: process.env.WECHAT_DEVTOOLS_CLI || '/Applications/wechatwebdevtools.app/Contents/MacOS/cli',
projectPath: process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/devtools-project',
})
const page = await miniProgram.reLaunch('/pages/form/index')
await page.waitFor(300)
const panel = await page.$('form-panel')
if (!panel) throw new Error('form-panel not found')
const input = await panel.$('input')
if (!input) throw new Error('Input inside component not found')
await input.tap()
await input.input('13800138000')
await page.waitFor(100)
const value = await input.value()
if (value !== '13800138000') {
throw new Error(`Input value does not match expectation: ${value}`)
}
// panel must be a custom component instance; native components or ordinary elements are not suitable for reading internal state with data()
const panelData = await panel.data().catch(() => null)
console.log('panel.data() =', panelData)
} finally {
if (miniProgram) await miniProgram.close()
}
}
run().catch((error) => {
console.error(error)
process.exit(1)
})When answering, clearly explain:
- cannot cross component boundaries
page.$ - Prioritize /
value()for reading native input valuesproperty('value') - Use component instance's to read component internal state
data()
模板 B:自定义组件内输入与校验
Template C: Mock wx.request
+ Listen to console/exception
wx.requestconsole/exception当用户提到“组件里有 input”“为什么 page.$ 选不到”“要读 value 或 data”,用这个模板。
js
const automator = require('miniprogram-automator')
async function run() {
let miniProgram
try {
miniProgram = await automator.launch({
cliPath: process.env.WECHAT_DEVTOOLS_CLI || '/Applications/wechatwebdevtools.app/Contents/MacOS/cli',
projectPath: process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/devtools-project',
})
const page = await miniProgram.reLaunch('/pages/form/index')
await page.waitFor(300)
const panel = await page.$('form-panel')
if (!panel) throw new Error('未找到 form-panel')
const input = await panel.$('input')
if (!input) throw new Error('未找到组件内 input')
await input.tap()
await input.input('13800138000')
await page.waitFor(100)
const value = await input.value()
if (value !== '13800138000') {
throw new Error(`输入框值不符合预期: ${value}`)
}
// panel 必须是自定义组件实例;原生组件或普通元素不适合用 data() 读取内部状态
const panelData = await panel.data().catch(() => null)
console.log('panel.data() =', panelData)
} finally {
if (miniProgram) await miniProgram.close()
}
}
run().catch((error) => {
console.error(error)
process.exit(1)
})回答时要明确解释:
- 不能跨组件边界
page.$ - 读原生输入值优先 /
value()property('value') - 读组件内部状态用组件实例的
data()
When the user wants to create regression scripts, fake interfaces, offline verification, or exception monitoring, prioritize this template.
First check the real feedback path of the target page: Some pages only update , print , or pop toast after successful requests, instead of rendering lists or tables. For example, the official demo's belongs to this type, so assertions should be designed around button status, logs, toast, or page data, rather than assuming the page will definitely render a list.
loadingconsolepackageAPI/pages/network/request/requestjs
const automator = require('miniprogram-automator')
async function run() {
let miniProgram
const consoleEvents = []
const exceptionEvents = []
try {
miniProgram = await automator.launch({
cliPath: process.env.WECHAT_DEVTOOLS_CLI || '/Applications/wechatwebdevtools.app/Contents/MacOS/cli',
projectPath: process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/devtools-project',
})
miniProgram.on('console', (payload) => {
consoleEvents.push(payload)
})
miniProgram.on('exception', (payload) => {
exceptionEvents.push(payload)
})
await miniProgram.mockWxMethod('request', (options = {}) => {
const res = {
data: {
code: 0,
list: [
{ id: 1, title: 'Mock Item A' },
{ id: 2, title: 'Mock Item B' },
],
},
statusCode: 200,
header: { 'content-type': 'application/json' },
cookies: [],
errMsg: 'request:ok',
}
Promise.resolve().then(() => {
if (typeof options.success === 'function') options.success(res)
if (typeof options.complete === 'function') options.complete(res)
})
return {
abort() {},
onHeadersReceived() {},
offHeadersReceived() {},
onChunkReceived() {},
offChunkReceived() {},
}
})
const page = await miniProgram.reLaunch('/packageAPI/pages/network/request/request')
await page.waitFor('button')
const button = await page.$('button')
if (!button) {
throw new Error('Request page button not found')
}
await button.tap()
await page.waitFor(100)
const pageData = await page.data().catch(() => ({}))
if (pageData.loading !== false) {
throw new Error(`Loading status abnormal after request: ${JSON.stringify(pageData)}`)
}
const successLogs = consoleEvents.filter((event) => {
if (!event || typeof event !== 'object' || !Array.isArray(event.args)) return false
return event.args.some((arg) => String(arg).includes('request success'))
})
if (!successLogs.length) {
throw new Error(`No request success logs observed: ${JSON.stringify(consoleEvents)}`)
}
const consoleErrors = consoleEvents.filter((event) => {
return event && typeof event === 'object' && event.type === 'error'
})
if (consoleErrors.length) {
throw new Error(`console.error exists: ${JSON.stringify(consoleErrors)}`)
}
if (exceptionEvents.length) {
throw new Error(`Exceptions exist: ${JSON.stringify(exceptionEvents)}`)
}
} finally {
if (miniProgram) {
await miniProgram.restoreWxMethod('request').catch(() => {})
await miniProgram.close().catch(() => {})
}
}
}
run().catch((error) => {
console.error(error)
process.exit(1)
})Additional Notes:
- The payload obtained by the event callback usually contains
consoleandtypeargs - When judging failures, prioritize filtering by , do not count normal
payload.type === 'error'as failureslog
模板 C:Mock wx.request
+ 监听 console/exception
wx.requestconsole/exceptionTemplate E: Complete Script for CLI v2 + connect()
connect()当用户要做回归脚本、接口伪造、无网验证、异常监控时,优先给这个模板。
先核对目标页面的真实反馈路径:有些页面只会在请求成功后更新 、打印 、弹出 toast,而不会渲染列表或表格。像官方 demo 的 就属于这种页面,因此断言应围绕按钮状态、日志、toast 或页面数据来设计,不要凭空假设页面一定会出现列表渲染。
loadingconsolepackageAPI/pages/network/request/requestjs
const automator = require('miniprogram-automator')
async function run() {
let miniProgram
const consoleEvents = []
const exceptionEvents = []
try {
miniProgram = await automator.launch({
cliPath: process.env.WECHAT_DEVTOOLS_CLI || '/Applications/wechatwebdevtools.app/Contents/MacOS/cli',
projectPath: process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/devtools-project',
})
miniProgram.on('console', (payload) => {
consoleEvents.push(payload)
})
miniProgram.on('exception', (payload) => {
exceptionEvents.push(payload)
})
await miniProgram.mockWxMethod('request', (options = {}) => {
const res = {
data: {
code: 0,
list: [
{ id: 1, title: 'Mock Item A' },
{ id: 2, title: 'Mock Item B' },
],
},
statusCode: 200,
header: { 'content-type': 'application/json' },
cookies: [],
errMsg: 'request:ok',
}
Promise.resolve().then(() => {
if (typeof options.success === 'function') options.success(res)
if (typeof options.complete === 'function') options.complete(res)
})
return {
abort() {},
onHeadersReceived() {},
offHeadersReceived() {},
onChunkReceived() {},
offChunkReceived() {},
}
})
const page = await miniProgram.reLaunch('/packageAPI/pages/network/request/request')
await page.waitFor('button')
const button = await page.$('button')
if (!button) {
throw new Error('未找到 request 页面按钮')
}
await button.tap()
await page.waitFor(100)
const pageData = await page.data().catch(() => ({}))
if (pageData.loading !== false) {
throw new Error(`请求结束后 loading 状态异常: ${JSON.stringify(pageData)}`)
}
const successLogs = consoleEvents.filter((event) => {
if (!event || typeof event !== 'object' || !Array.isArray(event.args)) return false
return event.args.some((arg) => String(arg).includes('request success'))
})
if (!successLogs.length) {
throw new Error(`未观察到 request success 日志: ${JSON.stringify(consoleEvents)}`)
}
const consoleErrors = consoleEvents.filter((event) => {
return event && typeof event === 'object' && event.type === 'error'
})
if (consoleErrors.length) {
throw new Error(`存在 console.error: ${JSON.stringify(consoleErrors)}`)
}
if (exceptionEvents.length) {
throw new Error(`存在 exception: ${JSON.stringify(exceptionEvents)}`)
}
} finally {
if (miniProgram) {
await miniProgram.restoreWxMethod('request').catch(() => {})
await miniProgram.close().catch(() => {})
}
}
}
run().catch((error) => {
console.error(error)
process.exit(1)
})补充说明:
- 事件回调拿到的 payload 通常包含
console和typeargs - 做失败判定时,优先按 过滤,不要把正常
payload.type === 'error'一起算成失败log
When the user says "DevTools is already open", "don't want to restart the tool", "launch reports an error and want to switch methods", use this template.
js
const automator = require('miniprogram-automator')
const path = require('node:path')
const fs = require('node:fs/promises')
const { spawnSync } = require('node:child_process')
const CLI_PATH =
process.env.WECHAT_DEVTOOLS_CLI ||
'/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
// projectPath is the actual directory opened by DevTools (contains project.config.json)
const PROJECT_PATH =
process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/project'
// Automation WebSocket port, default 9420
const AUTO_PORT = Number(process.env.WECHAT_AUTO_PORT || 9420)
const TARGET_PAGE = '/pages/home/index'
const OUTPUT_DIR = path.resolve(process.cwd(), 'outputs')
/**
* Detect the current HTTP service port of DevTools.
* Principle: Call the CLI once with a placeholder port, then parse the actual port from the error output.
* If the tool is not running, return null (CLI will start it automatically).
*/
function detectHttpPort() {
if (process.env.WECHAT_DEVTOOLS_PORT) {
return Number(process.env.WECHAT_DEVTOOLS_PORT)
}
try {
const result = spawnSync(
CLI_PATH,
['auto', '--project', PROJECT_PATH, '--port', '9999'],
{ encoding: 'utf8', timeout: 8000 },
)
const output = (result.stdout || '') + (result.stderr || '')
const match = output.match(/started on http:\/\/127\.0\.0\.1:(\d+)/)
if (match) return Number(match[1])
} catch (_) {}
return null
}
/**
* Enable automation WebSocket port with CLI v2 command.
* Command: cli auto --project <path> --auto-port <port> [--port <httpPort>]
*/
function enableAutomation(httpPort) {
const args = ['auto', '--project', PROJECT_PATH, '--auto-port', String(AUTO_PORT)]
if (httpPort) args.push('--port', String(httpPort))
const result = spawnSync(CLI_PATH, args, { encoding: 'utf8', timeout: 20000 })
return {
success: result.status === 0,
output: (result.stdout || '') + (result.stderr || ''),
}
}
async function main() {
let miniProgram
try {
await fs.mkdir(OUTPUT_DIR, { recursive: true })
// Step 1: Detect HTTP port to avoid CLI port conflict errors
const httpPort = detectHttpPort()
console.log(httpPort ? `HTTP Port: ${httpPort}` : 'No HTTP port detected, started by CLI')
// Step 2: Enable automation ws port with CLI v2
const { success, output } = enableAutomation(httpPort)
if (!success) throw new Error(`CLI auto command failed:\n${output}`)
console.log('Automation port is ready')
// Step 3: Connect to ws endpoint
miniProgram = await automator.connect({
wsEndpoint: `ws://127.0.0.1:${AUTO_PORT}`,
})
console.log('Connected')
// Step 4: Navigate to page and operate
const page = await miniProgram.reLaunch(TARGET_PAGE)
await page.waitFor('.page-title')
await page.waitFor(200)
await miniProgram.screenshot({ path: path.join(OUTPUT_DIR, 'page.png') })
console.log('Screenshot completed')
} finally {
if (miniProgram) {
// In connect mode, close() only disconnects the ws connection, does not close DevTools
await miniProgram.close().catch(() => {})
}
}
}
main().catch((error) => {
console.error(error)
process.exit(1)
})Additional Notes:
- in
close()mode only disconnects the WebSocket connection and does not close the DevTools window — this is different from the behavior ofconnect()afterclose()launch() - is the automation ws port (used by automator to connect),
--auto-portis the tool's HTTP service port (used by CLI management), they are different--port - If unsure about the tool's HTTP port, use to detect first; if the tool is not running, CLI will start it automatically
detectHttpPort()
模板 E:CLI v2 + connect()
完整脚本
connect()Template D: Batch Screenshot Script Snippet
当用户说"开发者工具已经开着""不想重启工具""launch 报错想换一种方式",用这个模板。
js
const automator = require('miniprogram-automator')
const path = require('node:path')
const fs = require('node:fs/promises')
const { spawnSync } = require('node:child_process')
const CLI_PATH =
process.env.WECHAT_DEVTOOLS_CLI ||
'/Applications/wechatwebdevtools.app/Contents/MacOS/cli'
// projectPath 是开发者工具实际打开的目录(含 project.config.json)
const PROJECT_PATH =
process.env.MINIPROGRAM_PROJECT_PATH || '/absolute/path/to/project'
// 自动化 WebSocket 端口,默认 9420
const AUTO_PORT = Number(process.env.WECHAT_AUTO_PORT || 9420)
const TARGET_PAGE = '/pages/home/index'
const OUTPUT_DIR = path.resolve(process.cwd(), 'outputs')
/**
* 探测开发者工具当前 HTTP 服务端口。
* 原理:先用占位端口调用一次 CLI,从错误输出里解析实际端口。
* 如果工具还没启动,返回 null(CLI 会自行启动)。
*/
function detectHttpPort() {
if (process.env.WECHAT_DEVTOOLS_PORT) {
return Number(process.env.WECHAT_DEVTOOLS_PORT)
}
try {
const result = spawnSync(
CLI_PATH,
['auto', '--project', PROJECT_PATH, '--port', '9999'],
{ encoding: 'utf8', timeout: 8000 },
)
const output = (result.stdout || '') + (result.stderr || '')
const match = output.match(/started on http:\/\/127\.0\.0\.1:(\d+)/)
if (match) return Number(match[1])
} catch (_) {}
return null
}
/**
* 用 CLI v2 命令开启自动化 WebSocket 端口。
* 命令:cli auto --project <path> --auto-port <port> [--port <httpPort>]
*/
function enableAutomation(httpPort) {
const args = ['auto', '--project', PROJECT_PATH, '--auto-port', String(AUTO_PORT)]
if (httpPort) args.push('--port', String(httpPort))
const result = spawnSync(CLI_PATH, args, { encoding: 'utf8', timeout: 20000 })
return {
success: result.status === 0,
output: (result.stdout || '') + (result.stderr || ''),
}
}
async function main() {
let miniProgram
try {
await fs.mkdir(OUTPUT_DIR, { recursive: true })
// Step 1:探测 HTTP 端口,避免 CLI 因端口冲突报错
const httpPort = detectHttpPort()
console.log(httpPort ? `HTTP 端口: ${httpPort}` : '未检测到 HTTP 端口,由 CLI 启动')
// Step 2:用 CLI v2 开启自动化 ws 端口
const { success, output } = enableAutomation(httpPort)
if (!success) throw new Error(`CLI auto 命令失败:\n${output}`)
console.log('自动化端口已就绪')
// Step 3:connect 到 ws 端点
miniProgram = await automator.connect({
wsEndpoint: `ws://127.0.0.1:${AUTO_PORT}`,
})
console.log('已连接')
// Step 4:跳转页面并操作
const page = await miniProgram.reLaunch(TARGET_PAGE)
await page.waitFor('.page-title')
await page.waitFor(200)
await miniProgram.screenshot({ path: path.join(OUTPUT_DIR, 'page.png') })
console.log('截图完成')
} finally {
if (miniProgram) {
// connect 模式下 close() 只断开 ws 连接,不关闭开发者工具
await miniProgram.close().catch(() => {})
}
}
}
main().catch((error) => {
console.error(error)
process.exit(1)
})补充说明:
- 的
connect()只断开 WebSocket 连接,不会关闭开发者工具窗口——这与close()后的launch()行为不同close() - 是自动化 ws 端口(automator 连接用),
--auto-port是工具 HTTP 服务端口(CLI 管理用),两者不同--port - 如果不确定工具 HTTP 端口,可先用 探测;如果工具没有运行,CLI 会自行启动
detectHttpPort()
When the user wants to batch screenshot multiple pages, perform visual regression, or output a PNG list, this segment can be reused.
js
const PAGES = [
{ id: 'home', path: '/pages/home/index' },
{ id: 'list', path: '/pages/list/index' },
]
for (const item of PAGES) {
const page = await miniProgram.reLaunch(item.path)
await page.waitFor(500)
await miniProgram.screenshot({
path: path.join(OUTPUT_DIR, `${item.id}.png`),
})
}Additional Notes:
- If the page relies on asynchronous requests or animations, do not rely solely on , cooperate with selector waiting
waitFor(500) - It's better to output to summarize whether each page is successful
index.json - When the user only wants to take screenshots, a dedicated script can also be generated directly in this direction
模板 D:批量截图脚本片段
When to Provide Jest Versions
当用户要批量截图多个页面、做视觉回归、输出 PNG 清单时,可以复用这一段。
js
const PAGES = [
{ id: 'home', path: '/pages/home/index' },
{ id: 'list', path: '/pages/list/index' },
]
for (const item of PAGES) {
const page = await miniProgram.reLaunch(item.path)
await page.waitFor(500)
await miniProgram.screenshot({
path: path.join(OUTPUT_DIR, `${item.id}.png`),
})
}补充说明:
- 如果页面依赖异步请求或动画,不要只靠 ,要配合选择器等待
waitFor(500) - 最好输出 汇总每页是否成功
index.json - 用户只想截图时,也可以直接按这个方向生成专门脚本
If the user explicitly says "write as Jest cases", "integrate with CI", "create regression suites", rewrite the standalone script into:
- : Launch
beforeAllminiProgram - : Restore Mocks, close
afterAllminiProgram - : Execute page navigation, interaction, and assertions
test/it
But do not switch to Jest by default when the user only wants "a script template", as this will complicate simple problems.
Jest 版何时给
Common Errors and Corrections
如果用户明确说“写成 Jest 用例”“接入 CI”“做回归套件”,把独立脚本改写为:
- :启动
beforeAllminiProgram - :恢复 Mock、关闭
afterAllminiProgram - :执行页面跳转、交互和断言
test/it
但不要在用户只想要“一个脚本模板”时,默认切到 Jest。那会把简单问题变复杂。
| Common Error | Correct Approach |
|---|---|
Fix | Set it to "the actual directory opened by DevTools"; in scenarios like Taro, it may be |
| Only remind to install dependencies, not security settings | Must remind to enable the service port |
Rely entirely on fixed | Prioritize |
Use | First |
Use runtime patch | Default to |
| Forget to restore mocks | Use |
| Treat screenshots as a real machine capability | Clearly state that screenshots only support DevTools simulator |
Directly read | For native input boxes, prioritize |
Use old versions of | Upgrade to |
Call | Ensure DevTools is completely closed (Cmd+Q) before launch |
Do not set timeout or set too short timeout for | First launch + user trust confirmation takes time, recommend |
Page path does not start with | Must start with |
| Directly screenshot pages that may time out with selector waiting (e.g., home page list) | For complex pages like home page, fixed waiting |
Still use | Switch to CLI v2 + |
| In connect mode, |
常见错误与修正
Troubleshooting Tips
—
Launch Failure / Timeout
| 常见错误 | 正确做法 |
|---|---|
把 | 写成"开发者工具实际打开的目录",Taro 等场景可能是 |
| 只提醒安装依赖,不提醒安全设置 | 必须提醒开启服务端口 |
全靠固定 | 优先 |
用 | 先 |
用运行时 patch | 默认优先 |
| 忘记恢复 mock | 在 |
| 把截图当成真机能力 | 明确说明截图仅支持开发者工具模拟器 |
直接读 | 原生输入框优先 |
使用 | 升级到 |
| launch 前必须确保开发者工具完全退出(Cmd+Q) |
| 首次启动 + 用户确认信任需要时间,建议 |
页面路径不以 | 必须以 |
| 用选择器等待可能超时的页面(如首页列表)直接截图 | 首页等复杂页面用固定等待 |
开发者工具已开着时还用 | 改用 CLI v2 + |
| |
Check these first:
- Whether version is
miniprogram-automator(old versions will report code 31)>= 0.12.0 - Whether DevTools is completely closed (launch() cannot coexist with running instances)
- Whether DevTools is installed and logged in
- Whether CLI path is correct
- Whether Security Settings → Service Port is enabled
- Whether is really the directory opened by DevTools (contains
projectPath)project.config.json - Whether a sufficiently long is set (recommend
timeoutfor first launch)120000
If keeps failing (especially when DevTools is running), switch to CLI v2 + method (see Template E):
launch()connect()- First execute to enable automation ws port
cli auto --project <path> --auto-port <port> - Then connect with `automator.connect({ wsEndpoint: 'ws://127.0.0.1:<port>' })
- Use to detect current HTTP port to avoid port conflict errors
detectHttpPort()
排障提示
Page Navigation Successful but Elements Cannot Be Found
启动失败 / 超时
—
优先检查:
- 版本是否
miniprogram-automator(旧版会报 code 31)>= 0.12.0 - 开发者工具是否已完全退出(不能与已运行实例共存)
launch() - 开发者工具是否已安装并登录
- CLI 路径是否正确
- 安全设置 → 服务端口是否已开启
- 是否真的是开发者工具打开的目录(含
projectPath)project.config.json - 是否设置了足够长的 (首次启动建议
timeout)120000
如果 持续失败(尤其是开发者工具已在运行时),换用 CLI v2 + 方式(见模板 E):
launch()connect()- 先执行 开启自动化 ws 端口
cli auto --project <path> --auto-port <port> - 再用 连接
automator.connect({ wsEndpoint: 'ws://127.0.0.1:<port>' }) - 用 探测当前 HTTP 端口可避免端口冲突报错
detectHttpPort()
Check these first:
- Whether selectors are stable
- Whether the page is still rendering asynchronously
- Whether elements inside components are mistakenly treated as page-level nodes for querying
跳页成功但选不到元素
Mock Not Taking Effect
优先检查:
- 选择器是否稳定
- 页面是否仍在异步渲染
- 是否误把组件内部节点当成页面级节点查找
Check these first:
- Whether the mock was installed before the page's first request
- Whether the business layer actually calls the mocked wx method
- Whether there is residual pollution from previous rounds at the end of the use case
Mock 没生效
Screenshot Failure
优先检查:
- 是否在页面首次请求前就安装了 mock
- 业务层是否最终真的调用了被 mock 的 wx 方法
- 用例结束时是否有前一轮残留污染
Check these first:
- Whether running in DevTools simulator
- Whether the page has rendered stably
- Whether the output directory is writable
截图失败
Implementation Habits When Answering
优先检查:
- 是否在开发者工具模拟器里运行
- 页面是否已稳定渲染
- 输出目录是否可写
If the user asks you to directly modify project files, follow this path first:
- First explain which script file you plan to create or modify
- First confirm the path, page, CLI, target behavior
- Then write the file
- Finally provide run commands and success judgment methods
If the user is just asking "how to do", output the template first, do not create a bunch of files without permission.
回答时的落地习惯
—
如果用户要你直接改项目文件,优先遵循这条路径:
- 先说明你准备创建或修改哪个脚本文件
- 先确认路径、页面、CLI、目标行为
- 再写文件
- 最后给运行命令和成功判定方式
如果用户只是在问“怎么做”,就先输出模板,不要擅自创建一堆文件。
—