最近一直在折腾微信小程序的自动化测试,如果你也在做这一块,大概率会碰到Minium这个框架。坦白说,微信小程序的测试一直有点特殊:它不像普通Web页面,可以完全靠Selenium那套思路直接驱动浏览器;也不像原生App,能天然被UIAutomator或XCUITest接管。小程序运行的宿主环境是微信原生容器,页面结构也有自己的渲染层,传统定位方式往往失效。我在真实项目里踩了不少坑,最后选择Minium来做自定义测试脚本,算是找到了相对顺手的路径。
这篇东西适合谁?对于刚准备把小程序自动化测起来、或者已经在用录制回放工具但遇到维护困难的测试小伙伴,应该都有参考价值。我不会只讲安装和调用,更多是讲为什么这么设计、自定义用例怎么组织、跟pytest和Allure集成时有哪些坑。等项目规模上来之后,你会明白这些比简单跑通一两条用例更重要。
1. 为什么我最终选了Minium做小程序自定义测试
1.1 小程序自动化测试的现状与痛点
先聊聊背景。中小团队做小程序自动化的常见方案,无非是三条路:第一,借助微信开发者工具自带的录制回放能力,快速生成用例,但这类用例通常只是“脚本”,没有经过合理的封装,一旦页面结构变动,基本等于重写。第二,用Appium或类似方案去模拟点击,但小程序并不是原生控件,Appium拿到的视图树和真实业务并不是一回事,定位既不稳定,速度也慢。第三,走纯前端单测路线,用jest等框架测试小程序逻辑,但这只覆盖到js层,页面交互、真机兼容性根本无法验证。
而Minium恰恰是围绕小程序运行时设计的测试框架,它运行在微信开发者工具提供的自动化通道之上,可以直接操作小程序页面元素、触发事件、读取页面数据,甚至模拟地理位置等能力。它把微信容器层的复杂度包装成了Python API,让我们可以把精力放在用例设计上。所谓“自定义测试”,核心就是由你自己去编排业务场景、设计测试数据、写断言,而不是依赖工具给你生成一堆不可维护的点击序列。
1.2 Minium的核心优势与选型对比
我选择Minium,不单纯因为它名字听起来像官方,更关键的是它和业务代码的耦合方式比较合理。它的核心优势之一,是能够拿到小程序当前页面实例,通过组件路径、文本、CSS选择器等条件去定位页面元素。这一点,和Web自动化里的WebElement.find_element思路非常接近,学习成本很低。另一方面,它支持通过mock来模拟小程序API,比如定位、账号信息等场景,这在多端兼容类测试里特别有用。
选型对比时,我曾经在Appium和Minium之间犹豫了很久。Appium生态成熟,社区资料多,但对小程序而言它更像是“隔着窗户看屋里”,稳定性和定位精度都打折扣。Minium则能真正看到小程序内部的渲染节点,并且和微信开发者工具的调试能力联动,定位失败时还能直接通过工具检查页面结构。简单说,如果你要测的对象是微信小程序本身,Minium是更贴近业务的解法。当然,它也有自己的限制,比如对运行环境有一定要求,后面我会专门说。
下面的对比可以帮你快速判断:
| 维度 | Appium | Minium |
|---|---|---|
| 控件识别 | 依赖原生控件树,小程序节点不完整 | 能拿到小程序渲染层节点,定位更准 |
| 用例编写 | 语言多,但环境和依赖重 | Python优先,pytest集成方便 |
| 稳定性 | 受微信原生容器限制,时灵时不灵 | 通过开发者工具协议,稳定性更高 |
| 适合场景 | 混合App、跨App | 纯微信小程序自动化测试 |
1.3 自定义测试在Minium中的实现路径
Minium里的自定义测试,最基础的单位是继承MiniTest的测试类,里面写test_开头的实例方法,运行时框架会自动连接已启动的小程序项目,执行用例并生成测试结果。你可以把它理解成把unittest和微信小程序桥接起来:测试类的生命周期、断言方法、用例收集方式都保留,而操作小程序的动作由Minium来提供。这样我们既可以用最熟悉的测试范式,又能直接操作小程序页面。
进阶一点的玩法是,用pytest作为执行引擎,再利用Minium提供的fixture和hooks去扩展。因为MiniTest本身就兼容unittest,接入pytest也不复杂。我实际落地时,是把公共的登录、初始化、数据清理逻辑放在fixture里,用例文件里只保留业务步骤和断言。这种方法的好处是:测试数据和用例逻辑分离,后期维护成本低。这也是“自定义测试”区别于“录制脚本”最关键的地方。
2. 环境准备:搭建可复现的Minium工程
2.1 官方工具链与依赖安装
先别急着写用例,环境不对后面全是问题。Minium的运行依赖微信开发者工具,你需要打开微信开发者工具的安全设置里的服务端口,这样Minium才能通过自动化协议驱动工具。第一次接触这套东西时我容易忽略,结果跑用例报连接失败,折腾了半天。具体路径每个版本略有区别,一般在设置里找“安全设置”,打开“服务端口”即可。
然后是Python环境安装。Minium支持Python3.7以上版本,直接pip install minium就可以。但要注意,Minium版本需要和微信开发者工具的版本、小程序基础库版本匹配,否则会出现部分API不可用或者协议不兼容。官方文档里有一张版本对应表,建议先查一下再固定版本。项目里的依赖最好用requirements.txt或poetry锁版本,避免隔几天没跑就环境漂移。
2.2 配置文件的要点
自定义测试要跑起来,一般需要准备两个配置文件。一个是小程序项目自己的project.config.json,它告诉开发者工具项目根目录、AppID、编译模式等。另一个是Minium自己的配置文件,一般命名为minium.json,里面会配置测试环境、真机还是模拟器、全局的启动参数、测试报告输出路径等。
在我的工程里,minium.json大致长这样:
{ "project_path": "./mini_program", "debug": false, "platform": "wx_devtool", "devices": ["iPhone 14 Pro"], "test_recordset": false, "output_dir": "./reports" }其中project_path填小程序代码路径,platform选择使用开发者工具运行,devices可以通过开发者工具获取的可用设备列表来填。配置里不要贪多,先让一条用例能跑通,再逐步加东西。每次调整配置后,最好重启一下自动化会话,避免缓存旧的配置。
2.3 一个最小可跑的Demo用例
环境Ready后,我们先写一个最小的自定义用例验证链路。假设你的小程序首页有一个按钮,点击后出现“测试成功”四个字。那么用例可以这么写:
import minium class DemoTest(minium.MiniTest): def test_click_button(self): self.app.switch_tab("首页") page = self.page btn = page.get_element("button", inner_text="开始测试") btn.click() self.assertTrue(page.get_element("text", inner_text="测试成功").wait_for_exist(timeout=3))这段代码里,self.app.switch_tab用于切换到首页tab,page.get_element负责查找元素,btn.click()触发点击,最后用wait_for_exist等待目标元素出现。注意get_element如果找不到元素默认会抛异常,加上wait_for_exist可以避免页面还没渲染完就断言的情况。跑通这个用例,你的自定义测试环境就算搭起来了。如果你用的Minium版本API命名有差异,看下对应版本文档即可,思路完全一样。
3. 实战自定义测试:从页面定位到业务封装
3.1 元素定位:支持选择器与数据绑定
一旦开始写真正的业务用例,第一个绕不开的问题就是:怎么稳定地找到页面元素。Minium的get_element支持多种方式,比如组件类型加文本内容、CSS属性选择器、通过>class HomePage: def __init__(self, page): self.page = page def click_search(self): self.page.get_element("input", placeholder="搜索").tap() def search(self, keyword): self.click_search() self.page.get_element("input", placeholder="搜索").input(keyword) self.page.get_element("button", inner_text="搜索").tap()
测试方法里使用HomePage(self.page).search("Python自动化"),是不是清爽很多?注意Minium里的点击方法在不同版本里可能是click或tap,以当前版本API为准。页面对象的核心思想是“把页面细节关在门里”,用例只看业务动作。
3.3 数据驱动:用参数化覆盖多场景
业务用例最怕一个一个写死,一条用例覆盖一种参数,改数据相当于改代码。因此我通常会把自定义测试和pytest的参数化机制结合起来。@pytest.mark.parametrize可以直接传一组测试数据,Minium用例同样可以享受这个能力。
比如搜索场景可以这样定义:
@pytest.mark.parametrize("keyword,expected", [ ("手机", "手机相关结果"), ("电脑", "电脑相关结果"), ]) def test_search(keyword, expected): home = HomePage(page) home.search(keyword) assert expected in page.get_element("view", class_name="result").text这样新增一组数据,不需要新开用例方法,跑起来自动生成多条测试记录。在实际项目中,我还会把数据从Excel、YAML或者数据库里读出来,通过fixture注入,做到真正的数据用例分离。你可能会问参数化到底解决什么问题?它解决的是把“多场景覆盖”从代码复制粘贴中解放出来,让测试设计回归到数据组织上。
3.4 异步处理与等待机制
小程序页面渲染本身是异步的,网络请求、动画、setData都可能造成元素晚于预期出现。所以自定义测试中一定要有等待策略。Minium提供了wait_for_exist、wait_for等方法,可以设定超时时间。如果你的公司业务里大量使用小程序动画,我会建议把所有关键断言前都加上显式等待,不要裸用get_element后立刻断言。
不过等待时长不是越长越好,设太大会拖慢整个测试集。这里有一个经验值:一般业务页面元素出现时间在500ms到3s之间,我通常把超时设为5秒,如果超过5s还没出现,这说明大概率不是网络慢,而是定位条件或逻辑有问题。具体项目中,还可以通过截图或者获取页面状态来辅助判断,避免盲等。
4. 与pytest和Allure集成,打造测试报告
4.1 pytest自定义用例的组织方式
要让Minium用例跑得规模化,必须有一个好用的测试框架和报告体系。pytest无疑是当前Python社区里的默认选项,Minium的MiniTest类可以很好地融入pytest的收集机制。我有一个建议:在conftest里封装好所有Minium会话相关的fixture,比如启动app、登录、清理缓存,而不是在整个测试过程里反复初始化。
典型组织方式是项目根目录下放conftest.py,里面定义driver或者app的fixture并用session作用域启动小程序,测试用例文件只依赖fixture。这样同样一批用例在本地和CI上执行时,环境逻辑完全一致。pytest还支持按标签筛选,我习惯给冒烟、回归、慢用例打上@pytest.mark.smoke或@pytest.mark.slow,执行时只跑需要的集合,节省大量等待时间。
4.2 Allure报告如何拿到失败截图
很多做接口测的同学已经习惯用Allure展示测试结果,小程序自动化同样可以。Minium运行过程中如果断言失败,通常能拿到当时的页面截图。我们要做的,就是在用例失败时主动把截图attach到Allure报告里。一般我是写一个pytest的hook,或者用Allure的生命周期装饰器,在当前测试失败时调用self.page.get_screenshot(),然后写入到测试步骤中。
这里有个容易被忽视的细节:截图要在失败发生的那一刻抓取,而不是等所有用例跑完再补抓。否则页面可能已经停在别的状态。如果测试报告里失败用例截图都是同一张,多半就是这种时机问题。Allure报告只是手段,核心是帮助人定位问题。所以除了截图,我还会把当时的小程序页面结构、控制台日志一起收集起来,失败一眼能看懂。
4.3 执行策略:本地调试与CI管道
在本地跑,可以直接用pytest命令加参数指定用例文件。在CI管道里跑,则要考虑并行和稳定性。Minium用例如果不做处理,默认是串行执行的,因为多个自动化会话需要独立的开发者工具实例。并行可以提高速度,但对机器资源和网络环境要求高,并不建议一开始就上。我建议先保证用例稳定,再考虑并行。
另一个CI里会踩的坑是:开发者工具不能在无头模式下完整跑所有能力,必须让CI机器上保留图形化界面。如果你的CI是Linux容器,可能需要特别处理xvfb或者使用官方支持的headless能力。这个要提前评估,不要等到流水线全红了才去排查。环境稳定性是自定义测试上量的基础,宁可慢,不能挂。
5. 踩坑记录:自定义测试中的常见问题与排查
5.1 定位不到元素时的排查思路
写小程序自动化,最多的问题就是“元素明明看得见,脚本就是找不到”。我先给一个固定排查顺序:先看页面是否加载完成,再确认元素是否在可见节点而不是wx:if移除了,然后看selector的写法是不是太严格。Minium有页面结构导出能力,你可以在失败时输出当前页面节点树,对照一下真实结构和你的selector,基本很快找到问题。
如果定位条件没有问题,那要考虑是不是业务动态渲染导致的时序问题。比如列表数据是网络请求后异步填充的,直接找元素时节点还没生成。解决方法不是加重试,而是用wait_for_exist配合可见条件。另一个常见点是多个页面栈里存在同名元素,Minium默认查找的是当前页面,你要明确调用switch_page或者指定页面层级。
5.2 自定义事件触发与原生控件边界
小程序里有些交互事件不是纯页面点击,比如长按、触摸滑动、下拉刷新,甚至调用到微信原生控件,比如键盘、相机、地图。Minium对自定义事件的触发有对应API,但原生控件暴露给自动化接口的信息有限,这一点必须心理有数。我曾在“调起地图选点”这个功能上卡了很久,因为地图是原生覆盖层,Minium的get_element拿不到原生View。
通用的解法是:不能完全通过UI层点击时,就通过业务层去绕过。比如在地图选点测试中,先直接调用小程序的接口把坐标数据写入本地存储,再刷新页面断言结果。这样一来,部分原生交互的边界问题被转化成了数据问题,测试可控性和稳定性都好很多。你要理解,自动化不是所有东西都必须模拟手指,有些场景用数据种子反而更可靠。
5.3 设备并发与测试数据隔离
当用例达到一定数量,跑整套案子时,数据隔离是最大的隐性坑。每个测试如果都依赖同一个账号,前面用例改了状态,后面用例直接失败。我用过的方案是:给每个用例或每组数据生成独立账号,或者用测试数据工厂动态创建。Minium支持在用例中通过mock或者接口调用来准备数据,把数据初始化作为用例的前置步骤。
并发跑的时候,还要注意页面状态隔离。不同设备上登录的账号不要混用,否则会出现A设备上的登录态把B设备顶掉的情况。我们的做法是把设备序列号或名称作为前缀拼到测试账号里,这样每个设备都有独立会话,互不干扰。这些经验不是官方文档会告诉你的,只有跑过一整套用例后才会意识到。
6. 经验总结:把自定义测试跑上量之后的体会
6.1 用例分层:从冒烟到全量回归的成本控制
用Minium写了大概几百条用例之后,我最大的体会就是:测试用例金字塔同样适用于小程序自动化。底部是最稳定的冒烟用例,只覆盖核心功能的主路径;中部是一级功能用例,覆盖主要分支;顶部才是不稳定的边缘用例,跑全量回归时才执行。每次代码提交只跑冒烟,每天凌晨跑全量,这样既能快速反馈,又不会把CI时间拖到让人骂娘。
分层思路也影响用例编写。冒烟用例我会尽量少依赖外部数据,把启动和后置清理都做到高频可执行;边缘用例则可以接受较长耗时,因为执行频率低。千万不要试图把所有用例都做成秒级跑完,那是反直觉的。真实项目中,稳定的、能给出明确结论的用例,远比数量重要。
6.2 从“复用别人的脚本”到“写自己的脚手架”
刚开始接触Minium时,我也喜欢在网上找现成封装,即插即用。可真到自己项目,每个页面结构、业务流、账号系统都不一样,别人的轮子往往无从下手。后来我静下心,按自己项目的页面抽Page类,把登录态、测试数据工厂、报告上传这些公共能力都沉淀到自己的脚手架里。这个阶段之后,写一条新用例的速度明显加快,很多page和fixture可以直接复用。
所以我的建议是:先花时间搭建属于自己的自定义测试脚手架,而不是急着铺用例量。脚手架里面至少有稳定的设备连接管理、页面对象基类、失败自动截图、测试数据准备四件套。等这套东西稳定了,用例增长就是水到渠成。你也一样,如果现在正处在用例乱糟糟的状态,可以考虑先停下来重构一下公共层。
6.3 后续可以扩展的方向
Minium的自定义测试能力不止于现在说的这些,后面还可以做接口和UI的联动验证,比如在UI执行前后调用后端接口校验数据落库;也可以接入性能统计,在关键页面节点自动抓取首屏时间;甚至可以把业务关键链路抽出来,做成生产环境巡检。个人认为,小程序自动化最终价值不在用例数量,而在于把测试逻辑变成产品反馈的一环,让每次发版前能快速回答“核心功能是否还正常”。
如果你愿意,可以把Minium这套能力和现有的接口测试平台结合,共享测试数据和报告。实验下来,这种“接口铺底、UI验证”的组合玩法,覆盖率比任何单跑一层都高。路还很长,但方向值得投入。