小程序自动化测试的示例代码
背景
近期团队打算做一个小程序自动化测试的工具,期望能够做的业务人员操作一遍小程序后,自动还原之前的操作路径,并且捕获操作过程中发生的异常,以此来判断这次发布时候会影响小程序的基础功能。
上述描述看似简单,但是中间还是有些难点的,第一个难点就是如何在业务人员操作小程序的时候记录操作路径,第二个难点就是如何将记录的操作路径进行还原。
自动化SDK
如何将操作路径还原这个问题,当然首选官方提供的SDK:miniprogram-automator。
小程序自动化SDK为开发者提供了一套通过外部脚本操控小程序的方案,从而实现小程序自动化测试的目的。通过该SDK,你可以做到以下事情:
- 控制小程序跳转到指定页面
- 获取小程序页面数据
- 获取小程序页面元素状态
- 触发小程序元素绑定事件
- 往AppService注入代码片段
- 调用wx对象上任意接口
- ...
上面的描述都来自官方文档,建议阅读后面内容之前可以先看看官方文档,当然如果之前用过puppeteer,基本是无缝衔接。下面简单介绍下SDK的使用方式。
//引入sdk
constautomator=require('miniprogram-automator')
//启动微信开发者工具
automator.launch({
//微信开发者工具安装路径下的cli工具
//Windows下为安装路径下的cli.bat
//MacOS下为安装路径下的cli
cliPath:'path/to/cli',
//项目地址,即要运行的小程序的路径
projectPath:'path/to/project',
}).then(asyncminiProgram=>{//miniProgram为IDE启动后的实例
//启动小程序里的index页面
constpage=awaitminiProgram.reLaunch('/page/index/index')
//等待500ms
awaitpage.waitFor(500)
//获取页面元素
constelement=awaitpage.$('.main-btn')
//点击元素
awaitelement.tap()
//关闭IDE
awaitminiProgram.close()
})
有个地方需要提醒一下:使用SDK之前需要开启开发者工具的服务端口,要不然会启动失败。
捕获用户行为
有了还原操作路径的办法,接下来就要解决记录操作路径的难题了。
在小程序中,并不能像web中通过事件冒泡的方式在window中捕获所有的事件,好在小程序所以的页面和组件都必须通过Page、Component方法来包装,所以我们可以改写这两个方法,拦截传入的方法,并判断第一个参数是否为event对象,以此来捕获所有的事件。
//暂存原生方法
constoriginPage=Page
constoriginComponent=Component
//改写Page
Page=(params)=>{
constnames=Object.keys(params)
for(constnameofnames){
//进行方法拦截
if(typeofobj[name]==='function'){
params[name]=hookMethod(name,params[name],false)
}
}
originPage(params)
}
//改写Component
Component=(params)=>{
if(params.methods){
const{methods}=params
constnames=Object.keys(methods)
for(constnameofnames){
//进行方法拦截
if(typeofmethods[name]==='function'){
methods[name]=hookMethod(name,methods[name],true)
}
}
}
originComponent(params)
}
consthookMethod=(name,method,isComponent)=>{
returnfunction(...args){
const[evt]=args//取出第一个参数
//判断是否为event对象
if(evt&&evt.target&&evt.type){
//记录用户行为
}
returnmethod.apply(this,args)
}
}
这里的代码只是代理了所有的事件方法,并不能用来还原用户的行为,要还原用户行为还必须知道该事件类型是否是需要的,比如点击、长按、输入。
constevtTypes=[
'tap',//点击
'input',//输入
'confirm',//回车
'longpress'//长按
]
consthookMethod=(name,method)=>{
returnfunction(...args){
const[evt]=args//取出第一个参数
//判断是否为event对象
if(
evt&&evt.target&&evt.type&&
evtTypes.includes(evt.type)//判断事件类型
){
//记录用户行为
}
returnmethod.apply(this,args)
}
}
确定事件类型之后,还需要明确点击的元素到底是哪个,但是小程序里面比较坑的地方就是,event对象的target属性中,并没有元素的类名,但是可以获取元素的dataset。
为了准确的获取元素,我们需要在构建中增加一个步骤,修改wxml文件,将所以元素的class属性复制一份到data-className。
但是获取到class之后,又会有另一个坑,小程序的自动化测试工具并不能直接获取页面里自定义组件中的元素,必须先获取自定义组件。
{{text}}
//如果直接查找.toast-close会得到null
constelement=awaitpage.$('.toast-close')
element.tap()//Error!
//必须先通过自定义组件的tagName找到自定义组件
//再从自定义组件中通过className查找对应元素
constelement=awaitpage.$('toast.toast-close')
element.tap()
所以我们在构建操作的时候,还需要为元素插入tagName。
现在我们可以继续愉快的记录用户行为了。
//记录用户行为的数组
constactions=[];
//添加用户行为
constaddAction=(type,query,value='')=>{
actions.push({
time:Date.now(),
type,
query,
value
})
}
//代理事件方法
consthookMethod=(name,method,isComponent)=>{
returnfunction(...args){
const[evt]=args//取出第一个参数
//判断是否为event对象
if(
evt&&evt.target&&evt.type&&
evtTypes.includes(evt.type)//判断事件类型
){
const{type,target,detail}=evt
const{id,dataset={}}=target
const{className=''}=dataset
const{value=''}=detail//input事件触发时,输入框的值
//记录用户行为
letquery=''
if(isComponent){
//如果是组件内的方法,需要获取当前组件的tagName
query=`${this.dataset.tagName}`
}
if(id){
//id存在,则直接通过id查找元素
query+=id
}else{
//id不存在,才通过className查找元素
query+=className
}
addAction(type,query,value)
}
returnmethod.apply(this,args)
}
}
到这里已经记录了用户所有的点击、输入、回车相关的操作,但是还有一个滚动屏幕的操作还没记录。这里可以直接监听Page的onPageScroll。
//记录用户行为的数组
constactions=[];
//添加用户行为
constaddAction=(type,query,value='')=>{
if(type==='scroll'||type==='input'){
//如果上一次行为也是滚动或输入,则重置value即可
constlast=this.actions[this.actions.length-1]
if(last&&last.type===type){
last.value=value
last.time=Date.now()
return
}
}
actions.push({
time:Date.now(),
type,
query,
value
})
}
Page=(params)=>{
constnames=Object.keys(params)
for(constnameofnames){
//进行方法拦截
if(typeofobj[name]==='function'){
params[name]=hookMethod(name,params[name],false)
}
}
const{onPageScroll}=params
//拦截滚动事件
params.onPageScroll=function(...args){
const[evt]=args
const{scrollTop}=evt
addAction('scroll','',scrollTop)
onPageScroll.apply(this,args)
}
originPage(params)
}
这里有个优化点,就是滚动操作记录的时候,可以判断一下上次操作是否也为滚动操作,如果是同一个操作,则只需要修改一下滚动距离即可,以为两次滚动可以一步到位。同理,输入事件也是,输入的值也可以一步到位。
还原用户行为
用户操作完毕后,可以在控制台输出用户行为的json文本,把json文本复制出来后,就可以通过自动化工具运行了。
//引入sdk
constautomator=require('miniprogram-automator')
//用户操作行为
constactions=[
{type:'tap',query:'goods.title',value:'',time:1596965650000},
{type:'scroll',query:'',value:560,time:1596965710680},
{type:'tap',query:'gotoTop',value:'',time:1596965770000}
]
//启动微信开发者工具
automator.launch({
projectPath:'path/to/project',
}).then(asyncminiProgram=>{
letpage=awaitminiProgram.reLaunch('/page/index/index')
letprevTime
for(constactionofactions){
const{type,query,value,time}=action
if(prevTime){
//计算两次操作之间的等待时间
awaitpage.waitFor(time-prevTime)
}
//重置上次操作时间
prevTime=time
//获取当前页面实例
page=awaitminiProgram.currentPage()
switch(type){
case'tap':
constelement=awaitpage.$(query)
awaitelement.tap()
break;
case'input':
constelement=awaitpage.$(query)
awaitelement.input(value)
break;
case'confirm':
constelement=awaitpage.$(query)
awaitelement.trigger('confirm',{value});
break;
case'scroll':
awaitminiProgram.pageScrollTo(value)
break;
}
//每次操作结束后,等待5s,防止页面跳转过程中,后面的操作找不到页面
awaitpage.waitFor(5000)
}
//关闭IDE
awaitminiProgram.close()
})
这里只是简单的还原了用户的操作行为,实际运行过程中,还会涉及到网络请求和localstorage的mock,这里不再展开讲述。同时,我们还可以接入jest工具,更加方便用例的编写。
总结
看似很难的需求,只要用心去发掘,总能找到对应的解决办法。另外微信小程序的自动化工具真的有很多坑,遇到问题可以先到小程序社区去找找,大部分坑都有前人踩过,还有一些一时无法解决的问题只能想其他办法来规避。最后祝愿天下无bug。
到此这篇关于小程序自动化测试的示例代码的文章就介绍到这了,更多相关小程序自动化测试内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。