环境搭建之第一个测试程序
什么是Minium?
minium是为小程序专门开发的自动化框架,使用minium可以进行小程序UI自动化测试。 当然,它的能力不仅仅局限于UI自动化, 比如:
- 使用
minium来进行函数的mock - 可以直接跳转到小程序某个页面
- 设置页面数据, 做针对性的全面测试
这些能力是其他的一些工具所不具备的,不仅如此,它还有许多其他特性,也是很吸引人的:
- 支持一套脚本,
iOS&Android& 模拟器,三端运行 - 提供丰富的页面跳转方式,看不到也能去得到
- 可以获取和设置小程序页面数据,让测试不止点点点
- 可以直接触发小程序元素绑定事件
- 支持往
AppSerive注入代码片段执行 - 可以调用部分
wx对象上的接口 - 支持
Mock wx对象上的接口 - 支持
Hook wx对象上的接口 - 通过
suite方式管理用例,config管理运行设备 - ...
环境搭建
准备工作
- Python 3.8及以上
- 选择稳定版下载 微信开发者工具
- 微信 >= 7.0.7
自动安装
pip3 install minium或者
pip3 install https://minitest.weixin.qq.com/minium/Python/dist/minium-latest.zip
手动安装
下载 minium安装包, 解压后进入文件夹, 运行
1 | python3 setup.py install |
设置微信开发者工具

找开发要源代码
这里我们以官方示例小程序项目作为演示,使用git直接clone:
1 | git clone https://github.com/wechat-miniprogram/miniprogram-demo.git |
下载到本地之后,先cd到miniprogram-demo中,然后cnpm i。接着再cd到miniprogram中,再次cnpm i。(npm 真不好使)
安装、依赖、编译成功,就可以看到如下小程序:

环境确认
minium安装完成后,可执行以下命令查看版本:
输入minitest -v
出现如下表示安装成功

开发者工具自动化能力检查
1 | path/to/cli" auto --project "path/to/project" --auto-port 9420 |
路径说明:
path/to/project: 指代填写存放小程序源码的目录地址,文件夹中需要包含有project.config.json文件path/to/cli: 指代开发者工具cli命令路径。macOS:<安装路径>/Contents/MacOS/cli,Windows:<安装路径>/cli.bat- 有类似以下
log并且开发者工具上有以下提示的则通过,否则根据提示和开发者工具文档调试解决
举个栗子:
以我win10系统为例,输入如下命令:
"E:\Program Files (x86)\Tencent\微信web开发者工具\cli.bat" auto --project "D:\pyworkspace\miniprogram-demo" --auto-port 9420
看到如下显示,证明安装成功且小程序也会被启动!

编写测试脚本
项目结构
简单创建一个python项目即可,如下所示:

添加配置文件
在项目目录添加suite.json,示例如下:
1 | { |
在项目目录添加config.json,这里替换上你自己本地环境的对应路径。示例如下:
1 | { |
编写测试代码
示例代码如下:
1 | # -*- coding: utf-8 -*- |
执行测试脚本
运行结果如下:

命令行形式
1 | minitest -m test_case.first_test -c config.json -g -s suite.json |
运行结果如下:

效果

生成测试报告
输入如下命令,可生成一份美丽的测试报告
1 | python -m http.server 12345 -d outputs |

打开浏览器,访问http://localhost:12345即可查看报告。

项目配置及测试套件使用说明
搞定配置项
配置文件部分
示例如下:
1 | { |
这里很多参数没写,即走默认的配置项,关于详细的测试配置说明,请参看 官方文档说明
命令行工具
测试用例既可以用unittest的方式执行,也可以用minitest来加载用例执行,相关的参数说明如下:
minitest 命令
- -h, --help: 使用帮助。
- -v, --version: 查看 minium 的版本。
- -p PATH/--path PATH: 用例所在的文件夹,默认当前路径。
- -m MODULE_PATH, --module MODULE_PATH: 用例的包名或者文件名
- --case CASE_NAME: test_开头的用例名
- -s SUITE, --suite SUITE:就是suite.json文件,文件的格式如下:
1 | { |
说明:
- pkg_list字段说明要执行用例的内容和顺序,是一个数组,每个数组元素是一个匹配规则,会根据pkg去匹配包名,找到测试类,然后
- 根据case_list里面的规则去查找测试类的测试用例。可以根据需要编写匹配的粒度。注意匹配规则不是正则表达式,而是通配符。
入门栗子
目录结构

编写第一个case
1 | # -*- coding: utf-8 -*- |
运行单个case
执行如下命令:
1 | minitest -m test_case.sysinfo_test -c config.json -g |
重点:
测试用例的命名,一定要casename_test,否则不好使,不信你可以写成test_casename,命令行执行体验报错的感觉!
运行结果如下:

批量执行测试
执行如下命令:
1 | minitest -s suite.json -c config.json -g |
运行结果如下:

测试套件的意义在于可以批量执行用例,也是我们做自动化测试首选的方式。
部分参数说明:
- -c CONFIG, --config CONFIG:配置文件名,配置项目参考 配置文件
- -g, --generate: 生成网页测试报告
- --module_search_path [SYS_PATH_LIST [SYS_PATH_LIST ...]] : 添加 module 的搜索路径
- -a, --accounts: 查看开发者工具当前登录的多账号, 需要通过 9420 端口,以自动化模式打开开发者工具
- --mode RUN_MODE: 选择以
parallel(并行, 每个账号从队列中取一个pkg运行, 完成后取下一个)或者fork(复刻, 每个帐号都跑全部的pkg)的方式运行用例 - --task-limit-time: 任务超时时间,如果到期还没跑完测试,直接终止测试进程. 单位: s
更多命令行参数请参考 命令行工具
生成测试报告
本地报告
执行如下命令:
python -m http.server 12345 -d outputs
打开浏览器,访问http://localhost:12345即可查看报告。

利用nginx的配置报告
1 | server { |
元素定位详解
元素定位
元素定位,应该是很多UI自动化测试入门学习必会的技能了,下面我将为大家举例演示元素定位的几种方法。
CSS选择器
Minium 可以通过 WXSS 选择器定位元素,如下图所示:

如果有[CSS选择器]基础会上手更快 ,如没有可参考
示例:

CSS方式定位
示例代码如下:
1 | # class定位 |
XPATH方式定位
看到这里,有的同学可能会条件反射性的想到,右键选择 Copy,点击 Copy Xpath 或 Copy full Xpath?
没错,是支持的!

使用xpath语法定位,示例代码如下:
1 | # 绝对定位 |
个人感觉是完美兼容Selenium的CSS 、XPATH定位方式的,参考学习CSS定位入门、XPATH定位入门这两篇。
selector方式定位
推荐使用id/class/标签+属性。inner_text/text_contains/value为增强用法,实现本质还是通过selector获取到元素标签后再通过inner_text/text_contains/value筛选元素。
示例代码如下:
1 | # 适合没有属性元素定位 |
跨自定义组件元素定位方式
怎样识别自定义组件
- 看
wxml文件或微信开发者工具的wxml pannel,标签名字不在小程序官方组件列表中的都是 自定义组件 - 看微信开发者工具的
wxml pannel, 标签下面有#shadow-root的, 则为 自定义组件 。例如小程序页面wxml中mytest 、test2、 test22

定位 **test2** 标签下的 text 的元素
示例代码如下:
1 | # 没有元素属性,可以文本定位一试 |
定位示例详解
这里我贴出源代码给的注释部分,个人觉得这个注释写的真好,一看就懂,如下图所示:

总结
元素定位小结:
- 不建议使用基础标签
view、text方式定位元素,有时会因为dom加载不出来找不到元素,源码中参数max_timeout=0,有需要可自己指定超时时间。 - 小程序发版频繁不建议使用绝对定位,使用有一定标识性或属性结合定位,可考虑选择器定位
- 若元素没有属性,则可考虑
XPath,或标签+文本定位 - 自定义组件定位,可以考虑跨自定义组件的后代选择器或逐层定位元素方法定位
API详解(上)
常用API使用详解
get_system_info()
获取系统信息
shutdown()
测试结束时调用, 停止 微信开发者IDE 以及 minium, 并回收资源。一般供测试框架调用
screen_shot()
截图
ide上仅能截取到wxml页面的内容,Modal/Actionsheet/授权弹窗等无法截取
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| save_path | str | Not None | 截图保存路径 |
| format | str | raw | 截图数据返回格式,raw 或者 pillow |
示例代码如下:
1 | def test_screen_shot(self): |
evaluate()
向 app Service 层注入代码并执行
真机调试2.0下, 注入的代码只支持es5的语法
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| app_function | str | Not None | 代码字符串 |
| args | list | Not None | 参数 |
| sync | bool | False | 是否同步执行 |
Returns:
- sync == True: dict(result={"result": 函数返回值})
- sync == False: str(消息ID)。配合
get_async_response用获取返回值
示例代码如下:
1 | import minium |
get_async_response()
获取
evaluate方法异步调用的结果
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| msg_id | str | Not None | evaluate返回的消息ID |
| timeout | int | None | 等待超时时间,None: 立刻返回 |
示例代码如下:
1 | import minium |
get_all_pages_path()
获取所有已配置的页面路径
Returns:
list
示例代码如下:
1 | def test_get_all_pages_path(self): |
get_current_page()
获取当前顶层页面
Returns:
- 页面对象
示例代码如下:
1 | import minium |
go_home()
跳转到小程序首页
示例代码如下:
1 | def test_go_home(self): |
navigate_to()
以导航的方式跳转到指定页面
不能跳到 tabbar 页面。支持相对路径和绝对路径, 小程序中页面栈最多十层
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | str | Not None | 页面路径 |
| params | dict | None | 页面参数 |
| is_wait_url_change | bool | True | 是否等待新的页面跳转 |
个人感觉就是跳转到指定页面,也可以理解为导航栏上的超链接带路径那种的
示例代码如下:
1 | def test_navigate_to(self): |
navigate_back()
关闭当前页面,返回上一页面
示例代码如下:
1 | def test_navigate_to(self): |
redirect_to()
关闭当前页面,重定向到应用内的某个页面
不允许跳转到 tabbar 页面
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | str | Not None | 页面路径 |
| params | dict | None | 页面参数 |
| is_wait_url_change | bool | True | 是否等待新的页面跳转 |
个人感觉同navigate_to()用法很像,各种细节感兴趣的同学可以自行尝试。
示例代码如下:
1 | def redirect_to(self): |
relaunch()
关闭所有页面,打开到应用内的某个页面
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | str | Not None | 页面路径 |
示例代码如下:
1 | def test_relaunch(self): |
switch_tab()
跳转到 tabBar 页面
会关闭其他所有非tabBar页面
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | str | Not None | 需要跳转的 tabBar 页面的路径(需在 app.json 的 tabBar 字段定义的页面),路径后不能带参数 |
| is_click | bool | False | 切换tab的时候触发一次onTabItemTap |
示例代码如下:
1 | def switch_tab(self): |
什么是tabbar页面?
举个栗子,比如我们小程序的底部有图标加文字的几个按钮,每个按钮对应一个页面,而整个小程序中有很多页面,小程序底部图标加文字对应的几个页面是tabbar页面,这个在app.json中有设置。
eg:在app.json中设置对应的tabbar页面
1 | "tabBar": { |
get_perf_time()
查询小程序的性能指标,跟stop_get_perf_time配对使用
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| entry_types | list | Not None | 可选项为['render', 'script', 'navigation', 'loadPackage']中的1个或多个 |
stop_get_perf_time()
结束查询,跟get_perf_time配对使用
示例代码如下:
1 | def test_get_perf_time(self): |
可以看到一些路径跳转的耗时,结果如下:
1 | [{ |
wait_for_page()
等待页面跳转成功
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| page_path | str | Not None | 需要等待的页面路径, 需要绝对路径, 如/pages/index |
示例代码如下:
1 | def test_wait_for_page(self): |
wait_util()
指定时间内, 剩余没有完成的异步请求数 <= {cnt}个, 此时认为页面异步加载完成
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| cnt | int | Not None | 剩余的异步请求个数 |
| max_timeout | int | 10 | 最大等待时间 |
示例代码如下:
1 | def test_wait_util(self): |
API详解(下)
Page中API的使用
data
当前页面数据, 可直接赋值
1 | Page({ |
示例代码如下:
1 | def test_data(self): |
element_is_exists()
在当前页面查询元素是否存在
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| selector | str | Not None | css选择器或以/或//开头的xpath |
| max_timeout | int | 10 | 超时时间,单位 s |
| inner_text | str | None | 通过控件内的文字识别控件 |
| text_contains | str | None | 通过控件内的文字模糊匹配控件 |
| value | str | None | 通过控件的 value 识别控件 |
| xpath | str | None | 显式指定xpath |
示例代码如下:
1 | def test_element_is_exists(self): |
get_element()
获取页面元素
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| selector | str | Not None | CSS选择器或以/或//开头的 XPath |
| inner_text | str | None | 通过控件内的文字识别控件 |
| text_contains | str | None | 通过控件内的文字模糊匹配控件 |
| value | str | None | 通过控件的 value 识别控件 |
| max_timeout | int | 0 | 超时时间,单位 s |
| xpath | str | None | 显式指定 XPath, 小程序基础库2.19.5后支持 |
PS: selector 仅支持下列语法:
- ID选择器:
#the-id - class选择器(可以连续指定多个):
.a-class.another-class - 标签选择器:
view - 子元素选择器:
.the-parent > .the-child - 后代选择器:
.the-ancestor .the-descendant - 跨自定义组件的后代选择器:
custom-element1>>>.custom-element2>>>.the-descendantcustom-element1 和 .custom-element2必须是自定义组件标签或者能获取到自定义组件的选择器 - 多选择器的并集:
#a-node, .some-other-nodes - xpath:可以在真机调试的wxml pannel
选择节点->右键->copy->copy full xpath获取,暂不支持[text()='xxx']这类xpath条件 - 自定义组件不支持穿透, 需要先get自定义组件, 再使用Element.get_element获取其子节点, 或使用[>>>]连接自定义组件及其后代元素, 如发现无法正常定位, 可根据这个方法 辨别自定义组件
- 更多元素定位实例
Returns:
实例代码如下:
1 | def test_get_element(self): |
get_elements()
获取一组元素
PS: 支持的选择器同 get_element()
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| selector | str | Not None | css选择器或以/或//开头的xpath |
| max_timeout | int | 0 | 超时时间,单位 s |
| inner_text | str | None | 通过控件内的文字识别控件, xpath暂不支持 |
| text_contains | str | None | 通过控件内的文字模糊匹配控件, xpath暂不支持 |
| value | str | None | 通过控件的 value 识别控件, xpath暂不支持 |
| index | int | -1 | index==-1: 获取所有符合的元素, index>=0: 获取前index+1符合的元素 |
| xpath | str | None | 显式指定xpath, 小程序基础库2.19.5后支持 |
Returns:
- List[ Element]
示例代码如下:
1 | def test_get_elements(self): |
scroll_to()
滚动到指定高度
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| scroll_top | int | Not None | 高度,单位 px |
| duration | int | 300 | 滚动动画时长,单位 ms |
Returns:
None
示例代码如下:
1 | def test_scroll_to(self): |
wait_for()
等待直到指定的条件成立, 条件可以是页面元素, 也可以是自定义的函数或者是需要等待的时间(单位秒)
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| condition | int | str | function |
| max_timeout | int | 10 | 超时时间,单位 s |
Returns:
bool
示例代码如下:
1 | def test_wait_for(self): |
Element中API的使用
get_element()
查找一个元素
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| selector | str | Not None | 选择器 |
| inner_text | str | None | 通过控件内的文字识别控件 |
| text_contains | str | None | 通过控件内的文字模糊匹配控件 |
| value | str | None | 通过控件的 value 识别控件 |
| max_timeout | int | 0 | 超时时间,单位 s |
PS: selector 支持的语法:
- 除
xpath外,同 page.get_element
get_elements()
查找一组元素
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| selector | str | Not None | 选择器 |
| max_timeout | int | 0 | 超时时间,单位 s |
| inner_text | str | None | 通过控件内的文字识别控件 |
| text_contains | str | None | 通过控件内的文字模糊匹配控件 |
| value | str | None | 通过控件的 value 识别控件 |
| index | int | -1 | index==-1: 获取所有符合的元素, index>=0: 获取前index+1符合的元素 |
PS: 支持的css选择器同 get_element()
Returns:
- List[ Element]
示例代码如下:
1 | # 一个元素 |
attribute()
获取元素属性
示例代码如下:
1 | def test_attribute(self): |
tap()
点击元素
click()
在 tap()之前检查元素
pointer-events样式是否为none示例代码如下:
1 | # tap |
long_press()
长按元素
示例代码如下:
1 | # 长按操作 |
move()
移动元素(触发元素的 touchstart、touchmove、touchend 事件)
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| x_offset | int | Not None | x 方向上的偏移,往右为正数,往左为负数 |
| y_offset | int | Not None | y 方向上的偏移,往下为正数,往上为负数 |
| move_delay | int | 350 | 移动前摇,ms |
| smooth | bool | False | 平滑移动 |
1 | import minium, time |
styles()
获取元素的样式属性
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| names | str | list | Not None |
示例代码如下:
1 | def test_styles(self): |
scroll_to()
元素滚动
基础库
v2.23.4版本后支持
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| top | int | None | x 轴上滚动的距离 |
| left | int | None | y 轴上滚动的距离 |
示例代码如下:
1 | def test_scroll_to(self): |
input()
input&textarea组件输入文字IDE上不会改变element上的value属性,建议使用变化的Page.data/hook绑定的input方法判断是否生效
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| text | str | None | 输入文本 |
示例代码如下:
1 | def test_input(self): |
常见组件的处理
常见组件的处理
switch组件处理

改变 switch 组件的状态
示例代码如下:
1 | def test_switch(self): |
slide组件处理

slider 组件滑动到指定数值
示例代码如下:
1 | def test_slide_to(self): |
pick组件处理

picker 组件选值
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| value | 看下表 | Not None | 属性名称 |
value 的取值:
| 选择器类型 | 类型 | 说明 |
|---|---|---|
| selector: 普通选择器 | int | 表示选择了 range 中的第几个 (下标从 0 开始) |
| multiSelector: 多列选择器 | int | 表示选择了 range 中的第几个 (下标从 0 开始) |
| time: 时间选择器 | str | 表示选中的时间,格式为"hh:mm" |
| date: 日期选择器 | str | 表示选中的日期,格式为"YYYY-MM-DD" |
| region: 省市区选择器 | int | 表示选中的省市区,默认选中每一列的第一个值 |
示例代码如下:
1 | def test_picker(self): |
scroll_to组件处理

scroll-view 容器滚动操作
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| x | int | None | x 轴上滚动的距离 |
| y | int | None | y 轴上滚动的距离 |
示例代码如下:
1 | def test_scroll_to(self): |
swipe组件处理

切换 swiper 容器当前的页面
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| index | int | None | 索引值,从 0 开始 |
示例代码如下:
1 | def test_swipe_to(self): |
move组件处理

movable-view 容器拖拽滑动
Parameters:
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| x | int | None | x 轴方向的偏移距离 |
| y | int | None | y 轴方向的偏移距离 |
PS: x,y 偏移量相对于*movable-area*左上角,如示例中,*movable-area*左上角为(25, 25)
示例代码如下:
1 | def test_move_to(self): |
video、audio 组件

详见代码示例
video组件处理示例代码如下:
1 | def test_video(self): |
audio组件处理示例代码如下:
1 | def test_audio(self): |
其他示例
单页面示例
直接跳转到被测试的页面,进行脚本的测试。
示例代码如下:
1 | def test_set_data(self): |
数据驱动测试
测试框架继承自unittest,基于ddt封装的的简单封装。
示例代码如下:
1 | # -*- coding: utf-8 -*- |
测试框架的设计和开发
框架的设计开发
框架搭建设计要素
- 日志&测试步骤
- 报告&失败截图
- 配置文件&数据源设计
- 公共函数&API封装
- 测试数据&参数化、解耦
- 测试套件&测试用例设计、组装
工程结构

日志
日志可以很好辅助我们定位问题,示例代码如下:
1 | class LogUtils: |
数据源
这里我用的是Excel,示例如下:

示例代码如下:
1 | class ExcelUtils(object): |
可能评论区会有人说用yml、json、csv做数据源会更好,我不认同!
为什么用Excel做数据源?
- 所有的测试框架和测试工具,都应该以使用者角度考虑问题,以易用性和上手难度为先。
- 所有做测试工具及平台、测试框架,都是为他人服务,所以越简单,越好操作,更好,后期可以再优化、
- 上面做数据源,可能自我感觉技术上显得高大上,很牛逼,但是抱歉,使用者,根本不知道
yml、json是啥你怎么办,可以学,没错(互联网时代时间成本太昂贵了),不是不可能遇到,是因为最不可控的是使用者人群,不是吗?
框架的一开始设计很重要,所以整体的设计要清晰明了。
感动自己的实现不重要,而是被团队需要的实现,才会显得自己重要!
基础层
这里主要用于处理,元素对象和原生API的封装,部分代码示例如下图:

测试用例
在action层写测试用例,示例代码如下:
1 | class PageAction(BasePage): |
调用action层,执行测试用例,示例代码如下:
1 | # -*- coding: utf-8 -*- |
测试报告
觉得minium的测试报告颜值还可以,还可以看到历史的,感觉还不错,如下:

失败有截图还有日志:
