집 >위챗 애플릿 >미니 프로그램 개발 >소규모 프로그램 자동화 테스트에 대한 자세한 설명
[관련 학습 권장 사항: WeChat 미니 프로그램 튜토리얼]
최근 팀에서는 미니 프로그램을 자동으로 테스트하는 도구를 만들 계획이며, 이를 통해 비즈니스 담당자가 운영 후 이전 작업을 자동으로 복원할 수 있기를 바랍니다. 미니 프로그램 경로 및 작업 중에 발생하는 예외를 캡처하여 이번 릴리스가 미니 프로그램의 기본 기능에 영향을 미치는지 여부를 확인합니다.
위의 설명은 간단해 보이지만 여전히 중간에 난관이 좀 있습니다. 첫 번째 난관은 사업 담당자가 미니 프로그램을 운영할 때 작업 경로를 기록하는 방법이고, 두 번째 난관은 휴대하는 방법입니다. 기록된 작업 경로를 축소합니다.
작업 경로를 복원하는 방법은 무엇입니까? 이 문제의 경우 공식 SDK인 miniprogram-automator
를 사용하는 것이 좋습니다. miniprogram-automator
。
小程序自动化 SDK 为开发者提供了一套通过外部脚本操控小程序的소규모 프로그램 자동화 테스트에 대한 자세한 설명,从而实现小程序自动化测试的目的。通过该 SDK,你可以做到以下事情:
上面的描述都来自官方文档,建议阅读后面内容之前可以先看看官方文档,当然如果之前用过 puppeteer ,也可以快速上手,api 基本一致。下面简单介绍下 SDK 的使用方式。
// 引入sdkconst automator = require('miniprogram-automator')// 启动微信开发者工具automator.launch({ // 微信开发者工具安装路径下的 cli 工具 // Windows下为安装路径下的 cli.bat // MacOS下为安装路径下的 cli cliPath: 'path/to/cli', // 项目地址,即要运行的小程序的路径 projectPath: 'path/to/project', }).then(async miniProgram => { // miniProgram 为 IDE 启动后的实例 // 启动小程序里的 index 页面 const page = await miniProgram.reLaunch('/page/index/index') // 等待 500 ms await page.waitFor(500) // 获取页面元素 const element = await page.$('.main-btn') // 点击元素 await element.tap() // 关闭 IDE await miniProgram.close() })复制代码
有个地方需要提醒一下:使用 SDK 之前需要开启开发者工具的服务端口,要不然会启动失败。
有了还原操作路径的办法,接下来就要解决记录操作路径的难题了。
在小程序中,并不能像 web 中通过事件冒泡的方式在 window 中捕获所有的事件,好在小程序所以的页面和组件都必须通过 Page
、Component
方法来包装,所以我们可以改写这两个方法,拦截传入的方法,并判断第一个参数是否为 event
对象,以此来捕获所有的事件。
// 暂存原生方法const originPage = Pageconst originComponent = Component// 改写 PagePage = (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) }// 改写 ComponentComponent = (params) => { if (params.methods) { const { methods } = params const 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></view><view></view><!-- 构建后 --><view></view><view></view>复制代码
但是获取到 class 之后,又会有另一个坑,小程序的自动化测试工具并不能直接获取页面里自定义组件中的元素,必须先获取自定义组件。
<!-- Page --><toast></toast><!-- Component --><view> <text>{{text}}</text> <view></view></view>复制代码
// 如果直接查找 .toast-close 会得到 nullconst element = await page.$('.toast-close') element.tap() // Error!// 必须先通过自定义组件的 tagName 找到自定义组件// 再从自定义组件中通过 className 查找对应元素const element = await page.$('toast .toast-close') element.tap()复制代码
所以我们在构建操作的时候,还需要为元素插入 tagName。
<!-- 构建前 --><view></view><toast></toast><!-- 构建后 --><view></view><toast></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 } = evt const { id, dataset = {} } = target const { className = '' } = dataset const { value = '' } = detail // input事件触发时,输入框的值 // 记录用户行为 let query = '' if (isComponent) { // 如果是组件内的方法,需要获取当前组件的 tagName query = `${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 = value last.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] = args const { scrollTop } = evt addAction('scroll', '', scrollTop) onPageScroll.apply(this, args) } originPage(params) }复制代码
페이지
, 를 통과해야 합니다. 래핑할 구성 요소
메서드를 사용하여 이 두 메서드를 다시 작성하고 들어오는 메서드를 가로채고 첫 번째 매개변수가 모든 이벤트를 캡처하는 event
개체인지 확인합니다. 🎜// 引入sdkconst 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 prevTime for (const action of actions) { const { type, query, value, time } = action if (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) } // 关闭 IDE await miniProgram.close() })复制代码🎜여기의 코드는 모든 이벤트 메소드만을 나타내며 사용자의 행동을 복원하는 데 사용할 수 없습니다. 사용자의 행동을 복원하려면 클릭, 길게 누르기, 입력 등 이벤트 유형이 필요한지 여부도 알아야 합니다. 🎜rrreee🎜이벤트 유형을 결정한 후에도 어떤 요소가 클릭되었는지 명확히 해야 합니다. 그러나 미니 프로그램의 함정은 이벤트 개체의 대상 속성에 해당 요소의 클래스 이름이 없지만 해당 요소의 데이터 세트가 있다는 것입니다. 요소를 얻을 수 있습니다. 🎜🎜🎜🎜🎜🎜요소를 정확하게 얻으려면 구성 단계를 추가하고 wxml 파일을 수정한 다음
class
속성을 복사해야 합니다. data-className
의 모든 요소. 🎜rrreee🎜하지만 클래스를 얻은 후에는 또 다른 함정이 있습니다. 미니 프로그램의 자동화된 테스트 도구는 페이지의 사용자 정의 구성 요소를 직접 얻을 수 없습니다. 먼저 사용자 정의 구성 요소를 얻어야 합니다. 🎜rrreeerrreee🎜따라서 작업을 빌드할 때 요소에 tagName도 삽입해야 합니다. 🎜rrreee🎜이제 사용자 행동을 계속해서 즐겁게 기록할 수 있습니다. 🎜rrreee🎜지금까지 사용자의 클릭, 입력, 엔터 관련 작업이 모두 기록되었습니다. 그러나 스크롤 화면 작업은 기록되지 않습니다. Page의 onPageScroll
메서드를 직접 프록시할 수 있습니다. 🎜rrreee🎜 여기에 최적화 포인트가 있습니다. 즉, 스크롤 작업을 기록할 때 마지막 작업도 스크롤 작업인지 판단할 수 있습니다. 동일한 작업이라면 스크롤 거리만 수정하면 되기 때문입니다. 두 개의 스크롤을 한 번에 수행할 수 있습니다. 입력 이벤트도 마찬가지이며, 입력값도 한 단계로 도달할 수 있습니다. 🎜🎜사용자 행동 복원🎜🎜사용자 작업이 완료된 후 사용자 행동의 json 텍스트를 콘솔에 출력할 수 있습니다. json 텍스트를 복사한 후 자동화 도구를 통해 실행할 수 있습니다. 🎜rrreee🎜이것은 사용자의 작업 동작을 간단히 복원하는 것일 뿐입니다. 실제 작업 중에는 네트워크 요청 및 로컬 저장소 모의 작업도 포함되므로 여기서는 설명하지 않습니다. 동시에 Jest 도구에 액세스하여 사용 사례를 더 쉽게 작성할 수도 있습니다. 🎜어려워 보이는 요구 사항에 대해 주의 깊게 탐색하는 한 언제든지 해당 솔루션을 찾을 수 있습니다. 또한 WeChat 미니 프로그램의 자동화 도구에는 정말 많은 함정이 있습니다. 문제가 발생하면 먼저 미니 프로그램 커뮤니티에 가서 대부분의 함정을 찾을 수 있습니다. 현재 해결할 수 없는 문제는 피할 수 있는 다른 방법만 찾을 수 있습니다. 마지막으로, 버그 없는 세상이 되었으면 좋겠습니다.
관련 학습 권장사항: WeChat 공개 계정 개발 튜토리얼
위 내용은 소규모 프로그램 자동화 테스트에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!