京喜前端自動化測試之(zhī)路(小程序篇) - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 小程序相關 >

京喜前端自動化測試之(zhī)路(小程序篇)

發表時(shí)間:2021-1-11

發布人(rén):融晨科技

浏覽次數:103

如果你已經閱讀過 《京喜前端自動化測試之(zhī)路(一)》,可跳過前言部分閱讀。

前言

京喜(原京東拼購)項目,作爲(wéi / wèi)京東戰略級業務,擁有千萬級别的(de)流量入口。爲(wéi / wèi)了(le/liǎo)保障線上(shàng)業務的(de)穩定運行,每月例行開展前端容災演習,主要(yào / yāo)包含小程序及 H5 版本,要(yào / yāo)求各頁面各模塊在(zài)異常情況下進行适當的(de)降級處理,不(bù)能出(chū)現空窗、樣式錯亂、不(bù)合理的(de)錯誤提示等體驗問題。

容災演習是(shì)一項長期持續的(de)工作,且涉及頁面功能及場景多,人(rén)工的(de)切換場景模拟異常導緻演習效率較低,因此想通過開發自動化測試工具來(lái)提升演習效率,讓容災演習工作随時(shí)可以(yǐ)輕松開展。由于(yú)京喜 H5 和(hé / huò)小程序場景差異比較大(dà),自動化測試分 H5 和(hé / huò)小程序兩部分進行。前期已經分享過 H5 的(de)自動化測試方案 —— 京喜前端自動化測試之(zhī)路(一),本文則主要(yào / yāo)講述小程序版的(de)自動化測試方案。

綜上(shàng)所述,我們希望京喜小程序自動化測試工具可以(yǐ)提供以(yǐ)下功能:

  1. 訪問目标頁面,對頁面進行截圖;
  2. 模拟用戶點擊、滑動頁面操作;
  3. 網絡攔截、模拟異常情況(接口響應碼 500、接口返回數據異常);
  4. 操作緩存數據(模拟有無緩存的(de)場景等)。

小程序自動化 SDK

聊到(dào)小程序的(de)自動化工具,微信官方爲(wéi / wèi)開發者提供了(le/liǎo)一套小程序自動化 SDK —— miniprogram-automator , 我們不(bù)需要(yào / yāo)關注技術選型,可直接使用。

小程序自動化 SDK 爲(wéi / wèi)開發者提供了(le/liǎo)一套通過外部腳本操控小程序的(de)方案,從而(ér)實現小程序自動化測試的(de)目的(de)。

如果你之(zhī)前使用過 Selenium WebDriver 或者 Puppeteer,那你可以(yǐ)很容易快速上(shàng)手。小程序自動化 SDK 與它們的(de)工作原理是(shì)類似的(de),主要(yào / yāo)區别在(zài)于(yú)控制對象由浏覽器換成了(le/liǎo)小程序。

特性

通過該 SDK,你可以(yǐ)做到(dào)以(yǐ)下事情:

  • 控制小程序跳轉到(dào)指定頁面
  • 獲取小程序頁面數據
  • 獲取小程序頁面元素狀态
  • 觸發小程序元素綁定事件
  • 往 AppService 注入代碼片段
  • 調用 wx 對象上(shàng)任意接口
  • ...

示例

const automator = require('miniprogram-automator')

automator
    .launch({
        cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 工具 cli 位置(絕對路徑)
        projectPath: 'path/to/project', // 項目文件地(dì / de)址(絕對路徑)
    })
    .then(async miniProgram => {
        const page = await miniProgram.reLaunch('/pages/index/index')
        await page.waitFor(500)
        const element = await page.$('.banner')
        console.log(await element.attribute('class'))
        await element.tap()
        await miniProgram.close()
    })
複制代碼

綜上(shàng)所述,我們選擇使用官方維護的(de) SDK —— miniprogram-automator 開發小程序的(de)自動化測試工具,通過 SDK 提供的(de)一系列 API ,實現訪問目标頁面、模拟異常場景、生成截圖的(de)過程自動化。最後再通過人(rén)工比對截圖,判斷頁面降級處理是(shì)否符合預預期、用戶體驗是(shì)否友好。

實現方案

原來(lái)的(de)容災演習過程:

小程序的(de)通信方式改成 HTTPS ,通過 Whistle 對接口返回進行修改來(lái)模拟異常情況,驗證各頁面各模塊的(de)降級處理符合預期。

現階段的(de)容災演習自動化方案:

我們将容災演習過程分爲(wéi / wèi)自動化流程和(hé / huò)人(rén)工操作兩部分。

自動化流程:

  1. 啓動微信開發者工具(開發版);
  2. 訪問目标頁面,模拟用戶點擊、滑動等行爲(wéi / wèi);
  3. 模拟異常場景:攔截網絡請求,修改接口返回數據(接口返回 500、異常數據等);
  4. 生成截圖。

人(rén)工操作:

自動化腳本執行完畢後,人(rén)工比對各個(gè)場景的(de)截圖,判斷是(shì)否符合預期。

方案流程圖:


開發實錄

快速創建測試用例

爲(wéi / wèi)了(le/liǎo)提高測試腳本的(de)可維護性、擴展性,我們将測試用例的(de)信息都配置到(dào) JSON 文件中,這(zhè)樣編寫測試腳本的(de)時(shí)候,我們隻需關注測試流程的(de)實現。

測試用例 JSON 數據配置包括公用數據(global)和(hé / huò)私有數據

公用數據(global):各測試用例都需要(yào / yāo)用到(dào)的(de)數據,如:模拟訪問的(de)目标頁面地(dì / de)址、名字、描述、設備類型等。

私有數據: 各測試用例特定的(de)數據,如測試模塊信息、api 地(dì / de)址、測試場景、預期結果、截圖名字等數據。

{
  "global": {
    "url": "/pages/index/index",
    "pageName": "index",
    "pageDesc": "首頁",
    "device": "iPhone X"
  },
  "homePageApi": {
    "id": 1,
    "module": "home_page_api",
    "moduleDesc": "首頁主接口",
    "api": "https://xxx",
    "operation": "模拟響應碼 500",
    "expectRules": [
      "1. 有緩存數據,顯示容災兜底數據",
      "2. 請求容災接口,顯示容災兜底數據",
      "3. 容災接口異常,顯示信異常息、刷新按鈕",
      "4. 恢複網絡,點擊刷新按鈕,顯示正常數據"
    ],
    "screenshot": [
      {
        "name": "normal",
        "desc": "正常場景"
      },
      {
        "name": "500_cache",
        "desc": "有緩存-主接口返回500"
      },
      {
        "name": "500_no_cache",
        "desc": "無緩存-主接口返回500-容災兜底數據"
      },
      {
        "name": "500_no_cache_500_disaster",
        "desc": "無緩存-主接口返回500-容災兜底接口返回500"
      },
      {
        "name": "500_no_cache_recover",
        "desc": "無緩存-返回500-恢複網絡"
      }
    ]
  },
  …
}
複制代碼

編寫測試腳本

我們以(yǐ)京喜首頁主接口的(de)測試用例爲(wéi / wèi)例子(zǐ),通過模拟主接口返回 500 響應碼的(de)異常場景,驗證主接口的(de)異常處理機制是(shì)否完善、用戶體驗是(shì)否友好。

預期效果:

  • 主接口異常,有緩存數據,顯示緩存數據
  • 主接口異常,無緩存數據,則請求容災接口,顯示容災兜底數據
  • 主接口、容災接口異常,無緩存數據,顯示信異常息、刷新按鈕
  • 恢複網絡,點擊刷新按鈕,顯示正常數據

測試流程:

場景實現:

根據測試流程以(yǐ)及配置的(de)測試用例信息,編寫測試腳本,模拟測試用例場景:

  1. 訪問頁面
const miniProgram = await automator.launch({
      cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 開發者工具命令行工具(絕對路徑)
      projectPath: 'jx_project', // 項目地(dì / de)址(絕對路徑)

})
await miniProgram.reLaunch('/pages/index/index')
複制代碼
  1. 生成截圖
await miniProgram.screenshot({
    path: 'jx_weapp_index_home_page_500.png'
})

複制代碼
  1. 模拟異常數據
const getMockData = http://www.wxapp-union.com/(url, mockType, mockValue) => {
    const result = {
      data: 'test',
      cookies: [],
      header: {},
      statusCode: 200,
    }

    switch (mockType) {
      case 'data':
        result.data = http://www.wxapp-union.com/getMockResponse(url, mockValue) // 修改返回數據
        break
      case 'cookies':
        result.cookies = mockValue // 修改返回數據
        break
      case 'header':
        result.header = mockValue // 修改返回響應頭
        break
      case 'statusCode':
        result.statusCode = mockValue // 修改返回響應頭
        break
    }

    return {
      rule: url,
      result
    }
  }

 // 修改本地(dì / de)存儲數據
 const mockValue = http://www.wxapp-union.com/{
     data: {
         modules: [{
            tpl:'3000',
            content: []
         }]
     }
 }
 const mockData =  http://www.wxapp-union.com/[
    getMockData(api1, 'statusCode', 500), // 模拟接口返回 500
    getMockData(api2, 'data', mockValue) // 模拟接口返回異常數據
    ...
 ]
 
