1. 程式人生 > >京喜前端自動化測試之路(小程式篇)

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

![](https://img2020.cnblogs.com/other/1992869/202007/1992869-20200716094648960-263985640.png) 作者:阿翔 如果你已經閱讀過 [《京喜前端自動化測試之路(一)》](https://mp.weixin.qq.com/s/VhvXTNuM7TSfFtzBVmhTyg),可跳過前言部分閱讀。 ## 前言 **京喜**(原京東拼購)專案,作為京東戰略級業務,擁有千萬級別的流量入口。為了保障線上業務的穩定執行,每月例行開展前端容災演習,主要包含小程式及 H5 版本,要求各頁面各模組在異常情況下進行適當的降級處理,不能出現空窗、樣式錯亂、不合理的錯誤提示等體驗問題。 容災演習是一項長期持續的工作,且涉及頁面功能及場景多,人工的切換場景模擬異常導致演習效率較低,因此想通過開發自動化測試工具來提升演習效率,讓容災演習工作隨時可以輕鬆開展。由於京喜 H5 和小程式場景差異比較大,自動化測試分 H5 和小程式兩部分進行。前期已經分享過 H5 的自動化測試方案 —— `京喜前端自動化測試之路(一)`,本文則主要講述小程式版的自動化測試方案。 綜上所述,我們希望京喜小程式自動化測試工具可以提供以下功能: 1. 訪問目標頁面,對頁面進行截圖; 2. 模擬使用者點選、滑動頁面操作; 3. 網路攔截、模擬異常情況(介面響應碼 500、介面返回資料異常); 4. 操作快取資料(模擬有無快取的場景等)。 ## 小程式自動化 SDK 聊到小程式的自動化工具,微信官方為開發者提供了一套小程式自動化 SDK —— [miniprogram-automator](https://www.npmjs.com/package/miniprogram-automator) , 我們不需要關注技術選型,可直接使用。 >小程式自動化 SDK 為開發者提供了一套通過外部指令碼操控小程式的方案,從而實現小程式自動化測試的目的。 >如果你之前使用過 [Selenium WebDriver](https://www.selenium.dev/projects/) 或者 [Puppeteer](https://pptr.dev/),那你可以很容易快速上手。小程式自動化 SDK 與它們的工作原理是類似的,主要區別在於控制物件由瀏覽器換成了小程式。 **特性** 通過該 SDK,你可以做到以下事情: - 控制小程式跳轉到指定頁面 - 獲取小程式頁面資料 - 獲取小程式頁面元素狀態 - 觸發小程式元素繫結事件 - 往 AppService 注入程式碼片段 - 呼叫 wx 物件上任意介面 - ... **示例** ```js 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](https://wproxy.org/whistle/) 對介面返回進行修改來模擬異常情況,驗證各頁面各模組的降級處理符合預期。 **現階段的容災演習自動化方案:** 我們將容災演習過程分為`自動化流程`和`人工操作`兩部分。 **自動化流程:** 1. 啟動微信開發者工具(開發版); 2. 訪問目標頁面,模擬使用者點選、滑動等行為; 2. 模擬異常場景:攔截網路請求,修改介面返回資料(介面返回 500、異常資料等); 3. 生成截圖。 **人工操作:** 自動化指令碼執行完畢後,人工比對各個場景的截圖,判斷是否符合預期。 **方案流程圖:** ![xxx](https://img2020.cnblogs.com/other/1992869/202007/1992869-20200716094649468-864600434.png) ## 開發實錄 ### 快速建立測試用例 為了提高測試指令碼的可維護性、擴充套件性,我們將測試用例的資訊都配置到 JSON 檔案中,這樣編寫測試指令碼的時候,我們只需關注測試流程的實現。 測試用例 JSON 資料配置包括`公用資料(global)`和`私有資料`: `公用資料(global)`:各測試用例都需要用到的資料,如:模擬訪問的目標頁面地址、名字、描述、裝置型別等。 `私有資料`: 各測試用例特定的資料,如測試模組資訊、api 地址、測試場景、預期結果、截圖名字等資料。 ```json { "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](https://img2020.cnblogs.com/other/1992869/202007/1992869-20200716094649827-468043868.png) **場景實現:** 根據測試流程以及配置的測試用例資訊,編寫測試指令碼,模擬測試用例場景: 1. 訪問頁面 ```js const miniProgram = await automator.launch({ cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 開發者工具命令列工具(絕對路徑) projectPath: 'jx_project', // 專案地址(絕對路徑) }) await miniProgram.reLaunch('/pages/index/index') ``` 2. 生成截圖 ```js await miniProgram.screenshot({ path: 'jx_weapp_index_home_page_500.png' }) ``` 3. 模擬異常資料 ```js 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) // 模擬介面返回異常資料 ... ] ``` 4. 攔截介面請求,修改返回資料 ```js 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` 中獲取實時資料並修改。 5. 清除快取 ```js try { await miniProgram.callWxMethod('clearStorage') } catch (e) { await console.log(`清除快取報錯: `) await console.log(e) } ``` 6. 點選重新整理按鈕 ```js const page = await miniProgram.currentPage() const $refreshBtn = await page.$('.page-error__refresh-btn') // 同 WXSS,僅支援部分 CSS 選擇器 await $refreshBtn.tap() ``` 7. 取消攔截,恢復網路 ```js 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 欄位定義指令碼命令: ```json "scripts": { "start": "node pages/index/index.js" }, ``` **執行環境:** - 安裝 Node.js 並且版本大於 8.0 - 基礎庫版本為 2.7.3 及以上 - 開發者工具版本為 1.02.1907232 及以上 **執行:** 在終端切入到專案根目錄路徑,輸入以下命令列,就可以啟動測試工具,執行測試指令碼。 ``` $ npm run start ``` ### 測試結果 **人工比對截圖結果:** ![測試結果圖](https://img2020.cnblogs.com/other/1992869/202007/1992869-20200716094652287-2108251407.png) **執行指令碼示例:** ![執行指令碼示例](https://img2020.cnblogs.com/other/1992869/202007/1992869-20200716094656148-344929587.gif) ### 使用 SDK,你必須知道 Shadow DOM 當我們想控制小程式頁面時,需獲取頁面例項 page,利用 page 提供的方法控制頁面內的元素。 比如,當我們想點選頁面中搜索框時,我們一般會這麼做: ```js const page = await miniProgram.currentPage() const $searchBar = await page.$('search-bar') await $searchBar.tap() ``` 但這樣真的可行嗎?答案是: 試試就知道了。 執行這段測試指令碼後生成的截圖: ![沒有觸發點選](https://img2020.cnblogs.com/other/1992869/202007/1992869-20200716094658217-1127429323.png) 我們得到的結果是:根本沒有觸發點選事件。 **Shadow DOM:** 它是 HTML 的一個規範,它允許在文件( document )渲染時插入一顆DOM元素子樹,但是這個子樹不在主 DOM 樹中。 它允許瀏覽器開發者封裝自己的 HTML 標籤、css 樣式和特定的 javascript 程式碼、同時開發人員也可以建立類