小程序自动化测试的示例代码

目录

背景

自动化 SDK

还原用户行为

总结


背景

上述描述看似简单,但是中间还是有些难点的,第一个难点就是如何在业务人员操作小程序的时候记录操作路径,第二个难点就是如何将记录的操作路径进行还原。

自动化 SDK

如何将操作路径还原这个问题,当然首选官方提供的 SDK: miniprogram-automator 。

小程序自动化 SDK 为开发者提供了一套通过外部脚本操控小程序的方案,从而实现小程序自动化测试的目的。通过该 SDK,你可以做到以下事情:

  • 控制小程序跳转到指定页面
  • 获取小程序页面数据
  • 获取小程序页面元素状态
  • 触发小程序元素绑定事件
  • 往 AppService 注入代码片段
  • 调用 wx 对象上任意接口
  • ...

上面的描述都来自官方文档,建议阅读后面内容之前可以先看看官方文档 ,当然如果之前用过 puppeteer ,基本是无缝衔接。下面简单介绍下 SDK 的使用方式。

// 引入sdk
const automator = require('miniprogram-automator')// 启动微信开发者工具
automator.launch({// 微信开发者工具安装路径下的 cli 工具// Windows下为安装路径下的 cli.bat// MacOS下为安装路径下的 clicliPath: 'path/to/cli',// 项目地址,即要运行的小程序的路径projectPath: 'path/to/project',
}).then(async miniProgram => { // miniProgram 为 IDE 启动后的实例// 启动小程序里的 index 页面const page = await miniProgram.reLaunch('/page/index/index')// 等待 500 msawait page.waitFor(500)// 获取页面元素const element = await page.$('.main-btn')// 点击元素await element.tap()// 关闭 IDEawait miniProgram.close()
})

有个地方需要提醒一下:使用 SDK 之前需要开启开发者工具的服务端口,要不然会启动失败。


捕获用户行为

有了还原操作路径的办法,接下来就要解决记录操作路径的难题了。

在小程序中,并不能像 web 中通过事件冒泡的方式在 window 中捕获所有的事件,好在小程序所以的页面和组件都必须通过 Page 、 Component 方法来包装,所以我们可以改写这两个方法,拦截传入的方法,并判断第一个参数是否为 event 对象,以此来捕获所有的事件。

