微信小程序自动化测试实践总结
一、缘起-为什么要进行小程序自动化测试微信小程序生态日益完善,很多小程序项目页面越来越多,结构越来越复杂,业务逻辑也更加多样。以腾讯课堂小程序为例,目前腾讯课堂小程序部分页面结构和不同业务场景下的表现如下图所示: 可以看到在核心功能上主要页面对于不同业务场景有众多不同的表现,因此在开发与发布的过程中需要手动验证大量测试用例以保证小程序按预期表现运行,善于利用工具的程序员当然会想:这种重复的工作能不能交给程序自动进行呢? web开发中对于这类测试问题已经有了很多自动化解决方案比如Selenium、Puppeteer,思路大体相同,都是让浏览器按照指定顺序自动在页面上完成点击、输入等操作,再将操作后的页面表现与想要得到的结果进行比较得到测试结论(断言)。那小程序中有没有一种方案能够按照这种思路实现自动化操作并提供页面信息用于断言呢?为了微信底层安全考虑,小程序环境一直比较封闭,留给开发者操作的余地很小,自动化操作基本无法实现,但5月底出现了miniprogram-automator工具,给了小程序开发者希望。 二、缘遇-初试miniprogram-automator基于miniprogram-automator的文档描述简单总结一下,当通过命令打开开发版微信开发者工具的自动化接口并连接自动化接口后,此工具可提供以下能力:
所以小程序自动化控制的实现依赖于开发版小程序开发者工具以及miniprogram-automator工具。小程序开发者工具命令行用来打开指定自动化操作服务端口。(开发者工具版本需高于v1.02.1906042)。miniprogram-automator工具用来操作开发者工具中运行的小程序并获取所需的信息。对于测试需求可以结合jest框架进行测试用例的组织和断言。 不多废话,看完文档用一下: ? 调用开发者工具命令行打开项目与指定自动化操作服务端口
这一行命令需要注意的有:
命令运行成功后,开发者工具会自动打开项目,并弹出提示 ??
? 利用miniprogram-automator提供的接口操作小程序从首页重启并进行相关操作 );
// 从页面获取bottom-button组件
const button = await page.$('bottom-button');
// 打印出button的wxml信息
console.log(await button.wxml());
}).catch(e => {
console.log('catch a error',e);
});
? 利用miniprogram-automator获取操作后页面相关信息,利用jest进行组织和断言 // index.spec.js
'miniprogram-automator');
describe('课堂小程序自动化测试',() => {
let miniProgram;
// 运行测试前调用
beforeAll(async () => {
miniProgram = await automator.connect({
wsEndpoint: // 运行测试后调用
afterAll(() => {
miniProgram.disconnect();
});
// 测试内容
it('nohost检测',async () => {
'/pages/index/index');
const nohostButton = 'nohost');
expect(nohostButton).toBeNull();
});
});
运行 三、缘聚-自动化测试在课堂微信小程序中的应用腾讯课堂微信小程序引入自动化测试主要是为了解决开发、预发布环境、正式环境需要反复多次打开用例课程页面,操作繁琐,耗费大量人力的问题。针对课堂小程序checklist,尽可能利用自动化测试程序完成测试验证,减少手动操作,也可以避免人为检测的遗漏。 利用miniprogram-automator工具和jest框架,自动化测试主要能力为按照指定顺序模拟打开指定页面、点击、滚动等操作和设置page的data渲染数据,然后对特定的页面结构、数据、组件属性等信息进行断言,判断是否符合预期。 下面以腾讯课堂微信小程序的课程详情页为例来详细说明在实际项目中如何实现自动化测试: 课程详情页的UI主要分为视频部分,详情部分以及底部的购买按钮,未购买课程时付费课程详情页表现如下: 假如对于未购买的无优惠活动的付费课程详情页的测试目标如下:
实现这个测试,在
// 打开页面,通过url传参
`/pages/course/course?cid=${commonPayCid}`);
// 获取按钮组件信息
const basicApplyButton = '.basic--buy');
// 判断按钮显示内容
expect(await basicApplyButton.wxml()).toContain('立即购买');
// 模拟点击按钮
await basicApplyButton.tap();
// 等待页面跳转
await page.waitFor(1500);
// 获取当前页面路径
const currentPage = await miniProgram.currentPage();
// 判断跳转后路径是否正确
expect(currentPage.path).toContain('pages/order/order');
// 跳转回来
await miniProgram.navigateBack();
目前miniprogram-automator提供了两种方法获取到页面中的组件: 经过实验发现两者的selector支持通过组件名和类名选择组件,但对于自定义组件内部的结构,就不能直接这样拿到了。 课程详情页的底部按钮其实是一个自定义组件,并且还嵌套了子自定义组件,我们看一下底部按钮的wxml结构: 红色框框就是想要获取的目标,尝试一下直接通过 );
await basicApplyButton.wxml());
获取bottom-button并打印它的wxml字符串看一下:
发现了什么!小程序实际运行时,自定义组件内部的类名都加上了组件名前缀,再试试 接下来看一下跳转,可以直接获取到对应组件后调用
);
expect(await player_video.wxml()).toContain('video-current-time'); // 试学
由于微信开发者工具的限制,云点播会降级为tcplayer播放,tcplayer内部的核心组件其实是 如何判断视频是否成功播放呢? 我们先按照上面的方法获取播放成功的video组件的wxml字符串看看 "<video "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/AtmkzyWCuq..." autoplay="446"><div "video-container" "447"><"video-bar full" style="opacity: 1;" "457"><"video-controls" "458"><"video-control-button pause" "459"><parse-text-content="video-current-time" "460">00:02<"video-progress-container" "462"><"video-progress" "463"><"left: -21px;" "video-ball" "464"><"video-inner" "465"><"video-duration" "466">06:09<"video-fullscreen" "468"><"z-index: -9999" "video-danmu" "453"></video>"
惊了!原生 对比发现播放失败时根本不会出现class为video-current-time的div,所以直接用是否包含video-current-time来判断了。
点击非试看课程时,无法播放视频。由于不播放视频时页面中只显示cover封面图,不attatch async function tapTcplayer(page,className = '.task-item') { const taskItem = await page.$(className); await taskItem.tap(); await page.waitFor(3000); const playercover = await page.$('.player-cover'); const player_video = await playercover.$('.component-video-video--player_video'); return player_video; } it('付费课程详情页按钮显示、跳转、点播、试学功能测试',async () => { const page = await miniProgram.reLaunch(`/pages/course/course?cid=${commonPayCid}`); const basicApplyButton = await page.$('.basic--buy'); expect(await basicApplyButton.wxml()).toContain('立即购买'); // 按钮显示 await basicApplyButton.tap(); await page.waitFor(1500); const currentPage = await miniProgram.currentPage(); expect(currentPage.path).toContain('pages/order/order'); await miniProgram.navigateBack(); const player_video = await tapTcplayer(page); expect(player_video).toBeNull(); // 未报名不能播放视频 const player_video_new = await tapTcplayer(page,'.player-task'); expect(await player_video_new.wxml()).toContain('current'); // 试学 },20000); 可以看到实际上先测试了播放课程功能,再测试了试学功能,这是为什么呢? 这是一个坑:由于播放课程失败时会有showModel弹窗提示,这个弹窗是不在wxml结构中的,无法用自动化控制工具点击关闭,实际测试中这个弹窗会阻塞下一个测试项的第一步:页面跳转,导致下一个测试项直接打不开页面导致失败,只能等待一段时间再跳转,所以直接把弹窗放在测试试学功能之前,就不会影响下一个测试项了。 还有一个需要注意的地方,在项目中,点击播放后5秒不触发进度刷新的方法就会上报视频播放失败,实际测试发现一般3秒即可正常播放,所以只等待3秒,3秒后未成功播放的视为播放失败。 最后,jest默认一个测试项的时长不能大于5秒,这项测试既有页面跳转又有视频播放,明显会超出5秒的限制,实际耗时约为15秒左右,所以修改时长限制为20000毫秒。 运行测试脚本结果如下: 目前实现的测试功能如下:
Checklist中功能测试的完成情况如下:完成度为65%
四、缘续-遇到的问题与功能限制
希望这些问题后续能够得到解决~~ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |