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

来源:https://www.cnblogs.com/o2team/archive/2020/07/16/13321008.html
-Advertisement-
Play Games

作者:阿翔 如果你已經閱讀過 《京喜前端自動化測試之路(一)》,可跳過前言部分閱讀。 前言 京喜(原京東拼購)項目,作為京東戰略級業務,擁有千萬級別的流量入口。為了保障線上業務的穩定運行,每月例行開展前端容災演習,主要包含小程式及 H5 版本,要求各頁面各模塊在異常情況下進行適當的降級處理,不能出現 ...


作者:阿翔

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

前言

京喜(原京東拼購)項目,作為京東戰略級業務,擁有千萬級別的流量入口。為了保障線上業務的穩定運行,每月例行開展前端容災演習,主要包含小程式及 H5 版本,要求各頁面各模塊在異常情況下進行適當的降級處理,不能出現空窗、樣式錯亂、不合理的錯誤提示等體驗問題。

容災演習是一項長期持續的工作,且涉及頁面功能及場景多,人工的切換場景模擬異常導致演習效率較低,因此想通過開發自動化測試工具來提升演習效率,讓容災演習工作隨時可以輕鬆開展。由於京喜 H5 和小程式場景差異比較大,自動化測試分 H5 和小程式兩部分進行。前期已經分享過 H5 的自動化測試方案 —— 京喜前端自動化測試之路(一),本文則主要講述小程式版的自動化測試方案。

綜上所述,我們希望京喜小程式自動化測試工具可以提供以下功能:

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

小程式自動化 SDK

聊到小程式的自動化工具,微信官方為開發者提供了一套小程式自動化 SDK —— miniprogram-automator , 我們不需要關註技術選型,可直接使用。

小程式自動化 SDK 為開發者提供了一套通過外部腳本操控小程式的方案,從而實現小程式自動化測試的目的。

如果你之前使用過 Selenium WebDriver 或者 Puppeteer,那你可以很容易快速上手。小程式自動化 SDK 與它們的工作原理是類似的,主要區別在於控制對象由瀏覽器換成了小程式。

特性

通過該 SDK,你可以做到以下事情:

  • 控制小程式跳轉到指定頁面
  • 獲取小程式頁面數據
  • 獲取小程式頁面元素狀態
  • 觸發小程式元素綁定事件
  • 往 AppService 註入代碼片段
  • 調用 wx 對象上任意介面
  • ...

示例

const automator = require('miniprogram-automator')

automator
    .launch({
        cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 工具 cli 位置(絕對路徑)
        projectPath: 'path/to/project', // 項目文件地址(絕對路徑)
    })
    .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()
    })

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

實現方案

原來的容災演習過程:

小程式的通信方式改成 HTTPS ,通過 Whistle 對介面返回進行修改來模擬異常情況,驗證各頁面各模塊的降級處理符合預期。

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

我們將容災演習過程分為自動化流程人工操作兩部分。

自動化流程:

  1. 啟動微信開發者工具(開發版);
  2. 訪問目標頁面,模擬用戶點擊、滑動等行為;
  3. 模擬異常場景:攔截網路請求,修改介面返回數據(介面返回 500、異常數據等);
  4. 生成截圖。

人工操作:

自動化腳本執行完畢後,人工比對各個場景的截圖,判斷是否符合預期。

方案流程圖:
xxx

開發實錄

快速創建測試用例

為了提高測試腳本的可維護性、擴展性,我們將測試用例的信息都配置到 JSON 文件中,這樣編寫測試腳本的時候,我們只需關註測試流程的實現。

測試用例 JSON 數據配置包括公用數據(global)私有數據

公用數據(global):各測試用例都需要用到的數據,如:模擬訪問的目標頁面地址、名字、描述、設備類型等。

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

{
  "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-恢復網路"
      }
    ]
  },
  …
}

編寫測試腳本

我們以京喜首頁主介面的測試用例為例子,通過模擬主介面返回 500 響應碼的異常場景,驗證主介面的異常處理機制是否完善、用戶體驗是否友好。

