puppeteer 自动性能测试

目的

用puppeteer对多个移动端页面进行性能测试,要求同一页面多次运行求平均值,结果导出为excel文件,需要wifi环境和弱网环境两份测试数据。

开始前

先简单的了解一下puppeteer 是什么? 先附上官方中文文档
官方的回答是:

Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless模式运行,但是可以通过修改配置文件运行“有头”模式。你可以在浏览器中手动执行的绝大多数操作都可以使用 Puppeteer 来完成!

安装步骤:

cnpm i puppeteer

用cnpm安装较快,因为安装puppeteer的同时会下载一个大小170MB~250MB的chromium浏览器(按操作系统而定)。

先来一个最简单的例子:

const puppeteer = require('puppeteer')

const test = (async () => {
  // 创建一个浏览器实例
  // 此处传入配置项设置headless为false,否则程序将在后台执行,不会打开浏览器
  const browser = await puppeteer.launch({headless: false}) 
  // 在刚才的浏览器上打开一个空页面
  const page = await browser.newPage()
  // 等待页面创建完成后跳转到简书官网
  await page.goto('https://www.jianshu.com')
})

test()

运行这段代码node将会开启一个chromium浏览器打开一个页面跳转到首页。

开始

现在我们已经可以打开一个页面,那么接下来

1 如何获取页面性能数据

在平时调试中我们通常用两种方法来看页面性能数据:
window.performance.timing 和 chrome的DevTools中的Performance工具。
经查阅得知,window.performance.timing是执行于网页上下文的,而DevTools是执行于浏览器上下文的。这边给出获取两种数据的代码

const puppeteer = require('puppeteer')

const test = (async () => {
  const browser = await puppeteer.launch({headless: false})
  const page = await browser.newPage()
  await page.goto('https://www.jianshu.com')
  // 获取 DevTools 中的数据
  let devData = await page._client.send('Performance.getMetrics')
  // 获取 window 中的数据
  let windowData = JSON.parse(await page.evaluate(() => {
    JSON.stringify(window.performance.timing
  })
})

test()

本次需求只需要得到页面的白屏时间和整屏时间,选择window中的数据是最便捷的,若想获取其他更为高级的数据,可选用DevTools中的数据。

2 对单个页面测试

为了最大化减少网速对页面加载的影响,我们单次只开启一个浏览器,一个页面,收集到这次数据后关闭浏览器。在chromium中关闭浏览器即可清空缓存,下次打开浏览器时所有数据都会重新加载,故检测一个页面的性能可使用如下代码

const puppeteer = require('puppeteer')

const url = 'https://www.jianshu.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 //整屏时间
  result.domTime = timeData.domContentLoadedEventStart  - timeData.domainLookupStart //白屏时间
  return result
}

test()

以上代码即可对简书官网进行20次性能测试求平均值。输出结果如下


牛刀小试
3 如何将数据转为excel文件

对了了各种将json转为excel的包,最终选择了功能和上手难度都挺均衡的node-xlsx

代码如下

const fs = require('fs')
const xlsx = require('node-xlsx')

const output = (async (timeData) => {
  let data = [['所属', '链接', '平均白屏时间/ms', '平均整屏时间/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); 
  let time = new Date().getTime()
  fs.writeFileSync(`./${time}.xlsx`, buffer, 'binary')
})

重构

以上我们已经可以对一个页面进行性能测试了,但是要完成需求还需要进一步完善代码。完善,先从重构开始
以下是重构后的目录结构及代码

目录结构

index.js

const performanceTest = require('./src/performanceTest')

performanceTest()

urlList.js

module.exports = [
  {name: '简书', url: 'https://www.jianshu.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  // 单个页面循环次数
let time = 2000 // 
const performanceTest = (async () => {
  const len = urlList.length
  let result = []
  const options = {
    headless: false // 默认为true,不会出现浏览器,后台静默执行脚本,设置为false后会自动弹浏览器执行脚本
  }
  for (let i = 0; i < len; i++) {
    result.push(await urlTest(options, urlList[i], times, time))
    // 获取urlTest方法返回的数据并存入result中
  }
  output(result) // 导出为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 //整屏时间
  result.domTime = timeData.domContentLoadedEventStart  - timeData.domainLookupStart //白屏时间
  return result
}

module.exports = dataHandle

output.js

const fs = require('fs')
const xlsx = require('node-xlsx')

const output = (async (timeData) => {
  let data = [['所属', '链接', '平均白屏时间/ms', '平均整屏时间/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将页面以移动端的方式打开

puppeteer有自带的包,可以将页面设置为移动端模式再进行操作

const devices = require('puppeteer/DeviceDescriptors')
const iPhone = devices['iPhone 6']

将变量iPhone作为参数传入page.emulate()方法中即可以移动端模式打开页面。

2 对网速进行限制

这边我们要用DevTools中的Network对网速进行限制,代码如下

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
    });

结语

puppeteer的功能十分强大,本次测试只是用到了九牛一毛而已(坐我旁边的大佬用puppeteer写了个域名防封杀系统),但是不管怎么说我已经可以通过这些简单的脚本获取到我想要的数据。

最后附上一个 DevTools Protocol 链接,可以通过page._client.send()方法向chromium发送DevTools原始指令

小技能+1。

路漫漫其修远兮,吾将上下而求索。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容