目的
用puppeteer對(duì)多個(gè)移動(dòng)端頁(yè)面進(jìn)行性能測(cè)試,要求同一頁(yè)面多次運(yùn)行求平均值,結(jié)果導(dǎo)出為excel文件,需要wifi環(huán)境和弱網(wǎng)環(huán)境兩份測(cè)試數(shù)據(jù)规求。
開(kāi)始前
先簡(jiǎn)單的了解一下puppeteer 是什么稿壁? 先附上官方中文文檔
官方的回答是:
Puppeteer 是一個(gè) Node 庫(kù),它提供了一個(gè)高級(jí) API 來(lái)通過(guò) DevTools協(xié)議控制 Chromium 或 Chrome奥秆。Puppeteer 默認(rèn)以 headless模式運(yùn)行逊彭,但是可以通過(guò)修改配置文件運(yùn)行“有頭”模式。你可以在瀏覽器中手動(dòng)執(zhí)行的絕大多數(shù)操作都可以使用 Puppeteer 來(lái)完成构订!
安裝步驟:
cnpm i puppeteer
用cnpm安裝較快侮叮,因?yàn)榘惭bpuppeteer的同時(shí)會(huì)下載一個(gè)大小170MB~250MB的chromium瀏覽器(按操作系統(tǒng)而定)。
先來(lái)一個(gè)最簡(jiǎn)單的例子:
const puppeteer = require('puppeteer')
const test = (async () => {
// 創(chuàng)建一個(gè)瀏覽器實(shí)例
// 此處傳入配置項(xiàng)設(shè)置headless為false悼瘾,否則程序?qū)⒃诤笈_(tái)執(zhí)行囊榜,不會(huì)打開(kāi)瀏覽器
const browser = await puppeteer.launch({headless: false})
// 在剛才的瀏覽器上打開(kāi)一個(gè)空頁(yè)面
const page = await browser.newPage()
// 等待頁(yè)面創(chuàng)建完成后跳轉(zhuǎn)到簡(jiǎn)書(shū)官網(wǎng)
await page.goto('http://www.reibang.com')
})
test()
運(yùn)行這段代碼node將會(huì)開(kāi)啟一個(gè)chromium瀏覽器打開(kāi)一個(gè)頁(yè)面跳轉(zhuǎn)到首頁(yè)谷异。
開(kāi)始
現(xiàn)在我們已經(jīng)可以打開(kāi)一個(gè)頁(yè)面,那么接下來(lái)
1 如何獲取頁(yè)面性能數(shù)據(jù)
在平時(shí)調(diào)試中我們通常用兩種方法來(lái)看頁(yè)面性能數(shù)據(jù):
window.performance.timing 和 chrome的DevTools中的Performance工具锦聊。
經(jīng)查閱得知歹嘹,window.performance.timing是執(zhí)行于網(wǎng)頁(yè)上下文的,而DevTools是執(zhí)行于瀏覽器上下文的孔庭。這邊給出獲取兩種數(shù)據(jù)的代碼
const puppeteer = require('puppeteer')
const test = (async () => {
const browser = await puppeteer.launch({headless: false})
const page = await browser.newPage()
await page.goto('http://www.reibang.com')
// 獲取 DevTools 中的數(shù)據(jù)
let devData = await page._client.send('Performance.getMetrics')
// 獲取 window 中的數(shù)據(jù)
let windowData = JSON.parse(await page.evaluate(() => {
JSON.stringify(window.performance.timing
})
})
test()
本次需求只需要得到頁(yè)面的白屏?xí)r間和整屏?xí)r間尺上,選擇window中的數(shù)據(jù)是最便捷的,若想獲取其他更為高級(jí)的數(shù)據(jù)圆到,可選用DevTools中的數(shù)據(jù)怎抛。
2 對(duì)單個(gè)頁(yè)面測(cè)試
為了最大化減少網(wǎng)速對(duì)頁(yè)面加載的影響,我們單次只開(kāi)啟一個(gè)瀏覽器芽淡,一個(gè)頁(yè)面马绝,收集到這次數(shù)據(jù)后關(guān)閉瀏覽器。在chromium中關(guān)閉瀏覽器即可清空緩存挣菲,下次打開(kāi)瀏覽器時(shí)所有數(shù)據(jù)都會(huì)重新加載富稻,故檢測(cè)一個(gè)頁(yè)面的性能可使用如下代碼
const puppeteer = require('puppeteer')
const url = 'http://www.reibang.com'
const times = 20
const time = 3000
let result = []
let temp = []
const test = (async () => {
for (let i = 0; i < times; i++) {
let browser = await puppeteer.launch({headless: false})
let page = await browser.newPage()
await page.goto(url)
await page.waitFor(time)
let timeData = JSON.parse(await page.evaluate(
() => JSON.stringify(window.performance.timing)
))
temp.push(dataHandler(timeData))
await browser.close()
}
result.push(url)
let domSum = 0
let holeSum = 0
for (let k = 0; k < times; k++) {
domSum += temp[k].domTime
holeSum += temp[k].holeTime
}
result.push(domSum / times)
result.push(holeSum / times)
console.log(result)
})
const dataHandler = (timeData) => {
let result = {}
result.holeTime = timeData.loadEventEnd - timeData.domainLookupStart //整屏?xí)r間
result.domTime = timeData.domContentLoadedEventStart - timeData.domainLookupStart //白屏?xí)r間
return result
}
test()
以上代碼即可對(duì)簡(jiǎn)書(shū)官網(wǎng)進(jìn)行20次性能測(cè)試求平均值。輸出結(jié)果如下
3 如何將數(shù)據(jù)轉(zhuǎn)為excel文件
對(duì)了了各種將json轉(zhuǎn)為excel的包白胀,最終選擇了功能和上手難度都挺均衡的node-xlsx
代碼如下
const fs = require('fs')
const xlsx = require('node-xlsx')
const output = (async (timeData) => {
let data = [['所屬', '鏈接', '平均白屏?xí)r間/ms', '平均整屏?xí)r間/ms']]
let len = timeData.length
for (let i = 0; i < len; i++) {
data.push(timeData[i])
}
// 設(shè)置單元格列寬
const xlsxOption = {'!cols': [{ wch: 15 }, { wch: 100 }, { wch: 15 }, { wch:15 }]}
var buffer = xlsx.build([{name: "mySheetName", data: data}], xlsxOption);
let time = new Date().getTime()
fs.writeFileSync(`./${time}.xlsx`, buffer, 'binary')
})
重構(gòu)
以上我們已經(jīng)可以對(duì)一個(gè)頁(yè)面進(jìn)行性能測(cè)試了椭赋,但是要完成需求還需要進(jìn)一步完善代碼。完善或杠,先從重構(gòu)開(kāi)始哪怔。
以下是重構(gòu)后的目錄結(jié)構(gòu)及代碼
index.js
const performanceTest = require('./src/performanceTest')
performanceTest()
urlList.js
module.exports = [
{name: '簡(jiǎn)書(shū)', url: 'http://www.reibang.com'},
{name: '百度', url: 'https://www.baidu.com'},
{name: '淘寶', url: 'https://www.taobao.com'}
]
performanceTest.js
const urlTest = require('./urlTest')
const urlList = require('./urlList')
const output = require('./output')
let times = 20 // 單個(gè)頁(yè)面循環(huán)次數(shù)
let time = 2000 //
const performanceTest = (async () => {
const len = urlList.length
let result = []
const options = {
headless: false // 默認(rèn)為true,不會(huì)出現(xiàn)瀏覽器向抢,后臺(tái)靜默執(zhí)行腳本认境,設(shè)置為false后會(huì)自動(dòng)彈瀏覽器執(zhí)行腳本
}
for (let i = 0; i < len; i++) {
result.push(await urlTest(options, urlList[i], times, time))
// 獲取urlTest方法返回的數(shù)據(jù)并存入result中
}
output(result) // 導(dǎo)出為xlsx文件
})
module.exports = performanceTest
urlTest.js
const puppeteer = require('puppeteer')
const dataHandler = require('./dataHandler')
const urlTest = (async (options, obj, times = 1, time = 1000) => {
let result = []
let temp = []
for (let j = 0; j < times; j++) {
let browser = await puppeteer.launch(options).catch((e) => {
let t = new Data().getTime()
console.log(t, obj, j, e)
urlTest(options, obj, times, time)
})
let page = await browser.newPage()
try {
await page.goto(obj.url)
} catch (e) {
let t = new Data().getTime()
console.log(t, obj, j, e)
urlTest(options, obj, times, time)
}
await page.waitFor(time)
let timeData = JSON.parse(await page.evaluate(
() => JSON.stringify(window.performance.timing)
))
temp.push(dataHandler(timeData))
browser.close()
}
result.push(obj.name)
result.push(obj.url)
let domSum = 0
let holeSum = 0
for (let k = 0; k < times; k++) {
domSum += temp[k].domTime
holeSum += temp[k].holeTime
}
result.push(domSum / times)
result.push(holeSum / times)
return result
})
module.exports = urlTest
dataHandler.js
const dataHandle = (timeData) => {
let result = {}
result.holeTime = timeData.loadEventEnd - timeData.domainLookupStart //整屏?xí)r間
result.domTime = timeData.domContentLoadedEventStart - timeData.domainLookupStart //白屏?xí)r間
return result
}
module.exports = dataHandle
output.js
const fs = require('fs')
const xlsx = require('node-xlsx')
const output = (async (timeData) => {
let data = [['所屬', '鏈接', '平均白屏?xí)r間/ms', '平均整屏?xí)r間/ms']]
let len = timeData.length
for (let i = 0; i < len; i++) {
data.push(timeData[i])
}
const xlsxOption = {'!cols': [{ wch: 15 }, { wch: 100 }, { wch: 15 }, { wch:15 }]}
var buffer = xlsx.build([{name: "mySheetName", data: data}], xlsxOption); // Returns a buffer
let time = new Date().getTime()
fs.writeFileSync(`./src/result/${time}.xlsx`, buffer, 'binary')
})
module.exports = output
完善
1將頁(yè)面以移動(dòng)端的方式打開(kāi)
puppeteer有自帶的包,可以將頁(yè)面設(shè)置為移動(dòng)端模式再進(jìn)行操作
const devices = require('puppeteer/DeviceDescriptors')
const iPhone = devices['iPhone 6']
將變量iPhone作為參數(shù)傳入page.emulate()方法中即可以移動(dòng)端模式打開(kāi)頁(yè)面挟鸠。
2 對(duì)網(wǎng)速進(jìn)行限制
這邊我們要用DevTools中的Network對(duì)網(wǎng)速進(jìn)行限制叉信,代碼如下
await page._client.send('Network.emulateNetworkConditions', {
offline: false,
latency: 200, // ms
downloadThroughput: 780 * 1024 / 8, // 780 kb/s
uploadThroughput: 330 * 1024 / 8, // 330 kb/s
});
結(jié)語(yǔ)
puppeteer的功能十分強(qiáng)大,本次測(cè)試只是用到了九牛一毛而已(坐我旁邊的大佬用puppeteer寫(xiě)了個(gè)域名防封殺系統(tǒng))兄猩,但是不管怎么說(shuō)我已經(jīng)可以通過(guò)這些簡(jiǎn)單的腳本獲取到我想要的數(shù)據(jù)忘闻。
最后附上一個(gè) DevTools Protocol 鏈接,可以通過(guò)page._client.send()方法向chromium發(fā)送DevTools原始指令
小技能+1盼理。
路漫漫其修遠(yuǎn)兮,吾將上下而求索。