【技术分享】基于 Audits 自动生成小程序质量评估报告
作者:吴甜,腾讯云高级工程师
近年来,随着微信生态不断完善,小程序已成为金融、政务、零售等多个行业加速数字化转型的重要载体,以小程序为载体的多元营销生态日渐成熟。往往需要多团队协助开发,还未上线便带来了以下几个问题:
多人协作开发,开发人员技术参差不齐,如何保证交付的小程序质量?
体验小程序时,发现页面很卡,用户体验差,需要研发耗费大量时间进行问题排查。优化之后,只能作用一小段时间,随着业务迭代,整体质量又垮掉了。
小程序性能差,体验不行,那具体是什么问题导致的?
面对这种情况,我们需要对业务所承接的小程序质量进行全方位的评估,并查找出具体的问题点,然后对症下药,帮助我们持续提升小程序的交付质量。
对于小程序质量评估,市面上也有很多优秀的方案,这里主要介绍三款常用的工具。
1. 微信广告页面评估工具。
微信广告官方提供的页面评估工具,在投放前可对指定页面进行质量评估,并给出改善建议,每次检测预估时间在10-20分钟。体验地址:https://ad.weixin.qq.com/weapptest.html。
[点击查看大图]
微信广告页面评估工具每次测试任务只能关联一个页面,并且检查出来的问题不会给出具体的解决方案,例如遇到下列的问题点:
指标:避免setData数据冗余 【建议】setData操作会引起框架处理一些渲染界面相关的工作,一个未绑定的变量意味着与界面渲染无关,传入setData会造成不必要的性能消耗。
那究竟是哪个变量与渲染无关呢?这排查起来简直精疲力尽。(注意: 创建测试计划时,需要填写小程序 ID,这里并不是 AppID, 而是小程序的原始 ID)
2. MiniTest
MiniTest 是一套由微信测试团队自主研发的、为小程序提供自动化测试的服务,可以帮助开发者简单快捷地实现对小程序进行 UI 自动化、性能和 Monkey 测试。MiniTest 平台地址为:https://minitest.weixin.qq.com。
使用该平台,我们对小程序进行了一次 Monkey 测试,最终会产出一份测试详情的报告:
[点击查看大图]
这份性能报告主要是集中在真机机器的 CPU、内存水位,对于我们定位具体的细节问题点帮助有限。
3. Audits
Audits (Google 开源的一个自动化测试工具)是一项给小程序体验评分的功能,它可以在小程序运行过程中实时检查,分析并定位出一些可能导致体验较差的功能,以及给出一些优化建议。
[点击查看大图]
使用 Audits 进行小程序优化,主要可以分为以下四个步骤:
我们在业务中,按照上面的模式来跑测,优化小程序遇到了下面几个问题:
不同的开发者由于本地机器性能、网络情况的不同,运行出来的体验报告可能不一致,这样就会使得我们每次测出来的报告会具有较大的不确定性。
运行 Audits 时,可以看到面板上面有这样的提示:“请自行操作小程序,尽可能的覆盖到所有页面“。部分小程序有 100+ 页面,单纯靠人工进行页面遍历,操作了一次耗费了 20-30 分钟。
当我们修改了部分问题时,想要看一下小程序评分有没有提升,这时候又需要 20+ 分钟的手动操作,这确实让人难以接受。
根据上面的技术调研,Audits 是比较符合我们的业务场景诉求,能够帮助我们进行小程序打分,并给出小程序存在的具体问题。只是在使用过程中,操作起来存在很多不爽的地方。为了在业务中使用 Audits 更加顺手,萌生了一个大胆的想法。
我们可以使用远程 MAC 开发机来运行小程序开发者工具,这样能保证运行环境(机器性能、网络情况)的一致性。通过编写自动化测试脚本,驱动小程序进行自动页面遍历,页面遍历完成后,生成 Audits 评分报告。
想要对小程序进行自动化测试,经常会接触到两款工具: Automator 和 Minium。两者对比下来,主要有以下差异点:
Minium 的定位是自动化测试框架,而 Automator 的定位是自动化测试工具,相比 Automator,Minium 具有以下优势:
1. 支持所有 Automator 功能。
2. 跨平台的产品(IDE,Android和IOS)支持真机测试。
3. 支持往 AppSerive 注入代码片段执行。
4. 支持 Mock、Hook wx 对象上的接口。
5. 支持截屏、断言并导出测试报告。
6. 支持账号池管理,IDE 登录无需用户扫码。
7. ...
因此最终我们选用了功能更加完备的 Minium 来编写小程序自动化用例,依托其现有的 IDE 登录、IDE 跑测、账号管理能力,为我们节约很多的开发时间。
方案架构图:
[点击查看大图]
方案流程图:
[点击查看大图]
1. 暴力遍历
[点击查看大图]
原理是通过 self.app.get_all_pages_path() 可以获取到小程序的所有页面路径,方法的底层其实是访问了 __wxConfig.pages,拿到小程序页面路径列表后,依次遍历列表进行页面访问。这样的做法虽然简单,但是却带来了一些问题,最为明显的就是我们遍历的时候没有携带页面参数,缺少了页面参数会使得页面报错,严重的还会造成内存泄漏。
2. 预置页面路径
通过提前设置好页面路径并携带好对应的参数,这样就能解决参数丢失的问题。常规做法可以在自动化脚本中写固定的 url 列表,如果路径或者参数需要变更,就需要手动修改测试脚本,这样成本较高。我们的做法是为不同的 AppID 配置好需要遍历的 url 列表,如果需要变更路径或者修改参数,修改配置即可。
后台URL配置:
[点击查看大图]
拉取后端配置的 urls 进行页面遍历:
这种做法我们运行了一段时间后,发现存在以下弊端:
新增或者删除页面后,路径往往更新不及时。
部分页面携带的参数具有一定的时效性。举个栗子:如果一个活动到了某个时间需要下线,下线后我们携带了之前的活动 ID 去访问, 可能会访问到错误的页面。
部分页面入口是通过后端接口配置的,当配置变化时,会导致线上可访问页面新增或者减少,通过预置 url 列表无法动态感知变化。
结论:通过预置 url 列表及参数的方案适合页面固定,url 参数变化较小的场景。url 列表需要我们进行定期维护和更新,保证尽可能多的覆盖所有页面,随之带来的问题便是 后期维护成本提高,当小程序数量非常多时,问题尤为明显。
3. 智能遍历
智能遍历是一种更加聪明的遍历方法,从小程序主页开始,模拟真实用户操作,进行控件点击,进行页面深度遍历,相比 url 他的优势主要是后期零维护。
想要进行模拟点击操作,首先我们比较关心的问题便是点击哪里?例如下面这个页面:
页面中可点击元素包含了:banner1、banner2、banner3(隐藏在右边,需要滚动才会展示),Button1 和 Button2。如何才能识别出哪些控件是可以点击的哪些是不能够点击的?
3.1 机器学习识别可点击控件
通过引入机器学习,对页面进行建模,当样本量足够大的时候,能够较为准确的识别可点击控件。但是很多时候,对于一个页面哪些地方可以点击,哪些地方不能点击,连我们自己都不太清楚,需要测试一下才知道,何况是机器呢?例如上图中的 Text 文案其实是不能点击的,但是在其他场景下它可能又是可以点击的。
3.2 Monkey
接触过 APP 测试的应该都了解 Monkey,里面有随机点击的功能,无需关注控件位置。随机点击坐标能否命中控件,具有很大的不确定性。例如上述智能遍历示例中的 banner,如果是在视窗范围外,就会一直命中不到点击。
下图是我们使用 Minitest 对小程序进行 Monkey 随机点击测试,可以发现其点击位置随机分布,并没有命中真实的点击控件。有人可能会问,如果把点击位置拆分成非常小的矩阵区域,这样就能覆盖整个屏幕了,但是随之带来的问题就是造成了很多无效点击,浪费跑测时间。
[点击查看大图]
页面覆盖率只达到了 8.33%
[点击查看大图]
3.3 基于源码分析进行控件识别
由于上诉两种方案效果差强人意,急需寻找一种高效的识别方法。由于跑测的时候是可以拿到项目源代码的,因此考虑是否可以将源代码作为切入点。
在小程序中,要实现点击事件绑定,通常是使用到 bindtap、bind:tap 、catchtap 、catch:tap。例如:
<button bindtap="toHome">我是按钮可以点击</button>
<navigator url="xxx" />
可以将绑定 bindtap、bind:tap、catchtap 、
catch:tap 属性的节点或者是 navigator 标签识别为可点击控件。
原本以为通过小程序的标签选择器、属性选择器 就能筛选出可点击节点,然而遭遇了现实的毒打。
通过微信小程序提供的API - wx.createSelectorQuery 可以获取到节点信息,但返回的节点只会包含常规属性值。要的在文档中明确做了标注事件绑定的属性值不可获取(https://developers.weixin.qq.com/miniprogram/dev/api/wxml/NodesRef.fields.html)。
[点击查看大图]
条条大路通罗马,我们可以另辟蹊径,虽然无法通过获取属性选择器的值来判断可点击控件。但是小程序对于 class 选择器是支持的,我们能否将属性选择转化到 class 选择器上?
目标代码:
<button bindtap="toHome">我是按钮可以点击</button>
转换后的代码:
<button class="xxx-bindtap" bindtap="toHome">我是按钮可以点击</button>
为所有绑定了 bindtap 属性的控件 在 class 属性上面添加 xxx-bindtap 属性,然后我们通过 class 选择器便可以定位到对应的控件。
tap_btns = self.page.get_elements(".xxx-bindtap")
小程序代码绑定了点击事件的节点特别多,如果全部通过手动来添加,无疑是无法实现的。因此需要考虑使用工程化手段来处理可点击控件打标问题,接下来会讲到具体方案。
4. 控件批量打标
首先要明确一点,可点击控件打标只会在 Audits 跑测评分时使用,不会影响到生产环境。wxml 节点解析原理和解析 html 一致,因此可以使用 htmlparser2 来将 DSL -> AST。大家可以访问在线工具:https://astexplorer.net/。
[点击查看大图]
整体思路如下:
通过 htmlparser2 可以将 wxml 转换成 AST 然后遍历 AST 找到 属性值 bindtap、bind:tap 、catchtap 、catch:tap 不为空的节点 并修改其 class 属性值, 最终将修改后的 AST 转换成 DSL 获得打标后的 wxml 文件。相关打标 demo 代码参考示例:https://github.com/wutianSweet/minitest-audits/blob/master/src/tasks/mark-wxml.js 。
5.如何做页面深度遍历
[点击查看大图]
step1:访问页面
首先将小程序启动页面作为遍历的第一个页面:
first_page = self.app.get_current_page()
step2: 获取当前页面打标控件
获取当前访问页面中被打标的控件 可以使用 Minium 提供的方法进行选中:
tap_btns = self.page.get_elements(".xxx-bindtap")
控件选择档可以参考:https://minitest.weixin.qq.com/#/minium/Python/api/Page?id=get_elements。
step3: 遍历当前页面控件 并触发 tap
for current_btn in tap_btns:
current_btn.tap()
step4: 拦截页面跳转
其实最先采用的方案是页面发生跳转之后,通过调用 wx.navigateBack 使页面返回去,让整个页面控件可以全部遍历完。但是这样做在实践过程中发现两个问题:
为了获取链接,触发了一次页面跳转和页面渲染,会带来很大的消耗,且影响遍历速度。
很多页面跳转之后,就不能再返回回去了。
为此采用了通过 mock 微信原生跳转方法,使得在不发生页面跳转的前提下就能快速获取点击触发的 URL。
实现页面跳转拦截的核心代码如下:
[点击查看大图]
除了 mock 方法外,还需要将 拦截的 url 暴露给外部环境,两边环境不一致,这里需要往 AppService 中注入方法,将小程序原生跳转方法的 url 参数 暴露给外部的自动化测试脚本。
step5: 取消拦截 跳转新页面
当前页面打标控件点击触发完毕后,需要对下一个页面进行打标控件遍历,由于上一个步骤我们对跳转方法进行了 mock,想要进行页面跳转 需要将 mock 的方法 进行 reset 操作,可以使用 self.app.restore_wx_method,需要访问的页面用一个队列来控制,已经被访问过的页面不会再被二次访问。为了提升遍历效率, 相同路径 不同页面参数的 会被认为是一个页面。/pages/home?id=1 被访问后, /pages/home?id=2 不会再被遍历到。
6. 提升页面覆盖率
使用智能遍历的方案,最初在业务中使用,页面覆盖率只有 28%。
[点击查看大图]
经过多轮优化 将 覆盖率提升到了 77%。
[点击查看大图]
接下来会介绍下 为了提升页面覆盖率,都做了哪些事情:主要是分为常规优化和非常规优化。
常规优化
智能遍历主要是基于正常的用户访问路径进行操作,用户访问页面时,需要使数据尽可能丰富,例如遍历列表的时候,列表是空的,那必然是无法访问到详情页面。因此我们要格外注意用户权限、数据丰富性。基于数据的完备性,用户的可访问页面也会越来越多。
非常规优化
通过对没有覆盖到的 54 个页面进行分析,发现其中大部分页面都是入口比较深的,部分还是用于投放的页面,想要把场景覆盖全面非常困难。深入分析页面时发现 没有覆盖的页面中,有接近一半的页面是不需要携带参数就能进行访问,如果遍历的时候能够遍历这部分页面,整体的页面覆盖率就能上去。
哪些页面不需要 页面参数就能访问?
[点击查看大图]
例如上面的代码需要判断页面参数有没有使用到,只需要判断 onLoad 方法中的参数有没有被使用到,通过 @babel/parser可以将上诉代码转换成 AST。
通过@babel/traverse 可以对 AST 进行遍历,查找到 onLoad 方法,然后对函数的方法是否有被引用到进行判断即可(当然这种判断方式并不是 100% 准确,因为还可能依赖了全局变量或者是消息通知等,现目前通过截图判断误判页面 加入黑名单,后期会优化分析算法,提升识别率)
通过对静态代码进行分析,便能将不需要携带参数的页面路径计算出来,最终将 小程序页面覆盖度从 28% 提升到 77%。
7. 截图信息采集 异常分析
发布的时候,很难将所有页面都检查一遍 。有了截图分析的功能,可以很直观的发现哪些页面存在问题。例如下面的页面截图,很容易看出页面存在的问题,这样使得我们每次发布更加有底气。
[点击查看大图]
目前一期功能只是进行图像信息采集,后期会引入图像识别能力,对页面白屏,楼层错乱,服务异常 等 CASE 进行预警。
8. 质检流水线搭建
为了方便业务中新小程序接入,提供了质检流水线,只需要在小程序发布流水线中运行子流水线即可,每次发布体验版和正式版本 就会自动进行质检任务。
[点击查看大图]
智能遍历脚本驱动小程序遍历录制了一个视频,给大家展示下效果:
录制回放
上面已经介绍了固定 URL 遍历 和智能遍历两种模式,但是这两种模式是有局限性的,无法覆盖强依赖步骤的场景,例如表单填写、注册流程等,只有在上一个页面填写完表单信息,才能进入到下一个环节,因此需要介绍第三种补充模式 -- 录制回放。
小程序开发者工具现已支持录制、回放相关功能(https://developers.weixin.qq.com/miniprogram/dev/devtools/auto/record.html),用户的操作记录会通过 json 文件记录下来。我们对用户信息编辑的场景进行了录制,下面的视频是回放的效果展示:
MiniTest 通过录制的 json 文件,可以将其转换成 Minium 脚本,上述录制 json 转换后的脚本为:
[点击查看大图]
通过录制回放,我们能够对强依赖表单输入的流程进行补充和覆盖,不过目前在能业务中使用此类的场景并不是很多,主要还是录制回放用例需要定期维护、更新,有一定的成本。
目前我们将小程序质检能力集成到了统一的管理端,管理端具备以下能力。
1. 小程序列表整体性能展示:
[点击查看大图]
2. 质检任务列表:
3. 质检报告:
[点击查看大图]
4. 按照页面维度进行问题分析,方便指派处理人:
[点击查看大图]
5. 遍历页面截图:
[点击查看大图]
质检常见问题
通过对接入的多个小程序进行了问题分析,发现了一些高频问题会导致小程序质量下降,供大家参考。
1. 合理利用图片大小。
根据实际区域合理使用图片尺寸,避免出现显示区域只有 40x40 但是原图使用了 700x700。 这样对网络资源是一种很大的浪费,原则上尽量使用一倍图,视觉要求高的场景不超过二倍图。
2. 图片请求并发控制。
避免在短时间发送大量图片请求,不要在一进入页面的时候就将不在视窗范围的图片全部都拉取下来,建议开启图片懒加载策略。
3. 保持图片宽高比。
当图片宽高写死后,需要 check 下和原图比例是否一致,不要出现拉伸变形。
4. 可点击区域太小。
相信大多数开发者都试过把点击事件绑定在很小的 icon 上,这样会使得用户点击按钮的时候经常会点不中,需要多次点击。这个问题在小屏手机上面会更加明显,点击区域宽、高不能低于 20px。
5. 避免 setData 数据冗余。
与 WXML 渲染无关的数据不要放到 setData 中 避免性能开销,可以挂载到 this 上。
Audits 专注于小程序开发过程中的质量评估,您可以结合腾讯云前端性能监控(RUM)实现小程序从开发、交付到上线的全程质量评估,有效提升用户体验。您可以点击文末「阅读原文」立即了解 RUM。
更多 RUM 相关文章:
1. 小程序评分规则:
https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html
2. Minium 使用文档:
https://developers.weixin.qq.com/miniprogram/dev/framework/audits/performance.html