小程序自动化测试总结

2019 年 7 月 2 日 IMWeb前端社区

本文由 IMWeb 首发于 IMWeb 社区网站 imweb.io。点击阅读原文查看 IMWeb 社区更多精彩文章。


一、缘起-为什么要进行小程序自动化测试

微信小程序生态日益完善,很多小程序项目页面越来越多,结构越来越复杂,业务逻辑也更加多样。以腾讯课堂小程序为例,目前腾讯课堂小程序部分页面结构和不同业务场景下的表现如下图所示:

可以看到在核心功能上主要页面对于不同业务场景有众多不同的表现,因此在开发与发布的过程中需要手动验证大量测试用例以保证小程序按预期表现运行,善于利用工具的程序员当然会想:

这种重复的工作能不能交给程序自动进行呢?

web开发中对于这类测试问题已经有了很多自动化解决方案比如Selenium、Puppeteer,思路大体相同,都是让浏览器按照指定顺序自动在页面上完成点击、输入等操作,再将操作后的页面表现与想要得到的结果进行比较得到测试结论(断言)。那小程序中有没有一种方案能够按照这种思路实现自动化操作并提供页面信息用于断言呢?为了微信底层安全考虑,小程序环境一直比较封闭,留给开发者操作的余地很小,自动化操作基本无法实现,但5月底出现了miniprogram-automator(https://www.npmjs.com/package/miniprogram-automator)工具,给了小程序开发者希望。

二、缘遇-初试miniprogram-automator

基于miniprogram-automator的文档描述简单总结一下,当通过命令打开开发版微信开发者工具的自动化接口并连接自动化接口后,此工具可提供以下能力:

  • MiniProgram:获取小程序信息(页面堆栈、系统信息、页面内容),控制小程序(跳转页面、切换tab、调用方法)

  • Page:获取页面信息(路径、元素、数据、结构),控制页面(设置渲染数据、调用方法)

  • Element:获取元素信息(属性、样式、内容、位置),操控元素(点击、长按、调用方法)

所以小程序自动化控制的实现依赖于开发版小程序开发者工具以及miniprogram-automator工具。小程序开发者工具命令行用来打开指定自动化操作服务端口。(开发者工具版本需高于v1.02.1906042)。miniprogram-automator工具用来操作开发者工具中运行的小程序并获取所需的信息。对于测试需求可以结合jest框架进行测试用例的组织和断言。

不多废话,看完文档用一下:

Ø 调用开发者工具命令行打开项目与指定自动化操作服务端口

  
  
    
  1. PS D:\programs\内\微web开发者工具> ./cli.bat --auto D:\weApp\testMiniprogram --auto-port 9420

  2. Initializing...

  3. idePortFile: C:\Users\billcui\AppData\Local\微信开发者工具\User Data\Default\.ide

  4. starting ide...

  5. IDE server has started, listening on http://127.0.0.1:35510

  6. initialization finished

  7. Open project with automation enabled success D:\weApp\testMiniprogram

这一行命令需要注意的有

  1. 文档要求开发者工具版本号必须高于v1.02.1906042,最好是最新的内测版工具,我是在v1.03.1906062运行成功的;

  2. 运行这行命令之前需要先打开开发者工具菜单中的设置->安全设置->服务端口

  3. 自动化端口是独立于服务端口的(比如终端打印出的35510其实是服务端口),必须要看到Openprojectwithautomation enabled success D:\weApp\testMiniprogram这行提示才算是成功打开了自动化端口(9420)。

命令运行成功后,开发者工具会自动打开项目,并弹出提示

Ø npm i miniprogram-automator--save-dev安装SDK,创建test.js,代码中引入miniprogram-automator工具,连接自动化操作端口

  
  
    
  1. const automator = require('miniprogram-automator');


  2. const miniProgram = automator.connect({

  3. wsEndpoint: 'ws://localhost:9420',

  4. })

Ø 利用miniprogram-automator提供的接口操作小程序从首页重启并进行相关操作

  
  
    
  1. const automator = require('miniprogram-automator');


  2. const miniProgram = automator.connect({

  3. wsEndpoint: 'ws://localhost:9420',

  4. }).then(async miniProgram => {

  5. // 从首页重启

  6. const page = await miniProgram.reLaunch('/pages/index/index');

  7. // 从页面获取bottom-button组件

  8. const button = await page.$('bottom-button');

  9. // 打印出button的wxml信息

  10. console.log(await button.wxml());

  11. }).catch(e => {

  12. console.log('catch a error', e);

  13. });

Ø 利用miniprogram-automator获取操作后页面相关信息,利用jest进行组织和断言

  
  
    
  1. // index.spec.js

  2. const automator = require('miniprogram-automator');


  3. describe('课堂小程序自动化测试', () => {

  4. let miniProgram;

  5. // 运行测试前调用

  6. beforeAll(async () => {

  7. miniProgram = await automator.connect({

  8. wsEndpoint: 'ws://localhost:9420',

  9. });

  10. });

  11. // 运行测试后调用

  12. afterAll(() => {

  13. miniProgram.disconnect();

  14. });

  15. // 测试内容

  16. it('nohost检测', async () => {

  17. const page = await miniProgram.reLaunch('/pages/index/index');

  18. const nohostButton = await page.$('nohost');

  19. expect(nohostButton).toBeNull();

  20. });

  21. });

运行 jest index.spec.js, 如果页面中不存在nohost组件则测试通过,结果如图所示:

三、缘聚-自动化测试在课堂微信小程序中的应用

腾讯课堂微信小程序引入自动化测试主要是为了解决开发、预发布环境、正式环境需要反复多次打开用例课程页面,操作繁琐,耗费大量人力的问题。针对课堂小程序checklist,尽可能利用自动化测试程序完成测试验证,减少手动操作,也可以避免人为检测的遗漏。

利用miniprogram-automator工具和jest框架,自动化测试主要能力为按照指定顺序模拟打开指定页面、点击、滚动等操作和设置page的data渲染数据,然后对特定的页面结构、数据、组件属性等信息进行断言,判断是否符合预期。

下面以腾讯课堂微信小程序的课程详情页为例来详细说明在实际项目中如何实现自动化测试:

课程详情页的UI主要分为视频部分,详情部分以及底部的购买按钮,未购买课程时付费课程详情页表现如下:

假如对于未购买的无优惠活动的付费课程详情页的测试目标如下:

  1. 按钮应显示“立即购买”,点击购买按钮可跳转到支付页

  2. 点击试学按钮可正常播放试学视频

  3. 未购买课程时点击课程视频无法播放

实现这个测试,在 x.spec.js文件中首先需要要按照上文的步骤引入miniprogram-automator,在beforeAll中连接已经打开自动化端口的微信小程序项目。(这里不再重复代码,见上一章)下面直接看测试内容的代码。

1. 按钮显示和点击跳转支付页测试

  
  
    
  1. // 打开页面,通过url传参

  2. const page = await miniProgram.reLaunch(`/pages/course/course?cid=${commonPayCid}`);

  3. // 获取按钮组件信息

  4. const basicApplyButton = await page.$('.basic--buy');

  5. // 判断按钮显示内容

  6. expect(await basicApplyButton.wxml()).toContain('立即购买');

  7. // 模拟点击按钮

  8. await basicApplyButton.tap();

  9. // 等待页面跳转

  10. await page.waitFor(1500);

  11. // 获取当前页面路径

  12. const currentPage = await miniProgram.currentPage();

  13. // 判断跳转后路径是否正确

  14. expect(currentPage.path).toContain('pages/order/order');

  15. // 跳转回来

  16. await miniProgram.navigateBack();

目前miniprogram-automator提供了两种方法获取到页面中的组件:page.$()page.$$()

经过实验发现两者的selector支持通过组件名和类名选择组件,但对于自定义组件内部的结构,就不能直接这样拿到了。

课程详情页的底部按钮其实是一个自定义组件,并且还嵌套了子自定义组件,我们看一下底部按钮的wxml结构:

红色框框就是想要获取的目标,尝试一下直接通过 page.$('.bottom-btn')page.$('.buy')返回的都是undefined,那怎么获取呢?我们先来看看bottom-button内部是什么样子的。

  
  
    
  1. const basicApplyButton = await page.$('bottom-button');

  2. console.log(await basicApplyButton.wxml());

获取bottom-button并打印它的wxml字符串看一下:

  
  
    
  1. // 输出实际上是字符串,为了方便显示格式化了一下

  2. <view class="bottom-button--bottom-button-space" wx:nodeid="17">

  3. <view class="bottom-button--bottom-button-wrapper" wx:nodeid="261">

  4. <basic is="components/discount-button/components/basic/basic" wx:nodeid="262">

  5. <view wx:nodeid="263">

  6. <view class="basic--bottom-button-container" wx:nodeid="264">

  7. <view class="basic--bottom-btn basic--buy" wx:nodeid="265">立即购买</view>

  8. </view>

  9. </view>

  10. </basic>

  11. </view>

  12. </view>

发现了什么!小程序实际运行时,自定义组件内部的类名都加上了组件名前缀,再试试 page.$('.basic--buy')发现果然成功获取到了,所以虽然表面上miniprogram-automator只能操作和获取page中的内容,但自定义组件内部的结构实际上也是以某种方式存在于page中的。

接下来看一下跳转,可以直接获取到对应组件后调用 .tap()方法来模拟点击,这里需要注意的是,由于微信小程序开发者工具中点击打开新页面耗时较长,需要等待页面加载一会,不然接下来获取当前页面路径的时候页面还没跳转过去就拿不到不到新页面路径了。等待的时长可以根据经验给个稍大的比较安全的值。

2. 点击试学按钮可正常播放试学视频

  
  
    
  1. const player_video = await tapTcplayer(page, '.player-task');

  2. expect(await player_video.wxml()).toContain('video-current-time'); // 试学

由于微信开发者工具的限制,云点播会降级为tcplayer播放,tcplayer内部的核心组件其实是 <video>组件,wxml结构如下:

如何判断视频是否成功播放呢?

我们先按照上面的方法获取播放成功的video组件的wxml字符串看看

  
  
    
  1. "<video class="component-video-video--player_video" controls="" danmu-list="[]" initial-time="0" object-fit="contain" poster="https://10.url.cn/qqc..." src="http://113.96.98.148/vedu.tc.qq.com..." autoplay="" wx:nodeid="446"><div class="video-container" wx:nodeid="447"><div class="video-bar full" style="opacity: 1;" wx:nodeid="457"><div class="video-controls" wx:nodeid="458"><div class="video-control-button pause" wx:nodeid="459"><div parse-text-content="" class="video-current-time" wx:nodeid="460">00:02<div class="video-progress-container" wx:nodeid="462"><div class="video-progress" wx:nodeid="463"><div style="left: -21px;" class="video-ball" wx:nodeid="464"><div class="video-inner" wx:nodeid="465"><div parse-text-content="" class="video-duration" wx:nodeid="466">06:09<div class="video-fullscreen" wx:nodeid="468"><div style="z-index: -9999" class="video-danmu" wx:nodeid="453"></video>"

惊了!原生 <video>组件内部竟然是 <div> ,我们还可以注意到一个关键的class: video-current-time 内部数值为00:02,这不是当前播放进度吗?刚好可以用来判断视频有没有播放成功,就是它了!

对比发现播放失败时根本不会出现class为video-current-time的div,所以直接用是否包含video-current-time来判断了。

3. 未购买课程时点击课程视频无法播放

点击非试看课程时,无法播放视频。由于不播放视频时页面中只显示cover封面图,不attach <video>组件,所以直接用获取视频组件的结果进行toBeNull()判断即可。结合上面所有的代码如下:

  
  
    
  1. async function tapTcplayer(page, className = '.task-item') {

  2. const taskItem = await page.$(className);

  3. await taskItem.tap();

  4. await page.waitFor(3000);

  5. const playercover = await page.$('.player-cover');

  6. const player_video = await playercover.$('.component-video-video--player_video');

  7. return player_video;

  8. }

  9. it('付费课程详情页按钮显示、跳转、点播、试学功能测试', async () => {

  10. const page = await miniProgram.reLaunch(`/pages/course/course?cid=${commonPayCid}`);

  11. const basicApplyButton = await page.$('.basic--buy');

  12. expect(await basicApplyButton.wxml()).toContain('立即购买'); // 按钮显示

  13. await basicApplyButton.tap();

  14. await page.waitFor(1500);

  15. const currentPage = await miniProgram.currentPage();

  16. expect(currentPage.path).toContain('pages/order/order');

  17. await miniProgram.navigateBack();

  18. const player_video = await tapTcplayer(page);

  19. expect(player_video).toBeNull(); // 未报名不能播放视频

  20. const player_video_new = await tapTcplayer(page, '.player-task');

  21. expect(await player_video_new.wxml()).toContain('current'); // 试学

  22. }, 20000);

可以看到实际上先测试了播放课程功能,再测试了试学功能,这是为什么呢?

这是一个坑:由于播放课程失败时会有showModel弹窗提示,这个弹窗是不在wxml结构中的,无法用自动化控制工具点击关闭,实际测试中这个弹窗会阻塞下一个测试项的第一步:页面跳转,导致下一个测试项直接打不开页面导致失败,只能等待一段时间再跳转,所以直接把弹窗放在测试试学功能之前,就不会影响下一个测试项了。

还有一个需要注意的地方,在项目中,点击播放后5秒不触发进度刷新的方法就会上报视频播放失败,实际测试发现一般3秒即可正常播放,所以只等待3秒,3秒后未成功播放的视为播放失败。

最后,jest默认一个测试项的时长不能大于5秒,这项测试既有页面跳转又有视频播放,明显会超出5秒的限制,实际耗时约为15秒左右,所以修改时长限制为20000毫秒。

运行测试脚本结果如下:

目前实现的测试功能如下:

  • nohost检测

  • 首页数据拉取、显示、跳转测试

  • 付费课程详情页按钮显示、跳转、点播、试学功能测试

  • 优惠券按钮显示、领取功能测试

  • 限时优惠按钮显示测试

  • 免费课程详情页按钮显示、报名、点播功能测试

  • 分类页展示、跳转列表页、跳转详情页测试

checklist中功能测试的完成情况如下:完成度为65%

review点 自动化测试 备注
是否去除nohost插件 支持
首页是否正常显示 支持
pc首页小程序登陆是否正常 暂不 信息授权无法自动完成
安卓支付能力是否正常 暂不 webview内部无法获取信息
分类页是否正常显示 支持
是否可以正常登陆 暂不 信息授权无法自动完成
课程表是否正常展示,学习进度/直播状态是否正常显示 支持 待完善
课程详情页是否可以正常展示 支持
扫码/分享是否正常唤起小程序 暂不 开发者工具不支持
付费课直播是否可以正常播放(上云跟腾讯视频) 暂不 开发者工具不支持直播
免费课直播是否可以正常播放(上云跟腾讯视频) 暂不 开发者工具不支持直播
免费课录播是否可以正常播放(上云跟腾讯视频) 部分支持 开发者工具降级到tcplayer
付费课录播是否可以正常播放(上云跟腾讯视频) 部分支持 开发者工具降级到tcplayer
试学任务是否可以正常播放 支持
详情页视频是否正常播放 支持
营销工具相关显示是否正常 支持
是否能正常完成支付逻辑 暂不 webview内部无法获取信息
类目筛选是否正常 支持 待完善
是否可以正常搜索且列表显示正常 支持 待完善
本地加载耗时是否保持1s内 支持

四、缘续-遇到的问题与功能限制

  1. 获取页面中的组件只能采用 page.$()或 page.$$()方法,经尝试选择器仅支持组件名和类名。无法直接获取自定义组件内部组件元素,需要在类名前增加前缀。实际项目的页面中大量使用自定义组件,对于自定义组件内部的结构判断非常不方便,只能通过 wxml()方法将自定义组件内部结构打印出来才能确认内部的子组件的实际情况。且无法调用自定义组件内部的方法。

  2. Jest的snapshot功能对于结构相对固定的组件或页面是一种非常好的测试方式,但用起来有坑。在小程序中snapshot的对照内容通常是通过组件的wxml方法打印的字符串,但实际在运行时,wxml方法返回结果可能会不同,组件可能会被自动添加上wx:nodeid属性,但有时返回字符串中又不添加,会导致snapshot测试不通过。

  3. 目前只能在开发者工具环境下测试,导致直播功能无法测试且云点播会自动降级为腾讯视频点播,直播也无法测试。(工具更新后支持真机调试,应该有所改善)

  4. 登陆、扫码等功能无法测试,因为自动化控制工具无法扫描和点击授权弹窗。

  5. <web-view>组件获取不到任何内部信息,也无法自动化控制。

希望这些问题后续能够得到解决~~


关注我们

IMWeb 团队隶属腾讯公司,是国内最专业的前端团队之一。

我们专注前端领域多年,负责过 QQ 资料、QQ 注册、QQ 群等亿级业务。目前聚焦于在线教育领域,精心打磨 腾讯课堂、企鹅辅导 及 ABCMouse 三大产品。

社区官网

http://imweb.io/

加入我们

https://hr.tencent.com/position_detail.php?id=45616


扫码关注 IMWeb前端社区 公众号,获取最新前端好文

微博、掘金、Github、知乎可搜索 IMWebIMWeb团队 关注我们。


👇点击阅读原文获取更多参考资料


登录查看更多
0

相关内容

Automator是苹果公司为他们的Mac OS X系统开发的一款软件。 只要通过点击拖拽鼠标等操作就可以将一系列动作组合成一个工作流,从而帮助你自动的(可重复的)完成一些复杂的工作。Automator还能横跨很多不同种类的程序,包括:查找器、Safari网络浏览器、iCal、地址簿或者其他的一些程序。它还能和一些第三方的程序一起工作,如微软的Office、Adobe公司的Photoshop或者Pixelmator等。
【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
234+阅读 · 2020年5月21日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
7 款实用到哭的App,只说一遍
高效率工具搜罗
84+阅读 · 2019年4月30日
职人沙龙 | 走进小打卡,小程序技术实战交流
34个最优秀好用的Python开源框架
专知
9+阅读 · 2019年3月1日
爱奇艺基于AI的移动端自动化测试框架的设计
前端之巅
18+阅读 · 2019年2月27日
手把手教你用R语言制作网络爬虫机器人(一)
R语言中文社区
4+阅读 · 2019年1月26日
如何编写完美的 Python 命令行程序?
CSDN
5+阅读 · 2019年1月19日
十五条有用的Golang编程经验
CSDN大数据
5+阅读 · 2017年8月7日
33款可用来抓数据的开源爬虫软件工具 (推荐收藏)
数据科学浅谈
7+阅读 · 2017年7月29日
Clustered Object Detection in Aerial Images
Arxiv
5+阅读 · 2019年8月27日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
234+阅读 · 2020年5月21日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
224+阅读 · 2020年3月22日
算法与数据结构Python,369页pdf
专知会员服务
161+阅读 · 2020年3月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
相关资讯
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
7 款实用到哭的App,只说一遍
高效率工具搜罗
84+阅读 · 2019年4月30日
职人沙龙 | 走进小打卡,小程序技术实战交流
34个最优秀好用的Python开源框架
专知
9+阅读 · 2019年3月1日
爱奇艺基于AI的移动端自动化测试框架的设计
前端之巅
18+阅读 · 2019年2月27日
手把手教你用R语言制作网络爬虫机器人(一)
R语言中文社区
4+阅读 · 2019年1月26日
如何编写完美的 Python 命令行程序?
CSDN
5+阅读 · 2019年1月19日
十五条有用的Golang编程经验
CSDN大数据
5+阅读 · 2017年8月7日
33款可用来抓数据的开源爬虫软件工具 (推荐收藏)
数据科学浅谈
7+阅读 · 2017年7月29日
Top
微信扫码咨询专知VIP会员