Web前端性能自动化监控


脚本语言:Node.JS

使用到的模块:puppeteer、fs


先说一下整体思路:

  • 使用puppeteer访问指定url
  • 获取数据采用的方法是PerformanceAPI,当浏览器打开了指定页面之后使用page.evaluate()命令获取数据
  • 捕获数据输出至json文件

首先安装puppeteer

npm install puppeteer
安装完之后可以开始写脚本,关于puppeteer的基本写法可以参考文档

先把框架写出来

const performance = (async (url) => {

    let browser = await puppeteer.launch({
        headless:false
    })

    let page = await browser.newPage()

    await page.goto(url)

    await browser.close()
})

由于是要获取性能数据,所以为了减少误差需要进行多次测试,思路是使用for循环迭代实现。
具体代码如下:

const performance = (async (url, times) => {

    async function runPage (url) {

        const browser = await puppeteer.launch({
            headless:false
        })

        const page = await browser.newPage()

        await page.setDefaultNavigationTimeout(0);    // 防止访问超时

        await page.waitForTimeout(3000)

        await page.goto(url)

        await page.waitForNetworkIdle()    // 等待至网络连接断开

        await browser.close()
    }

    for (let i = 0; i < times; i++) {

        await runPage(url).then(() => {
            console.log(`第${i+1}次访问完成`)
        })

    }
})

先把每次访问页面的操作单独封装成 runPage 函数,然后使用 for 循环,当 i 小于传入的 times 时循环执行runPage

在 goto(url) 前加入 waitForTimeout 等待3秒后再打开 URL 防止短时间内频繁访问页面触发网站防御机制,提升脚本稳定性。


打开链接并且指定执行次数解决了,接下来是获取每次访问链接时的Performance数据。
获取数据使用的是 PerformanceAPI中performance.getEntries()命令。
performance.getEntries()需要在浏览器的console中输入,所以这里需要用到page.evaluate()命令。
具体代码如下:

const performance = (async (url, times) => {

    async function runPage (url) {

        const browser = await puppeteer.launch({
            headless:false
        })

        const page = await browser.newPage()

        await page.setDefaultNavigationTimeout(0);

        await page.waitForTimeout(3000)

        await page.goto(url)

        await page.waitForNetworkIdle()    // 等待至网络连接断开

        let timingData = JSON.parse(await page.evaluate(
            () => JSON.stringify(window.performance.getEntries())
        ))

        await browser.close()
        
        return timingData
    }

    for (let i = 0; i < times; i++) {

        await runPage(url).then(data => {
            console.log(`第${i+1}次访问完成`)
            console.log(data)
        })

    }
})

runPage函数内在网络连接断开后加入,执行了getEntries操作,并且使用了变量timingData来接收数据,最后在函数末尾return了timingData

在执行runPage时,then里面使用data接收runPage里面return的timingData
这样就拿到了每次打开页面时所有的PerformanceAPI数据,接下来就是处理数据了。


以百度为例子,执行上面的代码打印出来的data数据是这样的,其他的数据被我删了,这些数据的具体解释可以参考这篇文章

[
    {
        name: 'https://www.baidu.com/',
        entryType: 'navigation',
        startTime: 0,
        duration: 557,
        initiatorType: 'navigation',
        nextHopProtocol: 'http/1.1',
        workerStart: 0,
        redirectStart: 0,
        redirectEnd: 0,
        fetchStart: 0.19999980926513672,
        domainLookupStart: 31.5,
        domainLookupEnd: 34.39999985694885,
        connectStart: 34.39999985694885,
        connectEnd: 78.19999980926514,
        secureConnectionStart: 42.09999990463257,
        requestStart: 78.29999995231628,
        responseStart: 88.89999985694885,
        responseEnd: 125,
        transferSize: 86446,
        encodedBodySize: 86146,
        decodedBodySize: 345591,
        serverTiming: [],
        workerTiming: [],
        unloadEventStart: 0,
        unloadEventEnd: 0,
        domInteractive: 434,
        domContentLoadedEventStart: 474.7999999523163,
        domContentLoadedEventEnd: 485.09999990463257,
        domComplete: 552.2999999523163,
        loadEventStart: 552.3999998569489,
        loadEventEnd: 557,
        type: 'navigate',
        redirectCount: 0
    },
    {
        name: 'first-paint',
        entryType: 'paint',
        startTime: 164.09999990463257,
        duration: 0
    },
    {
        name: 'first-contentful-paint',
        entryType: 'paint',
        startTime: 164.09999990463257,
        duration: 0
    }
]

因此,可以再写一个for循环,循环data里面的数据,判断type等于navigate时拿到整体数据,name等于first-paint时就拿到了FP数据,name等于first-contentful-paint时就拿到了FCP数据
其他的数据可以按照自己的实际需求进行计算。

由于是每次访问一次页面时都会输出一次data数据,我们要拿的是平均值,所以可以再for循环外面定义一个变量,基础数数值为0
然后将data里面每次的得到的数据,累加到for循环外面的变量里面,最后再除以运行的次数,就拿到了平均值。

具体代码如下:

const performance = (async (url, times, outPath) => {

    async function runPage (url) {

        const browser = await puppeteer.launch({
            headless:false
        })

        const page = await browser.newPage()

        await page.setDefaultNavigationTimeout(0);

        await page.waitForTimeout(3000)

        await page.goto(url)

        await page.waitForNetworkIdle()    // 等待至网络连接断开

        let timingData = JSON.parse(await page.evaluate(
            () => JSON.stringify(window.performance.getEntries())
        ))

        await browser.close()

        return timingData
    }

    let duration = 0
    let firstPaint = 0
    let firstContentfulPaint = 0
    let name

    for (let i = 0; i < times; i++) {

        await runPage(url).then(data => {
            console.log(`第${i+1}次访问完成`)

            for (let dataKey in data){

                let navigationData = data[dataKey]

                if (navigationData.type === 'navigate'){

                    duration += navigationData.duration
                    name = navigationData.name

                }
                else if (navigationData.name === 'first-paint'){

                    firstPaint += navigationData.startTime
                }
                else if (navigationData.name === 'first-contentful-paint') {

                    firstContentfulPaint += navigationData.startTime

                }
            }
        })

        let PerformanceData = {}
        PerformanceData.name = name
        PerformanceData.fistPaint = firstPaint / times
        PerformanceData.firstContentfulPaint = firstContentfulPaint / times
        PerformanceData.duration = duration / times

        let json_data = JSON.stringify(PerformanceData,null,'\t')
        let filePath = outPath + '\\' + 'PerformanceReport.json'

        fs.writeFile(filePath,json_data,function (err) {
            if (err){
                return console.log(err);
            }
        })
    }
})

平均数值拿到了将储存在PerformanceData内,并且使用fs写入到文件内并且输出到指定的路径

到这里其实脚本的核心功能已经是实现了的,但是项目中不仅仅只有一个URL,所以这里需要让它自动访问指定的URL,并且执行指定的次数,最后输出报告文件

这里我的思路是,将URL写入到json文件内,用for循环遍历json文件里面的URL,再把URL传到runPage里面
具体代码如下:

const performance = (async (urlPath, times, outPath) => {

    async function runPage (url) {

        const browser = await puppeteer.launch({
            headless:true
        })

        const page = await browser.newPage()

        await page.setDefaultNavigationTimeout(0);

        await page.waitForTimeout(3000)

        await page.goto(url)

        await page.waitForNetworkIdle()    // 等待至网络连接断开

        let timingData = JSON.parse(await page.evaluate(
            () => JSON.stringify(window.performance.getEntries())
        ))

        await browser.close()

        return timingData
    }


    function readJson(path) {
        let raw_data = fs.readFileSync(path)
        raw_data = raw_data.toString()
        return JSON.parse(raw_data)
    }

    let readJsonData = readJson(urlPath)

    for (const readJsonDataKey in readJsonData){

        let duration = 0
        let firstPaint = 0
        let firstContentfulPaint = 0
        let name

        for (let i = 0; i < times; i++) {

            await runPage(readJsonData[readJsonDataKey]).then(data => {
                console.log(`${readJsonDataKey}第${i+1}次访问完成`)

                for (let dataKey in data){

                    let navigationData = data[dataKey]

                    if (navigationData.type === 'navigate'){

                        duration += navigationData.duration
                        name = navigationData.name

                    }
                    else if (navigationData.name === 'first-paint'){

                        firstPaint += navigationData.startTime
                    }
                    else if (navigationData.name === 'first-contentful-paint') {

                        firstContentfulPaint += navigationData.startTime

                    }
                }
            })

            let PerformanceData = {}
            PerformanceData.name = name
            PerformanceData.fistPaint = firstPaint / times
            PerformanceData.firstContentfulPaint = firstContentfulPaint / times
            PerformanceData.duration = duration / times

            let json_data = JSON.stringify(PerformanceData,null,'\t')
            let filePath = outPath + '\\' +readJsonDataKey+ 'PerformanceReport.json'

            fs.writeFile(filePath,json_data,function (err) {
                if (err){
                    return console.log(err);
                }
            })
        }
    }
})

urlJson文件格式如下:

{
    "JianShu": "https://www.jianshu.com/",
    "BaiDu": "https://www.baidu.com",
    "QQ": "https://www.qq.com"
}

2022.3.3

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容

  • 1.file,n.文件;v.保存文件 [faɪl] 2.command,n.命令,指令 [kəˈmænd] 3.u...
    巢峰阅读 384评论 0 0
  • 一、什么是框架 1.框架 框架(framework)是一个框子 -- 指其约束性,也是一个架子 -- 指其支撑性,...
    艺术家123阅读 194评论 0 0
  • 随着手机等移动设备的普及,适配移动端的页面变得越来越有必要。这也意味着移动端的调试变得越来越频繁,那么就会发生以下...
    大jiojioShow阅读 803评论 0 0
  • https://www.arduino.cn/forum.php?mod=viewthread&tid=47262...
    健健_2676阅读 1,968评论 0 0
  • H5新增属性(一时没答上)? input 新增 number、color、file、email、url、range...
    网恋被骗二块二阅读 112评论 0 0