複制代碼
  1. 攔截接口請求,修改返回數據
const interceptAPI = async (miniProgram, url, mockData) => {
    try {
      await miniProgram.mockWxMethod(
        'request',
        function(obj, data) { // 處理返回函數
          for (let i = 0, len = data.length; i < len; i++) {
            const item = data[i]
            // 命中規則的(de)返回 mockData
            if (obj.url.indexOf(item.rule) > -1) {
              return item.result
            }
          }
          // 沒命中規則的(de)真實訪問後台
          return new Promise(resolve => {
            obj.success = res => resolve(res)
            obj.fail = res => resolve(res)
            / origin 指向原始方法
            this.origin(obj)
          })
        },
        mockData, // 傳入 mock 數據
      )

    } catch (e) {
      console.error(`攔截【${url}】API報錯`)
      console.error(e)
    }
  }

await interceptAPI(interceptAPI, url, mockData)
複制代碼
  • miniProgram.mockWxMethod:覆蓋 wx 對象上(shàng)指定方法的(de)調用結果。利用該 API,可以(yǐ)覆蓋 wx.request API,攔截網絡請求,修改返回數據。
  • 目前是(shì)本地(dì / de)存儲一份接口返回的(de) JSON 數據,通過修改本地(dì / de)的(de) JSON 數據生成 mockData。若需要(yào / yāo)修改接口實時(shí)返回的(de)數據,可在(zài) obj.success 中獲取實時(shí)數據并修改。
  1. 清除緩存
try {
    await miniProgram.callWxMethod('clearStorage')
} catch (e) {
    await console.log(`清除緩存報錯: `)
    await console.log(e)
}
複制代碼
  1. 點擊刷新按鈕
const page = await miniProgram.currentPage()
const $refreshBtn = await page.$('.page-error__refresh-btn') // 同 WXSS,僅支持部分 CSS 選擇器
await $refreshBtn.tap()
複制代碼
  1. 取消攔截,恢複網絡
const cancelInterceptAPI = async (miniProgram) => {
    try {
      await miniProgram.restoreWxMethod('request') // 重置 wx.request ,消除 mockWxMethod 調用的(de)影響。
    } catch (e) {
      console.error(`取消攔截【${url}】API報錯`)
      console.error(e)
    }
}

await cancelInterceptAPI(miniProgram)

複制代碼

啓動自動化測試

由于(yú)第一階段的(de)測試工具尚未平台化,先通過在(zài)終端輸入命令行,運行腳本的(de)方式,啓動自動化測試。

在(zài)項目的(de) package.json 文件中,使用 scripts 字段定義腳本命令:

 "scripts": {
    "start": "node pages/index/index.js"
  },
複制代碼

運行環境:

  • 安裝 Node.js 并且版本大(dà)于(yú) 8.0
  • 基礎庫版本爲(wéi / wèi) 2.7.3 及以(yǐ)上(shàng)
  • 開發者工具版本爲(wéi / wèi) 1.02.1907232 及以(yǐ)上(shàng)

運行:

在(zài)終端切入到(dào)項目根目錄路徑,輸入以(yǐ)下命令行,就(jiù)可以(yǐ)啓動測試工具,運行測試腳本。

$ npm run start
複制代碼

測試結果

運行腳本示例:

使用 SDK,你必須知道(dào) Shadow DOM

當我們想控制小程序頁面時(shí),需獲取頁面實例 page,利用 page 提供的(de)方法控制頁面内的(de)元素。

比如,當我們想點擊頁面中搜索框時(shí),我們一般會這(zhè)麽做:

 const page = await miniProgram.currentPage()
 const $searchBar = await page.$('search-bar')
 await $searchBar.tap()
複制代碼

但這(zhè)樣真的(de)可行嗎?答案是(shì):

試試就(jiù)知道(dào)了(le/liǎo)。

運行這(zhè)段測試腳本後生成的(de)截圖:

我們得到(dào)的(de)結果是(shì):根本沒有觸發點擊事件。

Shadow DOM:

它是(shì) HTML 的(de)一個(gè)規範,它允許在(zài)文檔( document )渲染時(shí)插入一顆DOM元素子(zǐ)樹,但是(shì)這(zhè)個(gè)子(zǐ)樹不(bù)在(zài)主 DOM 樹中。