預期效果:

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

測試流程:

ddd

場景實現:

根據測試流程以及配置的測試用例信息,編寫測試腳本,模擬測試用例場景:

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

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

  1. 模擬異常數據
const getMockData = (url, mockType, mockValue) => {
    const result = {
      data: 'test',
      cookies: [],
      header: {},
      statusCode: 200,
    }

    switch (mockType) {
      case 'data':
        result.data = 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
    }
  }

 // 修改本地存儲數據
 const mockValue = {
     data: {
         modules: [{
            tpl:'3000',
            content: []
         }]
     }
 }
 const mockData =  [
    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]
            // 命中規則的返回 mockData
            if (obj.url.indexOf(item.rule) > -1) {
              return item.result
            }
          }
          // 沒命中規則的真實訪問後臺
          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 對象上指定方法的調用結果。利用該 API,可以覆蓋 wx.request API,攔截網路請求,修改返回數據。
  • 目前是本地存儲一份介面返回的 JSON 數據,通過修改本地的 JSON 數據生成 mockData。若需要修改介面實時返回的數據,可在 obj.success 中獲取實時數據並修改。
  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 調用的影響。
    } catch (e) {
      console.error(`取消攔截【${url}】API報錯`)
      console.error(e)
    }
}

await cancelInterceptAPI(miniProgram)

啟動自動化測試

由於第一階段的測試工具尚未平臺化,先通過在終端輸入命令行,運行腳本的方式,啟動自動化測試。

在項目的 package.json 文件中,使用 scripts 欄位定義腳本命令:

 "scripts": {
    "start": "node pages/index/index.js"
  },

運行環境:

  • 安裝 Node.js 並且版本大於 8.0
  • 基礎庫版本為 2.7.3 及以上
  • 開發者工具版本為 1.02.1907232 及以上

運行:

在終端切入到項目根目錄路徑,輸入以下命令行,就可以啟動測試工具,運行測試腳本。

$ npm run start

測試結果

人工比對截圖結果:

測試結果圖

運行腳本示例:

運行腳本示例

使用 SDK,你必須知道 Shadow DOM

當我們想控制小程式頁面時,需獲取頁面實例 page,利用 page 提供的方法控制頁面內的元素。

比如,當我們想點擊頁面中搜索框時,我們一般會這麼做:

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

但這樣真的可行嗎?答案是:

試試就知道了。

運行這段測試腳本後生成的截圖:

沒有觸發點擊

我們得到的結果是:根本沒有觸發點擊事件。

Shadow DOM:

它是 HTML 的一個規範,它允許在文檔( document )渲染時插入一顆DOM元素子樹,但是這個子樹不在主 DOM 樹中。

它允許瀏覽器開發者封裝自己的 HTML 標簽、css 樣式和特定的 javascript 代碼、同時開發人員也可以創建類似 <input>、<video>、<audio> 等、這樣的自定義的一級標簽。創建這些標簽內容相關的 API,可以被叫做 Web Component。

Shadow DOM 的關鍵所在,它可以將一個隱藏的、獨立的 DOM 附加到一個元素上。

  • Shadow host: 一個常規 DOM 節點,Shadow DOM 會被附加到這個節點上。它是 Shadow DOM 的一個宿主元素。比如:<input />、<audio>、<video> 標簽,就是 Shadow DOM 的宿主元素。
  • Shadow tree: Shadow DOM 內部的 DOM 樹。
  • Shadow root: Shadow DOM 的根節點。通過 createShadowRoot 返回的文檔片段被稱為 shadow-root , 它和它的後代元素,都會對用戶隱藏。

回到我們剛剛的問題:

由於小程式使用了 Shadow DOM,因此我們不能直接通過 page 實例獲取到搜索框真實 DOM。我們看到的頁面中渲染的搜索框,實際上是一個 Shadow DOM。因此,我們必須先獲取到搜索框 Shadow DOM 的宿主元素,並通過宿主元素獲取到搜索框真實的 DOM,最後觸發真實 DOM 的點擊事件。

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

