知識(shí)
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X(jué)表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷(xiāo)的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷(xiāo)工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!
您當(dāng)前位置>首頁(yè) » 新聞資訊 » 小程序相關(guān) >
京喜前端自動(dòng)化測(cè)試之路(小程序篇)
發(fā)表時(shí)間:2021-1-11
發(fā)布人:葵宇科技
瀏覽次數(shù):103
如果你已經(jīng)閱讀過(guò) 《京喜前端自動(dòng)化測(cè)試之路(一)》,可跳過(guò)前言部分閱讀。
前言
京喜(原京東拼購(gòu))項(xiàng)目,作為京東戰(zhàn)略級(jí)業(yè)務(wù),擁有千萬(wàn)級(jí)別的流量入口。為了保障線(xiàn)上業(yè)務(wù)的穩(wěn)定運(yùn)行,每月例行開(kāi)展前端容災(zāi)演習(xí),主要包含小程序及 H5 版本,要求各頁(yè)面各模塊在異常情況下進(jìn)行適當(dāng)?shù)慕导?jí)處理,不能出現(xiàn)空窗、樣式錯(cuò)亂、不合理的錯(cuò)誤提示等體驗(yàn)問(wèn)題。
容災(zāi)演習(xí)是一項(xiàng)長(zhǎng)期持續(xù)的工作,且涉及頁(yè)面功能及場(chǎng)景多,人工的切換場(chǎng)景模擬異常導(dǎo)致演習(xí)效率較低,因此想通過(guò)開(kāi)發(fā)自動(dòng)化測(cè)試工具來(lái)提升演習(xí)效率,讓容災(zāi)演習(xí)工作隨時(shí)可以輕松開(kāi)展。由于京喜 H5 和小程序場(chǎng)景差異比較大,自動(dòng)化測(cè)試分 H5 和小程序兩部分進(jìn)行。前期已經(jīng)分享過(guò) H5 的自動(dòng)化測(cè)試方案 —— 京喜前端自動(dòng)化測(cè)試之路(一)
,本文則主要講述小程序版的自動(dòng)化測(cè)試方案。
綜上所述,我們希望京喜小程序自動(dòng)化測(cè)試工具可以提供以下功能:
- 訪(fǎng)問(wèn)目標(biāo)頁(yè)面,對(duì)頁(yè)面進(jìn)行截圖;
- 模擬用戶(hù)點(diǎn)擊、滑動(dòng)頁(yè)面操作;
- 網(wǎng)絡(luò)攔截、模擬異常情況(接口響應(yīng)碼 500、接口返回?cái)?shù)據(jù)異常);
- 操作緩存數(shù)據(jù)(模擬有無(wú)緩存的場(chǎng)景等)。
小程序自動(dòng)化 SDK
聊到小程序的自動(dòng)化工具,微信官方為開(kāi)發(fā)者提供了一套小程序自動(dòng)化 SDK —— miniprogram-automator , 我們不需要關(guān)注技術(shù)選型,可直接使用。
小程序自動(dòng)化 SDK 為開(kāi)發(fā)者提供了一套通過(guò)外部腳本操控小程序的方案,從而實(shí)現(xiàn)小程序自動(dòng)化測(cè)試的目的。
如果你之前使用過(guò) Selenium WebDriver 或者 Puppeteer,那你可以很容易快速上手。小程序自動(dòng)化 SDK 與它們的工作原理是類(lèi)似的,主要區(qū)別在于控制對(duì)象由瀏覽器換成了小程序。
特性
通過(guò)該 SDK,你可以做到以下事情:
- 控制小程序跳轉(zhuǎn)到指定頁(yè)面
- 獲取小程序頁(yè)面數(shù)據(jù)
- 獲取小程序頁(yè)面元素狀態(tài)
- 觸發(fā)小程序元素綁定事件
- 往 AppService 注入代碼片段
- 調(diào)用 wx 對(duì)象上任意接口
- ...
示例
const automator = require('miniprogram-automator')
automator
.launch({
cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 工具 cli 位置(絕對(duì)路徑)
projectPath: 'path/to/project', // 項(xiàng)目文件地址(絕對(duì)路徑)
})
.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()
})
復(fù)制代碼
綜上所述,我們選擇使用官方維護(hù)的 SDK —— miniprogram-automator
開(kāi)發(fā)小程序的自動(dòng)化測(cè)試工具,通過(guò) SDK 提供的一系列 API ,實(shí)現(xiàn)訪(fǎng)問(wèn)目標(biāo)頁(yè)面、模擬異常場(chǎng)景、生成截圖的過(guò)程自動(dòng)化。最后再通過(guò)人工比對(duì)截圖,判斷頁(yè)面降級(jí)處理是否符合預(yù)預(yù)期、用戶(hù)體驗(yàn)是否友好。
實(shí)現(xiàn)方案
原來(lái)的容災(zāi)演習(xí)過(guò)程:
小程序的通信方式改成 HTTPS ,通過(guò) Whistle 對(duì)接口返回進(jìn)行修改來(lái)模擬異常情況,驗(yàn)證各頁(yè)面各模塊的降級(jí)處理符合預(yù)期。
現(xiàn)階段的容災(zāi)演習(xí)自動(dòng)化方案:
我們將容災(zāi)演習(xí)過(guò)程分為自動(dòng)化流程
和人工操作
兩部分。
自動(dòng)化流程:
- 啟動(dòng)微信開(kāi)發(fā)者工具(開(kāi)發(fā)版);
- 訪(fǎng)問(wèn)目標(biāo)頁(yè)面,模擬用戶(hù)點(diǎn)擊、滑動(dòng)等行為;
- 模擬異常場(chǎng)景:攔截網(wǎng)絡(luò)請(qǐng)求,修改接口返回?cái)?shù)據(jù)(接口返回 500、異常數(shù)據(jù)等);
- 生成截圖。
人工操作:
自動(dòng)化腳本執(zhí)行完畢后,人工比對(duì)各個(gè)場(chǎng)景的截圖,判斷是否符合預(yù)期。
方案流程圖:
開(kāi)發(fā)實(shí)錄
快速創(chuàng)建測(cè)試用例
為了提高測(cè)試腳本的可維護(hù)性、擴(kuò)展性,我們將測(cè)試用例的信息都配置到 JSON 文件中,這樣編寫(xiě)測(cè)試腳本的時(shí)候,我們只需關(guān)注測(cè)試流程的實(shí)現(xiàn)。
測(cè)試用例 JSON 數(shù)據(jù)配置包括公用數(shù)據(jù)(global)
和私有數(shù)據(jù)
:
公用數(shù)據(jù)(global)
:各測(cè)試用例都需要用到的數(shù)據(jù),如:模擬訪(fǎng)問(wèn)的目標(biāo)頁(yè)面地址、名字、描述、設(shè)備類(lèi)型等。
私有數(shù)據(jù)
: 各測(cè)試用例特定的數(shù)據(jù),如測(cè)試模塊信息、api 地址、測(cè)試場(chǎng)景、預(yù)期結(jié)果、截圖名字等數(shù)據(jù)。
{
"global": {
"url": "/pages/index/index",
"pageName": "index",
"pageDesc": "首頁(yè)",
"device": "iPhone X"
},
"homePageApi": {
"id": 1,
"module": "home_page_api",
"moduleDesc": "首頁(yè)主接口",
"api": "https://xxx",
"operation": "模擬響應(yīng)碼 500",
"expectRules": [
"1. 有緩存數(shù)據(jù),顯示容災(zāi)兜底數(shù)據(jù)",
"2. 請(qǐng)求容災(zāi)接口,顯示容災(zāi)兜底數(shù)據(jù)",
"3. 容災(zāi)接口異常,顯示信異常息、刷新按鈕",
"4. 恢復(fù)網(wǎng)絡(luò),點(diǎn)擊刷新按鈕,顯示正常數(shù)據(jù)"
],
"screenshot": [
{
"name": "normal",
"desc": "正常場(chǎng)景"
},
{
"name": "500_cache",
"desc": "有緩存-主接口返回500"
},
{
"name": "500_no_cache",
"desc": "無(wú)緩存-主接口返回500-容災(zāi)兜底數(shù)據(jù)"
},
{
"name": "500_no_cache_500_disaster",
"desc": "無(wú)緩存-主接口返回500-容災(zāi)兜底接口返回500"
},
{
"name": "500_no_cache_recover",
"desc": "無(wú)緩存-返回500-恢復(fù)網(wǎng)絡(luò)"
}
]
},
…
}
復(fù)制代碼
編寫(xiě)測(cè)試腳本
我們以京喜首頁(yè)主接口的測(cè)試用例為例子,通過(guò)模擬主接口返回 500 響應(yīng)碼的異常場(chǎng)景,驗(yàn)證主接口的異常處理機(jī)制是否完善、用戶(hù)體驗(yàn)是否友好。
預(yù)期效果:
- 主接口異常,有緩存數(shù)據(jù),顯示緩存數(shù)據(jù)
- 主接口異常,無(wú)緩存數(shù)據(jù),則請(qǐng)求容災(zāi)接口,顯示容災(zāi)兜底數(shù)據(jù)
- 主接口、容災(zāi)接口異常,無(wú)緩存數(shù)據(jù),顯示信異常息、刷新按鈕
- 恢復(fù)網(wǎng)絡(luò),點(diǎn)擊刷新按鈕,顯示正常數(shù)據(jù)
測(cè)試流程:
場(chǎng)景實(shí)現(xiàn):
根據(jù)測(cè)試流程以及配置的測(cè)試用例信息,編寫(xiě)測(cè)試腳本,模擬測(cè)試用例場(chǎng)景:
- 訪(fǎng)問(wèn)頁(yè)面
const miniProgram = await automator.launch({
cliPath: '/Applications/wechatwebdevtools.app/Contents/MacOS/cli', // 開(kāi)發(fā)者工具命令行工具(絕對(duì)路徑)
projectPath: 'jx_project', // 項(xiàng)目地址(絕對(duì)路徑)
})
await miniProgram.reLaunch('/pages/index/index')
復(fù)制代碼
- 生成截圖
await miniProgram.screenshot({
path: 'jx_weapp_index_home_page_500.png'
})
復(fù)制代碼
- 模擬異常數(shù)據(jù)
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) // 修改返回?cái)?shù)據(jù)
break
case 'cookies':
result.cookies = mockValue // 修改返回?cái)?shù)據(jù)
break
case 'header':
result.header = mockValue // 修改返回響應(yīng)頭
break
case 'statusCode':
result.statusCode = mockValue // 修改返回響應(yīng)頭
break
}
return {
rule: url,
result
}
}
// 修改本地存儲(chǔ)數(shù)據(jù)
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) // 模擬接口返回異常數(shù)據(jù)
...
]
復(fù)制代碼
- 攔截接口請(qǐng)求,修改返回?cái)?shù)據(jù)
const interceptAPI = async (miniProgram, url, mockData) => {
try {
await miniProgram.mockWxMethod(
'request',
function(obj, data) { // 處理返回函數(shù)
for (let i = 0, len = data.length; i < len; i++) {
const item = data[i]
// 命中規(guī)則的返回 mockData
if (obj.url.indexOf(item.rule) > -1) {
return item.result
}
}
// 沒(méi)命中規(guī)則的真實(shí)訪(fǎng)問(wèn)后臺(tái)
return new Promise(resolve => {
obj.success = res => resolve(res)
obj.fail = res => resolve(res)
/ origin 指向原始方法
this.origin(obj)
})
},
mockData, // 傳入 mock 數(shù)據(jù)
)
} catch (e) {
console.error(`攔截【${url}】API報(bào)錯(cuò)`)
console.error(e)
}
}
await interceptAPI(interceptAPI, url, mockData)
復(fù)制代碼
miniProgram.mockWxMethod
:覆蓋 wx 對(duì)象上指定方法的調(diào)用結(jié)果。利用該 API,可以覆蓋 wx.request API,攔截網(wǎng)絡(luò)請(qǐng)求,修改返回?cái)?shù)據(jù)。- 目前是本地存儲(chǔ)一份接口返回的 JSON 數(shù)據(jù),通過(guò)修改本地的 JSON 數(shù)據(jù)生成 mockData。若需要修改接口實(shí)時(shí)返回的數(shù)據(jù),可在
obj.success
中獲取實(shí)時(shí)數(shù)據(jù)并修改。
- 清除緩存
try {
await miniProgram.callWxMethod('clearStorage')
} catch (e) {
await console.log(`清除緩存報(bào)錯(cuò): `)
await console.log(e)
}
復(fù)制代碼
- 點(diǎn)擊刷新按鈕
const page = await miniProgram.currentPage()
const $refreshBtn = await page.$('.page-error__refresh-btn') // 同 WXSS,僅支持部分 CSS 選擇器
await $refreshBtn.tap()
復(fù)制代碼
- 取消攔截,恢復(fù)網(wǎng)絡(luò)
const cancelInterceptAPI = async (miniProgram) => {
try {
await miniProgram.restoreWxMethod('request') // 重置 wx.request ,消除 mockWxMethod 調(diào)用的影響。
} catch (e) {
console.error(`取消攔截【${url}】API報(bào)錯(cuò)`)
console.error(e)
}
}
await cancelInterceptAPI(miniProgram)
復(fù)制代碼
啟動(dòng)自動(dòng)化測(cè)試
由于第一階段的測(cè)試工具尚未平臺(tái)化,先通過(guò)在終端輸入命令行,運(yùn)行腳本的方式,啟動(dòng)自動(dòng)化測(cè)試。
在項(xiàng)目的 package.json 文件中,使用 scripts 字段定義腳本命令:
"scripts": {
"start": "node pages/index/index.js"
},
復(fù)制代碼
運(yùn)行環(huán)境:
- 安裝 Node.js 并且版本大于 8.0
- 基礎(chǔ)庫(kù)版本為 2.7.3 及以上
- 開(kāi)發(fā)者工具版本為 1.02.1907232 及以上
運(yùn)行:
在終端切入到項(xiàng)目根目錄路徑,輸入以下命令行,就可以啟動(dòng)測(cè)試工具,運(yùn)行測(cè)試腳本。
$ npm run start
復(fù)制代碼
測(cè)試結(jié)果
運(yùn)行腳本示例:
使用 SDK,你必須知道 Shadow DOM
當(dāng)我們想控制小程序頁(yè)面時(shí),需獲取頁(yè)面實(shí)例 page,利用 page 提供的方法控制頁(yè)面內(nèi)的元素。
比如,當(dāng)我們想點(diǎn)擊頁(yè)面中搜索框時(shí),我們一般會(huì)這么做:
const page = await miniProgram.currentPage()
const $searchBar = await page.$('search-bar')
await $searchBar.tap()
復(fù)制代碼
但這樣真的可行嗎?答案是:
試試就知道了。
運(yùn)行這段測(cè)試腳本后生成的截圖:
我們得到的結(jié)果是:根本沒(méi)有觸發(fā)點(diǎn)擊事件。
Shadow DOM:
它是 HTML 的一個(gè)規(guī)范,它允許在文檔( document )渲染時(shí)插入一顆DOM元素子樹(shù),但是這個(gè)子樹(shù)不在主 DOM 樹(shù)中。
它允許瀏覽器開(kāi)發(fā)者封裝自己的 HTML 標(biāo)簽、css 樣式和特定的 javascript 代碼、同時(shí)開(kāi)發(fā)人員也可以創(chuàng)建類(lèi)似 <input>、<video>、<audio>
等、這樣的自定義的一級(jí)標(biāo)簽。創(chuàng)建這些標(biāo)簽內(nèi)容相關(guān)的 API,可以被叫做 Web Component。
Shadow DOM 的關(guān)鍵所在,它可以將一個(gè)隱藏的、獨(dú)立的 DOM 附加到一個(gè)元素上。
Shadow host:
一個(gè)常規(guī) DOM 節(jié)點(diǎn),Shadow DOM 會(huì)被附加到這個(gè)節(jié)點(diǎn)上。它是 Shadow DOM 的一個(gè)宿主元素。比如:<input />、<audio>、<video>
標(biāo)簽,就是 Shadow DOM 的宿主元素。Shadow tree:
Shadow DOM 內(nèi)部的 DOM 樹(shù)。Shadow root:
Shadow DOM 的根節(jié)點(diǎn)。通過(guò)createShadowRoot
返回的文檔片段被稱(chēng)為 shadow-root , 它和它的后代元素,都會(huì)對(duì)用戶(hù)隱藏。
回到我們剛剛的問(wèn)題:
由于小程序使用了 Shadow DOM,因此我們不能直接通過(guò) page 實(shí)例獲取到搜索框真實(shí) DOM。我們看到的頁(yè)面中渲染的搜索框,實(shí)際上是一個(gè) Shadow DOM。因此,我們必須先獲取到搜索框 Shadow DOM 的宿主元素,并通過(guò)宿主元素獲取到搜索框真實(shí)的 DOM,最后觸發(fā)真實(shí) DOM 的點(diǎn)擊事件。
const page = await miniProgram.currentPage()
const $searchBarShadow = await page.$('search-bar')
const $searchBar = await $searchBarShadow.$('.search-bar')
const { height } = await $searchBar.size()
復(fù)制代碼
運(yùn)行這段測(cè)試腳本后生成的截圖:
從截圖可以看到,觸發(fā)了搜索框的點(diǎn)擊事件。
更多測(cè)試場(chǎng)景實(shí)現(xiàn)
1. 下拉刷新
const pullDownRefresh = async (miniProgram) => {
try {
await miniProgram.callWxMethod('startPullDownRefresh')
} catch (e) {
console.error('下拉刷新操作失敗')
console.error(e)
}
}
復(fù)制代碼
2. 滾動(dòng)到指定 DOM
const page = await miniProgram.currentPage() // 獲取頁(yè)面實(shí)例
const $recommendTabShadow = await page.$('recommend-tab') // 獲取Shadow DOM
const $recommendTab = await $recommendTabShadow.$('.recommend') // 獲取真實(shí) DOM
const { top } = await $recommendTab.offset() // 獲取DOM 定位
await miniProgram.pageScrollTo(top) // 滾動(dòng)到指定DOM
復(fù)制代碼
3. 事件
- 日志打??;
- 監(jiān)聽(tīng)頁(yè)面崩潰事件
// 日志打印時(shí)觸發(fā)
miniProgram.on('console', msg => {
console.log(msg.type, msg.args)
})
})
// 頁(yè)面 JS 出錯(cuò)時(shí)觸發(fā)
page.on('error', (e) => {
console.log(e)
})
復(fù)制代碼
結(jié)語(yǔ)
第一階段的小程序自動(dòng)化測(cè)試之路告一段落。和 H5 自動(dòng)化測(cè)試一樣,容災(zāi)演習(xí)已實(shí)現(xiàn)了半自動(dòng)化,可通過(guò)在終端運(yùn)行測(cè)試腳本,模擬異常場(chǎng)景自動(dòng)生成截圖,再配合人工比對(duì)截圖操作,判斷演習(xí)結(jié)果是否符合預(yù)期。目前已投入到每個(gè)月的容災(zāi)演習(xí)中使用。
由于 H5 和小程序的差異比較大,第一階段的自動(dòng)化測(cè)試分兩端進(jìn)行,測(cè)試腳本語(yǔ)法也是截然不同,需要同時(shí)維護(hù)兩套測(cè)試工具。為了降低維護(hù)成本,提升測(cè)試腳本的開(kāi)發(fā)效率,我們正在研發(fā)第二階段的自動(dòng)化測(cè)試工具,一套代碼支持兩端測(cè)試,目前已經(jīng)進(jìn)入內(nèi)測(cè)階段。更多彩蛋,敬請(qǐng)期待第二階段自動(dòng)化測(cè)試工具——多端自動(dòng)化測(cè)試 SDK 。
歡迎關(guān)注凹凸實(shí)驗(yàn)室博客:aotu.io
或者關(guān)注凹凸實(shí)驗(yàn)室公眾號(hào)(AOTULabs),不定時(shí)推送文章
相關(guān)案例查看更多
相關(guān)閱讀
- 云南網(wǎng)站建設(shè)快速優(yōu)化
- 昆明小程序代建
- 網(wǎng)絡(luò)公司報(bào)價(jià)
- 云南百度小程序
- 小程序設(shè)計(jì)
- 海報(bào)插件
- 報(bào)廢車(chē)
- 云南小程序代建
- 云南網(wǎng)站制作哪家好
- 云南省城鄉(xiāng)建設(shè)廳網(wǎng)站
- 云南省建設(shè)廳網(wǎng)站官網(wǎng)
- 云南網(wǎng)站優(yōu)化公司
- 日歷組件
- 昆明網(wǎng)站建設(shè)公司
- web開(kāi)發(fā)
- 開(kāi)發(fā)微信小程序
- 網(wǎng)站建設(shè)招商
- 云南網(wǎng)頁(yè)制作
- 國(guó)內(nèi)知名網(wǎng)站建設(shè)公司排名
- 云南小程序公司
- php網(wǎng)站
- 搜索排名
- 小程序技術(shù)
- 昆明做網(wǎng)站
- 汽車(chē)報(bào)廢軟件
- 小程序被騙
- 英文網(wǎng)站建設(shè)公司
- 小程序用戶(hù)登錄
- 百度小程序開(kāi)發(fā)
- 云南手機(jī)網(wǎng)站建設(shè)