// 暂存原生方法
const originPage = Page
const originComponent = Component// 改写 Page
Page = (params) => {const names = Object.keys(params)for (const name of names) {// 进行方法拦截if (typeof obj[name] === 'function') {params[name] = hookMethod(name, params[name], false)}}originPage(params)
}
// 改写 Component
Component = (params) => {if (params.methods) {const { methods } = paramsconst names = Object.keys(methods)for (const name of names) {// 进行方法拦截if (typeof methods[name] === 'function') {methods[name] = hookMethod(name, methods[name], true)}}}originComponent(params)
}const hookMethod = (name, method, isComponent) => {return function(...args) {const [evt] = args // 取出第一个参数// 判断是否为 event 对象if (evt && evt.target && evt.type) {// 记录用户行为}return method.apply(this, args)}
}

这里的代码只是代理了所有的事件方法,并不能用来还原用户的行为,要还原用户行为还必须知道该事件类型是否是需要的,比如点击、长按、输入。

const evtTypes = ['tap', // 点击'input', // 输入'confirm', // 回车'longpress' // 长按
]
const hookMethod = (name, method) => {return function(...args) {const [evt] = args // 取出第一个参数// 判断是否为 event 对象if (evt && evt.target && evt.type &&evtTypes.includes(evt.type) // 判断事件类型) {// 记录用户行为}return method.apply(this, args)}
}

确定事件类型之后,还需要明确点击的元素到底是哪个,但是小程序里面比较坑的地方就是,event 对象的 target 属性中,并没有元素的类名,但是可以获取元素的 dataset。


为了准确的获取元素,我们需要在构建中增加一个步骤,修改 wxml 文件,将所以元素的 class 属性复制一份到 data-className 。

<!-- 构建前 -->
<view class="close-btn"></view>
<view class="{{mainClassName}}"></view>
<!-- 构建后 -->
<view class="close-btn" data-className="close-btn"></view>
<view class="{{mainClassName}}" data-className="{{mainClassName}}"></view>

但是获取到 class 之后,又会有另一个坑,小程序的自动化测试工具并不能直接获取页面里自定义组件中的元素,必须先获取自定义组件。

<!-- Page -->
<toast text="loading" show="{{showToast}}" />
<!-- Component -->
<view class="toast" wx:if="{{show}}"><text class="toast-text">{{text}}</text><view class="toast-close" />
</view>
// 如果直接查找 .toast-close 会得到 null
const element = await page.$('.toast-close')
element.tap() // Error!// 必须先通过自定义组件的 tagName 找到自定义组件
// 再从自定义组件中通过 className 查找对应元素
const element = await page.$('toast .toast-close')
element.tap()

所以我们在构建操作的时候,还需要为元素插入 tagName。

<!-- 构建前 -->
<view class="close-btn" />
<toast text="loading" show="{{showToast}}" />
<!-- 构建后 -->
<view class="close-btn" data-className="close-btn" data-tagName="view" />
<toast text="loading" show="{{showToast}}" data-tagName="toast" />

现在我们可以继续愉快的记录用户行为了。

// 记录用户行为的数组
const actions = [];
// 添加用户行为
const addAction = (type, query, value = '') => {actions.push({time: Date.now(),type,query,value})
}// 代理事件方法
const hookMethod = (name, method, isComponent) => {return function(...args) {const [evt] = args // 取出第一个参数// 判断是否为 event 对象if (evt && evt.target && evt.type &&evtTypes.includes(evt.type) // 判断事件类型) {const { type, target, detail } = evtconst { id, dataset = {} } = targetconst { className = '' } = datasetconst { value = '' } = detail // input事件触发时,输入框的值// 记录用户行为let query = ''if (isComponent) {// 如果是组件内的方法,需要获取当前组件的 tagNamequery = `${this.dataset.tagName} `}if (id) {// id 存在,则直接通过 id 查找元素query += id} else {// id 不存在,才通过 className 查找元素query += className}addAction(type, query, value)}return method.apply(this, args)}
}

到这里已经记录了用户所有的点击、输入、回车相关的操作,但是还有一个滚动屏幕的操作还没记录。这里可以直接监听 Page 的 onPageScroll。

// 记录用户行为的数组
const actions = [];
// 添加用户行为
const addAction = (type, query, value = '') => {if (type === 'scroll' || type === 'input') {// 如果上一次行为也是滚动或输入,则重置 value 即可const last = this.actions[this.actions.length - 1]if (last && last.type === type) {last.value = valuelast.time = Date.now()return}}actions.push({time: Date.now(),type,query,value})
}Page = (params) => {const names = Object.keys(params)for (const name of names) {// 进行方法拦截if (typeof obj[name] === 'function') {params[name] = hookMethod(name, params[name], false)}}const { onPageScroll } = params// 拦截滚动事件params.onPageScroll = function (...args) {const [evt] = argsconst { scrollTop } = evtaddAction('scroll', '', scrollTop)onPageScroll.apply(this, args)}originPage(params)
}

这里有个优化点,就是滚动操作记录的时候,可以判断一下上次操作是否也为滚动操作,如果是同一个操作,则只需要修改一下滚动距离即可,以为两次滚动可以一步到位。同理,输入事件也是,输入的值也可以一步到位。

还原用户行为

用户操作完毕后,可以在控制台输出用户行为的 json 文本,把 json 文本复制出来后,就可以通过自动化工具运行了。

// 引入sdk
const automator = require('miniprogram-automator')// 用户操作行为
const actions = [{ 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(async miniProgram => {let page = await miniProgram.reLaunch('/page/index/index')let prevTimefor (const action of actions) {const { type, query, value, time } = actionif (prevTime) {// 计算两次操作之间的等待时间await page.waitFor(time - prevTime)}// 重置上次操作时间prevTime = time// 获取当前页面实例page = await miniProgram.currentPage()switch (type) {case 'tap':const element = await page.$(query)await element.tap()break;case 'input':const element = await page.$(query)await element.input(value)break;case 'confirm':const element = await page.$(query)await element.trigger('confirm', { value });break;case 'scroll':await miniProgram.pageScrollTo(value)break;}// 每次操作结束后,等待 5s,防止页面跳转过程中,后面的操作找不到页面await page.waitFor(5000)}// 关闭 IDEawait miniProgram.close()
})

这里只是简单的还原了用户的操作行为,实际运行过程中,还会涉及到网络请求和 localstorage 的 mock,这里不再展开讲述。同时,我们还可以接入 jest 工具,更加方便用例的编写。

总结

看似很难的需求,只要用心去发掘,总能找到对应的解决办法。另外微信小程序的自动化工具真的有很多坑,遇到问题可以先到小程序社区去找找,大部分坑都有前人踩过,还有一些一时无法解决的问题只能想其他办法来规避。最后祝愿天下无 bug。


 以下是我收集到的比较好的学习教程资源,虽然不是什么很值钱的东西,如果你刚好需要,可以评论区,留言【777】直接拿走就好了

各位想获取资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/30367.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

哪个类包含clone方法?是Cloneable还是Object?

在Java中&#xff0c;clone方法是定义在Object类中的。所有的Java类都继承自Object类&#xff0c;因此每个Java对象都继承了clone方法。然而&#xff0c;要成功地使用clone方法&#xff0c;需要满足一些条件&#xff0c;其中之一是被克隆的类必须实现Cloneable接口。 虽然clone…

[C#] 简单的俄罗斯方块实现

一个控制台俄罗斯方块游戏的简单实现. 已在 github.com/SlimeNull/Tetris 开源. 思路 很简单, 一个二维数组存储当前游戏的方块地图, 用 bool 即可, true 表示当前块被填充, false 表示没有. 然后, 抽一个 “形状” 类, 形状表示当前玩家正在操作的一个形状, 例如方块, 直线…

OCR让纸质文档秒变电子文档,让自动驾驶成为现实

OCR文字识别具有广泛的应用范围&#xff0c;以下是一些常见和广泛应用的领域&#xff1a; 1. 文档数字化&#xff1a;OCR可以将印刷的文档、书籍、报纸等纸质文档转换为可编辑和可搜索的电子文本&#xff0c;从而实现文档的数字化存储和管理。这在图书馆、档案馆、企业和政府机…

关于eclipse导入部署具有增删改查的项目

目录 前言&#xff1a;当我们刚刚进入公司的第一步就是去部署当前公司的项目&#xff0c;本博客就是详细介绍怎么去部署当前公司的项目。 一&#xff0c;开发工具&#xff1a; 二&#xff0c;具体步骤&#xff1a; 2.1导入公司的项目 打开eclipse开发工具 2.2配置当前的环…

Java8新特性,Lambda,Stream流

Java8新特性,Lambda,Stream流 Java8版本在2014年3月18日发布&#xff0c;为Java语言添加了很多重要的新特性。新特性包括&#xff1a;Lambda表达式、方法引用、默认方法、新的时间日期API、Stream API、Optional类等等。这些新特性大大增强了Java语言的表达能力&#xff0c;使…

Django实现音乐网站 ⑺

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是后台对歌手原有实现功能的基础上进行优化处理。 目录 新增编辑 表字段名称修改 隐藏单曲、专辑数 姓名首字母 安装xpinyin 获取姓名首字母 重写保存方法 列表显示 图片显示处理 引入函数 路径改为显示…

百度飞桨助力高校培养AI大模型人才,2023年飞桨产学合作项目申报启动

7月7日&#xff0c;教育部产学合作协同育人项目公布项目指南通过的企业名单&#xff0c;百度被列入2023年&#xff08;5月&#xff09;批次名单&#xff1b;其中百度飞桨与文心大模型项目40个&#xff0c;包含教学内容和课程体系改革、实践条件和实践基地建设、师资培训项目三大…

oracle sql developer批量删除某个用户

随着navicate收费&#xff0c;还得破解&#xff0c;pl/sql developer配置麻烦&#xff0c;最近使用oracle sql developer来试试oracle的操作如何&#xff1b; 用着还行&#xff0c;没有卡顿现象&#xff0c; 最近要oracle sql developer批量删除某个用户下所有的表&#xff0…

如何恢复已删除的 PDF 文件 - Windows 11、10

在传输数据或共享专业文档时&#xff0c;大多数人依赖PDF文件格式&#xff0c;但很少知道如何恢复意外删除或丢失的PDF文件。这篇文章旨在解释如何有效地恢复 PDF 文件。如果您身边有合适的数据恢复工具&#xff0c;PDF 恢复并不像看起来那么复杂。 便携式文档格式&#xff08…

原型模式与享元模式:提升系统性能的利器

原型模式和享元模式&#xff0c;前者是在创建多个实例时&#xff0c;对创建过程的性能进行调优&#xff1b;后者是用减 少创建实例的方式&#xff0c;来调优系统性能。这么看&#xff0c;你会不会觉得两个模式有点相互矛盾呢&#xff1f; 在有些场景下&#xff0c;我们需要重复…

在进行git pull操作时,存在本地文件与远程仓库发生冲突的情况

在远程使用了git pull origin main:main命令后&#xff0c;出现下面的错误&#xff1a; # git pull origin main:main Password for https://347340github.com: remote: Enumerating objects: 11, done. remote: Counting objects: 100% (11/11), done. remote: Compressing o…

23款奔驰GLS450时尚型升级原厂香氛负离子系统,清香宜人,久闻不腻

奔驰原厂香氛合理性可通过车内空气调节组件营造芳香四溢的怡人氛围。通过更换手套箱内香氛喷雾发生器所用的香水瓶&#xff0c;可轻松选择其他香氛。香氛的浓度和持续时间可调。淡雅的香氛缓缓喷出&#xff0c;并且在关闭后能够立刻散去。车内气味不会永久改变&#xff0c;香氛…

第三天课程下

1.项目目录介绍和运行流程 工程化开发模式中&#xff1a;这里不再直接编写模板语法&#xff0c;通过 App.vue 提供结构渲染 main.js文件 // 文件核心作用&#xff1a;导入App.vue&#xff0c;基于App.vue创建结构渲染index.html // 1. 导入 Vue 核心包 import Vue from vue// …

electron项目开发环境搭建

由于最近需要做一款跨平台的桌面应用&#xff0c;所以选择使用electron来作为开发的框架&#xff0c;下面说一下如何搭建一个简单的electron项目: 一、准备工作 安装git&#xff1a;下载git | 官网 安装node&#xff1a;下载 | Node.js 中文网 安装npm/cnpm&#xff1a;npm …

解数独(Java)

题目链接&#xff1a; 力扣 题目详情&#xff1a; 37. 解数独t编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只…

nodejs实现解析chm文件列表,无需转换为PDF文件格式,在线预览chm文件以及目录,不依赖任何网页端插件

特性: 1、支持任意深度的chm文件解析 2、解析后内容结构转换为tree数据呈现 3、点击树节点可以在html实时查看数据 4、不依赖任何浏览器端插件,兼容性较好 nodejs端核心代码 const $g = global.SG.$g, fs = global.SG.fs, router = global.SG.router, xlsx = global.SG.xl…

解决Linux内存碎片化问题

一、引言 随着Linux系统的广泛应用&#xff0c;系统中的内存管理也变得越来越重要。在长时间运行的过程中&#xff0c;内存碎片化问题成为了影响系统性能的一个重要因素。内存碎片化指的是系统中的可用内存被分散成许多小块&#xff0c;导致无法高效地利用内存资源。本文将介绍…

一个可以通过多个条件筛选的系统界面是如何实现的(springboot+mybatis)

比如我们有一个订单记录管理界面 条件可以通过订单号、商品名称、创建日期范围、价格范 围。。。来进行筛选查询。 首先我们先确定数据库订单表&#xff08;我这里就不做连表了&#xff0c;都 放在一个表中&#xff09;模拟一个订单表 order表 订单号 商品名称 创建…

telnet和ping的区别

文章目录 telnet和ping的作用和区别用telnet测试主机端口的原因 telnet和ping的作用和区别 Telnet和Ping是两种常用的网络工具&#xff0c;它们的作用和功能有以下区别&#xff1a; Telnet&#xff08;远程终端协议&#xff09;&#xff1a;Telnet是一种远程登录协议&#xf…

【ADS】在同一个symbol层次化实现理想器件和EM仿真的切换

ADS层次化切换理想器件和电磁仿真 1.需求描述2.实现步骤2.1 层次结构2.2 新建schematic2.3 新建symbol2.4 使用演示 3.后言 1.需求描述 在使用ADS做电磁仿真时&#xff0c;得到的结果需要多次迭代去接近原理图&#xff0c;那么就需要反复与原理图切换进行对比。 比较直接的方法…