運行這段測試腳本後生成的截圖:

搜索頁

從截圖可以看到,觸發了搜索框的點擊事件。

更多測試場景實現

1. 下拉刷新

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

2. 滾動到指定 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) // 滾動到指定DOM

3. 事件

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

// 頁面 JS 出錯時觸發
page.on('error', (e) => {
    console.log(e)
})

結語

第一階段的小程式自動化測試之路告一段落。和 H5 自動化測試一樣,容災演習已實現了半自動化,可通過在終端運行測試腳本,模擬異常場景自動生成截圖,再配合人工比對截圖操作,判斷演習結果是否符合預期。目前已投入到每個月的容災演習中使用。

由於 H5 和小程式的差異比較大,第一階段的自動化測試分兩端進行,測試腳本語法也是截然不同,需要同時維護兩套測試工具。為了降低維護成本,提升測試腳本的開發效率,我們正在研發第二階段的自動化測試工具,一套代碼支持兩端測試,目前已經進入內測階段。更多彩蛋,敬請期待第二階段自動化測試工具——多端自動化測試 SDK 。


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

或者關註凹凸實驗室公眾號(AOTULabs),不定時推送文章:

歡迎關註凹凸實驗室公眾號


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • // 獲取當前時間 getNowTime: function () { let dateTime let yy = new Date().getFullYear() let mm = new Date().getMonth() + 1 let dd = new Date().getDate() le ...
  • elementUI時間日期選擇器選擇完傳給後臺的是林尼治標準時間 直接將格林尼治時間作參數傳到下麵的方法,下麵的方法直接貼, // 格林尼治轉換yyyy-MM-dd HH:mm:ss GMTToStr (time) { console.log('time:' + time) let date = n ...
  • 用JS製作導航顯示子菜單(滑鼠進入的時候,我們要a標簽顯示它的兄弟元素 ) HTML: <nav> <ul> <li><a href="#">關於</a></li> <li><a href="#">技能</a></li> <li> <a class="menutrigger" href="#">作品 ...
  • Ajax 的原理簡單來說是在⽤戶和伺服器之間加了—個中間層( AJAX 引擎),通過XmlHttpRequest 對象來向伺服器發非同步請求,從伺服器獲得數據,然後⽤ javascrip t 來操作 DOM ⽽更新⻚⾯。使⽤戶操作與伺服器響應非同步化。這其中最關鍵的⼀步就是從服 務器獲得請求數據 Aja ...
  • 1. 對CSS優化的理解(比如:代碼層面) 2. ES6的理解(let const變數/箭頭函數/promise.all併發/ES6的模塊化和node.js模塊化的區別) 3. jquery鏈式調用/鏈式編程的原理 4. element的二次封裝 5. js的繼承有哪幾種 6. 垂直居中有哪些方式 ...
  • 值 中文描述 英文名稱 100 繼續 Continue 101 交換協議 Switching Protocols 102 處理中 Processing 103 早期提示 Early Hints 104-199 未分配 Unassigned 200 請求成功 OK 201 已建立 Created 20 ...
  • 前端VUE頁面上的導出或者下載功能,一般是調用後端的一個介面,由介面生成excel,word這些文件的流信息,返回給vue,然後由vue去構建下載的動作,這邊整理了一下,封裝了一下,方便以後復用。 封裝一個download文件 使用年月日時分秒毫秒做為文件的名稱,下載為excel文件 /** * 下 ...
  • 對於前端入行現在大概有這麼幾種:1.大學里修前端課程,2.查資料看視頻自學前端,3.參加前端培訓。 對於這三種學習方式,自學前端是屬於最難的一種,咱們先簡單的說一下另外兩種方式, 然後我再著重說下自學前端。除了自學前端外,外兩種學習式稱為被動學習。大家可以看到,不管是大學里前端課程還是培訓學習,都有 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...