小程序自动化测试与 CI/CD 实战:miniprogram-simulate、automator 与流水线搭建
"每次发版前手工点一遍所有页面"——小程序项目超过 20 个页面后,人工回归就是一场酷刑,而且一定漏测。这篇介绍微信官方的两大测试利器(miniprogram-simulate 组件测试、miniprogram-automator 端到端测试)和基于 miniprogram-ci 的自动化发布流水线,把发版从"手工活"变成"流水线作业"。
一、测试金字塔:小程序版
/ E2E 测试 \ automator(真机/工具自动化)
/ 业务流程回归 \ 核心链路:登录、下单、支付
/---------------\
/ 页面测试 \ Page 级别,模拟交互
/-------------------\
/ 组件测试 \ miniprogram-simulate(单组件渲染+断言)
/-----------------------\
/ 纯函数/工具类单元测试 \ Jest(无渲染依赖的逻辑)
/---------------------------\比例原则:单元测试多而快,E2E 测试少而稳。全用 E2E 会又慢又脆,全用单元测试覆盖不了真实渲染。
二、组件测试:miniprogram-simulate
2.1 安装与配置
npm install --save-dev miniprogram-simulate jest// package.json
{
"scripts": {
"test": "jest"
},
"jest": {
"testEnvironment": "jsdom"
}
}2.2 测试一个组件
假设有个 price-tag 组件,props 传分转换为元展示:
// test/price-tag.test.js
const simulate = require('miniprogram-simulate')
const path = require('path')
test('price-tag 渲染价格', () => {
const id = simulate.load(path.join(__dirname, '../components/price-tag/index'), {
// 处理 usingComponents 里的自定义组件引用
rootPath: path.join(__dirname, '../')
})
const comp = simulate.render(id, {
price: 9900, // 分
currency: '¥'
})
const parent = document.createElement('parent-wrapper')
comp.attach(parent)
// 断言渲染结果
expect(comp.querySelector('.price').dom.innerHTML).toContain('99')
expect(comp.querySelector('.symbol').dom.innerHTML).toBe('¥')
// 修改数据触发更新
comp.setData({ price: 1050 })
expect(comp.dom.innerHTML).toContain('10.5')
})2.3 测试组件事件
test('点击触发 buy 事件', () => {
const id = simulate.load('/components/goods-card/index')
const comp = simulate.render(id, { goods: mockGoods })
// 监听组件自定义事件
let received = null
comp.addEventListener('buy', (e) => { received = e.detail })
// 模拟点击
comp.querySelector('.buy-btn').dispatchEvent('tap')
return simulate.sleep(10).then(() => {
expect(received).toEqual({ goodsId: 'g1', count: 1 })
})
})适用边界:simulate 是在 jsdom 里模拟小程序运行时,wxs、部分原生组件(map/canvas/web-view)渲染不了。这类组件用 automator 的真机/工具链路测。
三、端到端测试:miniprogram-automator
3.1 启动与连接
npm install --save-dev miniprogram-automator// test/e2e/login.e2e.js
const automator = require('miniprogram-automator')
describe('登录流程', () => {
let miniProgram, page
beforeAll(async () => {
miniProgram = await automator.launch({
cliPath: 'D:/software/wechat-web-devtools/cli.bat', // 开发者工具 CLI 路径
projectPath: 'D:/work/my-miniprogram', // 项目路径
// 需要开发者工具开启「服务端口」(设置→安全)
})
await miniProgram.reLaunch('/pages/login/login')
page = await miniProgram.currentPage()
}, 60000)
afterAll(async () => {
await miniProgram.close()
})
})3.2 页面元素操作与断言
test('微信授权登录成功后跳转首页', async () => {
// 获取元素
const btn = await page.$('.login-btn')
expect(await btn.text()).toBe('微信一键登录')
// mock 授权弹窗(真机上无法自动点授权,mock wx API 是 E2E 的常规手段)
await miniProgram.mockWxMethod('getSetting', {
authSetting: { 'scope.userInfo': true }
})
await miniProgram.mockWxMethod('login', { code: 'mock-code' })
// 点击
await btn.tap()
// 等待页面跳转(轮询获取当前页面路径)
await miniProgram.pageWaitFor('/pages/index/index')
const newPage = await miniProgram.currentPage()
expect(newPage.path).toBe('pages/index/index')
// 断言页面数据
const data = await newPage.data()
expect(data.userInfo.nickName).toBeTruthy()
})3.3 mock 能力是 E2E 的灵魂
真机自动化测不了微信授权弹窗、支付密码输入这类系统级交互,mockWxMethod 是官方给的口子:
// mock 掉支付,专注测试支付成功后的业务流转
await miniProgram.mockWxMethod('requestPayment', { errMsg: 'requestPayment:ok' })
// mock 网络请求,构造稳定测试环境
await miniProgram.mockWxMethod('request', { data: { code: 0, data: mockOrder } })
// ...测试结束后恢复
await miniProgram.restoreWxMethod('request')策略:mock 边界 API(登录/支付/授权),不 mock 业务接口——业务接口走测试环境真实数据,E2E 才有回归价值。
3.4 截图对比(可选)
// 关键页面截图,接入像素对比工具(如 pixelmatch)做视觉回归
await page.screenshot({ path: `snapshots/${page.path.replace(/\//g, '_')}.png` })四、发布流水线:miniprogram-ci
手动点"上传"发版的问题:谁传的、什么时候传的、传的什么代码全靠自觉。ci 工具让发版代码化。
4.1 准备密钥
小程序后台 → 开发管理 → 开发设置 → 小程序代码上传,生成上传密钥(IP 白名单建议配置)。
npm install --save-dev miniprogram-ci4.2 上传脚本
// scripts/upload.js
const ci = require('miniprogram-ci')
const path = require('path')
async function upload({ version, desc }) {
const project = new ci.Project({
appid: process.env.WX_APPID,
type: 'miniProgram',
projectPath: path.resolve(__dirname, '../dist'),
privateKeyPath: path.resolve(__dirname, './private.key'),
ignores: ['node_modules/**/*']
})
const result = await ci.upload({
project,
version, // 版本号,如 1.4.2
desc, // 版本描述
setting: {
es6: true,
minify: true, // 压缩
autoPrefixWXSS: true
},
// 机器人编号 1-30:不同流水线用不同机器人,后台显示区分来源
robot: process.env.CI_ROBOT || 1
})
console.log('上传成功', result)
}
upload({ version: process.env.VERSION, desc: process.env.DESC })4.3 预览二维码(测试分发)
const qrcode = await ci.preview({
project,
desc: '提测版本',
qrcodeFormat: 'image',
qrcodeOutputDest: './preview.jpg'
})
// 把 preview.jpg 推到测试群,测试同学扫码即测4.4 完整流水线(GitHub Actions 示例)
# .github/workflows/release.yml
name: miniprogram-release
on:
push:
tags: ['v*']
jobs:
test-then-upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: 安装依赖
run: npm ci
- name: 单元测试 + 组件测试
run: npm test -- --coverage
- name: 构建
run: npm run build
- name: 上传体验版
env:
PRIVATE_KEY: ${{ secrets.WX_PRIVATE_KEY }}
VERSION: ${{ github.ref_name }}
run: |
echo "$PRIVATE_KEY" > scripts/private.key
node scripts/upload.js
# 私钥用 GitHub Secrets 存,永远不进仓库
- name: 通知测试群
run: curl -X POST ${{ secrets.WEBHOOK }} -d '{"msg":"体验版已更新: ${{ github.ref_name }}"}'打 tag 触发 → 跑测试 → 构建产物 → 上传体验版 → 群通知领测。全流程 5 分钟,人工介入为零,正式版发布只是最后一步"提交审核"由人在后台点一下确认。
五、测试策略落地建议
| 项目阶段 | 投入策略 |
|---|---|
| 0-5 个页面的新项目 | 只测纯函数(Jest),先把工具函数测稳 |
| 10+ 页面,开始多人协作 | 引入组件测试,公共组件(请求弹层/卡片)全覆盖 |
| 20+ 页面,有交易链路 | E2E 覆盖核心链路(登录/下单/支付),流水线强制卡点 |
| 多端多项目 | 沉淀测试脚手架,统一 mock 数据管理 |
别追求覆盖率数字:90% 覆盖率但全是无断言的快照测试,不如 40% 覆盖率但每条都测在交易关键路径上。
六、避坑清单
| 坑 | 现象 | 解法 |
|---|---|---|
| automator 连不上工具 | launch 超时 | 开发者工具开启服务端口,关多个实例 |
| E2E 时好时坏 | 等待缺失 | 用 pageWaitFor 轮询代替 sleep |
| 真机授权弹窗卡死 | 自动化无法点击 | mockWxMethod 跳过系统弹窗 |
| simulate 渲染不出组件 | 使用了 wxs/原生组件 | 该组件改用 automator 测 |
| 上传报 40125 | 密钥或 IP 白名单问题 | 检查 privateKeyPath 与后台 IP 白名单 |
| 流水线密钥泄漏 | 私钥提交到仓库 | 用 CI 平台 Secrets,脚本里生成 |
| robot 冲突 | 多流水线互相顶掉 | 不同环境配不同 robot 编号 |
写在最后
小程序工程化的终局形态:git push 打 tag → 测试自动跑 → 体验版自动传 → 二维码自动进群 → 人只做两件事:看测试报告,点提交审核。前期搭流水线要花两三天,但从第一次"发版救火"开始,这些投入就开始指数级回本。测试代码不是负担,是团队交付速度的复利资产。
评论 (0)