它允許浏覽器開發者封裝自己的(de) HTML 标簽、css 樣式和(hé / huò)特定的(de) javascript 代碼、同時(shí)開發人(rén)員也(yě)可以(yǐ)創建類似 <input>、<video>、<audio> 等、這(zhè)樣的(de)自定義的(de)一級标簽。創建這(zhè)些标簽内容相關的(de) API,可以(yǐ)被叫做 Web Component。

Shadow DOM 的(de)關鍵所在(zài),它可以(yǐ)将一個(gè)隐藏的(de)、獨立的(de) DOM 附加到(dào)一個(gè)元素上(shàng)。

  • Shadow host: 一個(gè)常規 DOM 節點,Shadow DOM 會被附加到(dào)這(zhè)個(gè)節點上(shàng)。它是(shì) Shadow DOM 的(de)一個(gè)宿主元素。比如:<input />、<audio>、<video> 标簽,就(jiù)是(shì) Shadow DOM 的(de)宿主元素。
  • Shadow tree: Shadow DOM 内部的(de) DOM 樹。
  • Shadow root: Shadow DOM 的(de)根節點。通過 createShadowRoot 返回的(de)文檔片段被稱爲(wéi / wèi) shadow-root , 它和(hé / huò)它的(de)後代元素,都會對用戶隐藏。

回到(dào)我們剛剛的(de)問題:

由于(yú)小程序使用了(le/liǎo) Shadow DOM,因此我們不(bù)能直接通過 page 實例獲取到(dào)搜索框真實 DOM。我們看到(dào)的(de)頁面中渲染的(de)搜索框,實際上(shàng)是(shì)一個(gè) Shadow DOM。因此,我們必須先獲取到(dào)搜索框 Shadow DOM 的(de)宿主元素,并通過宿主元素獲取到(dào)搜索框真實的(de) DOM,最後觸發真實 DOM 的(de)點擊事件。

  const page = await miniProgram.currentPage()
  const $searchBarShadow = await page.$('search-bar')
  const $searchBar = await $searchBarShadow.$('.search-bar')
  const { height } = await $searchBar.size()
複制代碼

運行這(zhè)段測試腳本後生成的(de)截圖:

從截圖可以(yǐ)看到(dào),觸發了(le/liǎo)搜索框的(de)點擊事件。

更多測試場景實現

1. 下拉刷新

const pullDownRefresh = async (miniProgram) => {
    try {
      await miniProgram.callWxMethod('startPullDownRefresh')
    } catch (e) {
      console.error('下拉刷新操作失敗')
      console.error(e)
    }
}
複制代碼

2. 滾動到(dào)指定 DOM

const page = await miniProgram.currentPage() // 獲取頁面實例
const $recommendTabShadow = await page.$('recommend-tab') // 獲取Shadow DOM
const $recommendTab = await $recommendTabShadow.$('.recommend') // 獲取真實 DOM
const { top } = await $recommendTab.offset() // 獲取DOM 定位
await miniProgram.pageScrollTo(top) // 滾動到(dào)指定DOM
複制代碼

3. 事件

  • 日志打印;
  • 監聽頁面崩潰事件
// 日志打印時(shí)觸發
miniProgram.on('console', msg => {
    console.log(msg.type, msg.args)
  })
})

// 頁面 JS 出(chū)錯時(shí)觸發
page.on('error', (e) => {
    console.log(e)
})
複制代碼

結語

第一階段的(de)小程序自動化測試之(zhī)路告一段落。和(hé / huò) H5 自動化測試一樣,容災演習已實現了(le/liǎo)半自動化,可通過在(zài)終端運行測試腳本,模拟異常場景自動生成截圖,再配合人(rén)工比對截圖操作,判斷演習結果是(shì)否符合預期。目前已投入到(dào)每個(gè)月的(de)容災演習中使用。

由于(yú) H5 和(hé / huò)小程序的(de)差異比較大(dà),第一階段的(de)自動化測試分兩端進行,測試腳本語法也(yě)是(shì)截然不(bù)同,需要(yào / yāo)同時(shí)維護兩套測試工具。爲(wéi / wèi)了(le/liǎo)降低維護成本,提升測試腳本的(de)開發效率,我們正在(zài)研發第二階段的(de)自動化測試工具,一套代碼支持兩端測試,目前已經進入内測階段。更多彩蛋,敬請期待第二階段自動化測試工具——多端自動化測試 SDK 。


歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公衆号(AOTULabs),不(bù)定時(shí)推送文章

相關案例查看更多