puppeteer 自動(dòng)性能測(cè)試

目的

用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)及代碼

目錄結(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)兮,吾將上下而求索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末他嫡,一起剝皮案震驚了整個(gè)濱河市样漆,隨后出現(xiàn)的幾起案子检激,更是在濱河造成了極大的恐慌纠永,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件核蘸,死亡現(xiàn)場(chǎng)離奇詭異巍糯,居然都是意外死亡啸驯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)祟峦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)罚斗,“玉大人,你說(shuō)我怎么就攤上這事宅楞≌胱耍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵厌衙,是天一觀的道長(zhǎng)距淫。 經(jīng)常有香客問(wèn)我,道長(zhǎng)婶希,這世上最難降的妖魔是什么榕暇? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮喻杈,結(jié)果婚禮上彤枢,老公的妹妹穿的比我還像新娘。我一直安慰自己奕塑,他們只是感情好堂污,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著龄砰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讨衣。 梳的紋絲不亂的頭發(fā)上换棚,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音反镇,去河邊找鬼固蚤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛歹茶,可吹牛的內(nèi)容都是我干的夕玩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼惊豺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼燎孟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起尸昧,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揩页,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后烹俗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體爆侣,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萍程,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兔仰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茫负。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖乎赴,靈堂內(nèi)的尸體忽然破棺而出忍法,到底是詐尸還是另有隱情,我是刑警寧澤无虚,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布缔赠,位于F島的核電站,受9級(jí)特大地震影響友题,放射性物質(zhì)發(fā)生泄漏嗤堰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一度宦、第九天 我趴在偏房一處隱蔽的房頂上張望踢匣。 院中可真熱鬧,春花似錦戈抄、人聲如沸离唬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)输莺。三九已至,卻和暖如春裸诽,著一層夾襖步出監(jiān)牢的瞬間嫂用,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工丈冬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘱函,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓埂蕊,卻偏偏與公主長(zhǎng)得像往弓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蓄氧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容