搜尋
首頁微信小程式小程式開發詳解小程式自動化測試

詳解小程式自動化測試

Aug 21, 2020 pm 05:11 PM
小程式自動化

詳解小程式自動化測試

【相關學習推薦:微信小程式教學

背景

」近期團隊打算做一個小程式自動化測試的工具,期望能夠做到業務人員操作一遍小程式後,自動還原之前的操作路徑,並且捕獲操作過程中發生的異常,以此來判斷這次發布是否會影響小程式的基礎功能。

詳解小程式自動化測試

上述描述看似簡單,但是中間還是有些難點的,第一個難點就是如何在業務人員操作小程式的時候記錄操作路徑,第二個困難點就是如何將記錄的操作路徑進行還原。

自動化 SDK

如何將操作路徑還原這個問題,首選官方提供的 SDK: miniprogram-automator

小程式自動化 SDK 為開發者提供了一套透過外部腳本操控小程式的詳解小程式自動化測試,從而實現小程式自動化測試的目的。透過該SDK,你可以做到以下事情:

  • 控制小程式跳到指定頁面
  • #取得小程式頁面資料
  • 取得小程式頁面元素狀態
  • 觸發小程式元素綁定事件
  • 往AppService 注入程式碼片段
  • 呼叫wx 物件上任意介面
  • ...

上面的描述都來自官方文檔,建議閱讀後面內容之前可以先看看官方文檔,當然如果之前用過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 中捕捉所有的事件,好在小程式所以的頁面和元件都必須透過PageComponent 方法來包裝,所以我們可以改寫這兩個方法,攔截傳入的方法,並判斷第一個參數是否為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)
}复制代码

這裡有個優化點,就是滾動操作記錄的時候,可以判斷一下上次操作是否也為滾動操作,如果是同一個操作,則只需要修改一下滾動距離即可,因為兩次滾動可以一步到位。同理,輸入事件也是,輸入的值也可以一步到位。

還原使用者行為

使用者操作完畢後,可以在控制台輸出使用者行為的 json 文本,把 json 文本複製出來後,就可以透過自動化工具運行了。

// 引入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()
})复制代码

這裡只是簡單的還原了使用者的操作行為,實際運行過程中,還會涉及到網路請求和 localstorage 的 mock,這裡不再展開敘述。同時,我們也可以接取 jest 工具,更方便使用案例的編寫。

總結

看似很難的需求,只要用心去發掘,總能找到對應的解決方法。另外微信小程式的自動化工具真的有很多坑,遇到問題可以先到小程式社區去找找,大部分坑都有前人踩過,還有一些一時無法解決的問題只能想其他辦法來規避。最後祝福天下無 bug。

相關學習推薦:微信公眾號開發教學

#

以上是詳解小程式自動化測試的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:juejin。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能