Home  >  Article  >  WeChat Applet  >  Detailed explanation of small program automated testing

Detailed explanation of small program automated testing

coldplay.xixi
coldplay.xixiforward
2020-08-21 17:11:193298browse

Detailed explanation of small program automated testing

[Related learning recommendations: WeChat Mini Program Tutorial]

Background

The team plans to do a mini program automated test in the near future The tool is expected to enable business personnel to automatically restore the previous operation path after operating the mini program, and capture exceptions that occur during the operation to determine whether this release will affect the basic functions of the mini program.

Detailed explanation of small program automated testing

The above description seems simple, but there are still some difficulties in the middle. The first difficulty is how to record the operations when business personnel operate the mini program. path, the second difficulty is how to restore the recorded operation path.

Automation SDK

How to restore the operation path to this problem, the official SDK is preferred: miniprogram-automator.

The Mini Program Automation SDK provides developers with a set of solutions to control mini programs through external scripts, thereby achieving the purpose of automated testing of mini programs. Through this SDK, you can do the following:

  • Control the mini program to jump to the specified page
  • Get the mini program page data
  • Get the mini program page elements Status
  • Trigger mini program element binding event
  • Inject code snippet into AppService
  • Call any interface on wx object
  • ...

The above descriptions are all from official documents. It is recommended to read the official documents before reading the following content. Of course, if you have used puppeteer before, you can also get started quickly. The API is basically the same. The following is a brief introduction to how to use the 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()
})复制代码

There is one thing that needs to be reminded: before using the SDK, you need to open the service port of the developer tools, otherwise the startup will fail.

Detailed explanation of small program automated testing

Capture user behavior

With the method of restoring the operation path, the next step is to solve the problem of recording the operation path.

In the mini program, it is not possible to capture all events in the window through event bubbling like in the web. Fortunately, all pages and components of the mini program must pass Page, Component method to wrap, so we can rewrite these two methods, intercept the incoming method, and determine whether the first parameter is an event object to capture all events .

// 暂存原生方法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)
  }
}复制代码

The code here only proxies all event methods and cannot be used to restore the user's behavior. To restore the user's behavior, you must also know whether the event type is required, such as click, long press, and input.

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)
  }
}复制代码

After determining the event type, you still need to clarify which element was clicked. However, the pitfall in the mini program is that there is no class name of the element in the target attribute of the event object, but the element can be obtained. dataset.

Detailed explanation of small program automated testing

In order to obtain the elements accurately, we need to add a step in the construction, modify the wxml file, and change the class## of all elements # Copy the attribute to data-className.

<!-- 构建前 --><view></view><view></view><!-- 构建后 --><view></view><view></view>复制代码
But after obtaining the class, there will be another pitfall. The automated testing tool of the mini program cannot directly obtain the elements in the custom component in the page. The custom component must be obtained first.

<!-- 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()复制代码
So when we build the operation, we also need to insert tagName for the element.

<!-- 构建前 --><view></view><toast></toast><!-- 构建后 --><view></view><toast></toast>复制代码
Now we can continue happily recording user behavior.

// 记录用户行为的数组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)
  }
}复制代码
Up to this point, all the user’s clicks, inputs, and enter-related operations have been recorded. However, the scrolling screen operation is not recorded. We can directly proxy the

onPageScroll method of Page.

// 记录用户行为的数组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)
}复制代码
There is an optimization point here, that is, when the scroll operation is recorded, you can judge whether the last operation was also a scroll operation. If it is the same operation, you only need to modify the scroll distance, because the two The second scroll can be done in one step. In the same way, the same is true for input events, and the input value can also be reached in one step.

Restore user behavior

After the user operation is completed, the json text of the user behavior can be output on the console. After copying the json text, it can be run through the automation tool.

// 引入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()
})复制代码
This is just a simple restoration of the user's operation behavior. During the actual operation, network requests and localstorage mocks will also be involved, which will not be described here. At the same time, we can also access the jest tool to make it easier to write use cases.

Summary

For seemingly difficult needs, as long as you explore them carefully, you can always find a corresponding solution. In addition, there are really many pitfalls in the automation tools of WeChat mini programs. If you encounter problems, you can first go to the mini program community to find them. Most of the pitfalls have been stepped on by predecessors. There are also some problems that cannot be solved at the moment and you can only find other ways to solve them. avoid. Finally, I wish the world is bug-free.

Related learning recommendations: WeChat public account development tutorial

The above is the detailed content of Detailed explanation of small program automated testing. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.im. If there is any infringement, please contact admin@php.cn delete