
作者:阿翔
如果你已經閱讀過 《京喜前端自動化測驗之路(一)》,可跳過前言部分閱讀,
前言
京喜(原京東拼購)專案,作為京東戰略級業務,擁有千萬級別的流量入口,為了保障線上業務的穩定運行,每月例行開展前端容災演習,主要包含小程式及 H5 版本,要求各頁面各模塊在例外情況下進行適當的降級處理,不能出現空窗、樣式錯亂、不合理的錯誤提示等體驗問題,
容災演習是一項長期持續的作業,且涉及頁面功能及場景多,人工的切換場景模擬例外導致演習效率較低,因此想通過開發自動化測驗工具來提升演習效率,讓容災演習作業隨時可以輕松開展,由于京喜 H5 和小程式場景差異比較大,自動化測驗分 H5 和小程式兩部分進行,前期已經分享過 H5 的自動化測驗方案 —— 京喜前端自動化測驗之路(一),本文則主要講述小程式版的自動化測驗方案,
綜上所述,我們希望京喜小程式自動化測驗工具可以提供以下功能:
- 訪問目標頁面,對頁面進行截圖;
- 模擬用戶點擊、滑動頁面操作;
- 網路攔截、模擬例外情況(介面回應碼 500、介面回傳資料例外);
- 操作快取資料(模擬有無快取的場景等),
小程式自動化 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 對介面回傳進行修改來模擬例外情況,驗證各頁面各模塊的降級處理符合預期,
現階段的容災演習自動化方案:
我們將容災演習程序分為自動化流程和人工操作兩部分,
自動化流程:
- 啟動微信開發者工具(開發版);
- 訪問目標頁面,模擬用戶點擊、滑動等行為;
- 模擬例外場景:攔截網路請求,修改介面回傳資料(介面回傳 500、例外資料等);
- 生成截圖,
人工操作:
自動化腳本執行完畢后,人工比對各個場景的截圖,判斷是否符合預期,
方案流程圖:

開發實錄
快速創建測驗用例
為了提高測驗腳本的可維護性、擴展性,我們將測驗用例的資訊都配置到 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 回應碼的例外場景,驗證主介面的例外處理機制是否完善、用戶體驗是否友好,
預期效果:
- 主介面例外,有快取資料,顯示快取資料
- 主介面例外,無快取資料,則請求容災介面,顯示容災兜底資料
- 主介面、容災介面例外,無快取資料,顯示信例外息、重繪按鈕
- 恢復網路,點擊重繪按鈕,顯示正常資料
測驗流程:

場景實作:
根據測驗流程以及配置的測驗用例資訊,撰寫測驗腳本,模擬測驗用例場景:
- 訪問頁面
const miniProgram = await automator.launch({
cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 開發者工具命令列工具(絕對路徑)
projectPath: 'jx_project', // 專案地址(絕對路徑)
})
await miniProgram.reLaunch('/pages/index/index')
- 生成截圖
await miniProgram.screenshot({
path: 'jx_weapp_index_home_page_500.png'
})
- 模擬例外資料
const getMockData = (url, mockType, mockValue) => {
const result = {
data: 'test',
cookies: [],
header: {},
statusCode: 200,
}
switch (mockType) {
case 'data':
result.data = https://www.cnblogs.com/o2team/p/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 = https://www.cnblogs.com/o2team/p/[
getMockData(api1,'statusCode', 500), // 模擬介面回傳 500
getMockData(api2, 'data', mockValue) // 模擬介面回傳例外資料
...
]
- 攔截介面請求,修改回傳資料
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中獲取實時資料并修改,
- 清除快取
try {
await miniProgram.callWxMethod('clearStorage')
} catch (e) {
await console.log(`清除快取報錯: `)
await console.log(e)
}
- 點擊重繪按鈕
const page = await miniProgram.currentPage()
const $refreshBtn = await page.$('.page-error__refresh-btn') // 同 WXSS,僅支持部分 CSS 選擇器
await $refreshBtn.tap()
- 取消攔截,恢復網路
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),不定時推送文章:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/36866.html
標籤:JavaScript
上一篇:詳解JS中定時器setInterval和setTImeout的this指向問題
下一篇:d3js scales深入理解
