chrome devtools protocol——Web 性能自动化实践介绍

Posted TesterHome

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了chrome devtools protocol——Web 性能自动化实践介绍相关的知识,希望对你有一定的参考价值。

前言

在测试Web页面加载时间时,可能会是这样的:

  1. 打开chrome浏览器。

  2. 按F12打开开发者工具。

  3. 在浏览器上打开要测试的页面

  4. 查看开发者工具中Network面板的页面性能数据并记录

  5. 或者在开发者工具中Console面板运行performance.timingperformance.getEntries()收集数据

performance相关信息看这里PerformanceTiming

几十上百个页面,每个版本都这样来,估计疯了,所以就想怎么把它做成自动化呢?

chrome devtools protocol

chrome devtools protocol允许第三方对基于chrome的web应用程序进行调试、分析等,它基于WebSocket,利用WebSocket建立连接DevTools和浏览器内核的快速数据通道。一句话,有了这个协议就可以自己开发工具获取chrome的数据

协议详细内容看这里chrome devtools protocol

目前已经有很多大神针对这个协议封装出不同语言(nodejs,python,java...)的库,详细信息看这里awesome-chrome-devtools

这个库依赖websocket-client

获取performance api数据

这里使用Runtime Domain中运行javascript脚本的APIRuntime.evaluate

# 开始前先启动chrome,启动chrome必须带上参数`--remote-debugging-port=9222`开启远程调试否则无法与chrome交互
browser = pychrome.Browser('http://127.0.0.1:%d' % 9222)
tab = browser.new_tab()
tab.start()
tab.Runtime.enable()
tab.Page.navigate(url={你的页面地址})


# 设置等待页面加载完成的时间
tab.wait(10)


# 运行js脚本
timing_remote_object = tab.Runtime.evaluate(
           expression='performance.timing'
       )


# 获取performance.timing结果数据
timing_properties = tab.Runtime.getProperties(
           objectId=timing_remote_object.get('result').get('objectId')
       )
timing = {}
for item in timing_properties.get('result'):
           if item.get('value', {}).get('type') == 'number':
                   timing[item.get('name')] = item.get('value').get('value')

#
获取performance.getEntries()数据
entries_remote_object = tab.Runtime.evaluate(
           expression='performance.getEntries()'
       )
entries_properties = tab.Runtime.getProperties(
           objectId=entries_remote_object.get('result').get('objectId')
       )
entries_values = []
for item in entries_properties.get('result'):
 if item.get('name').isdigit():
   url_timing_properties = tab.Runtime.getProperties(
                   objectId=item.get('value').get('objectId')
               )
    entries_value = {}
    for son_item in url_timing_properties.get('result'):
                   if (son_item.get('value', {}).get('type') == 'number'or
                           son_item.get('value', {}).get('type') == 'string'):
                       entries_value[son_item.get('name')] = son_item.get('value').get('value')
               entries_values.append(entries_value)

获取Network数据

实际上performance.getEntries()不会记录404的请求信息,另外当前页面通过js触发新html页面请求时它只会记录第一个页面的请求,在这些情况下就需要通过Network Domain的API来收集所有请求信息,先介绍用到的API:

  1. Network.requestWillBeSent每个http请求发送前回调

  2. Network.responseReceived首次接送到http响应时回调

  3. Network.loadingFinished请求加载完成时回调

  4. Network.loadingFailed请求加载失败时回调

    # 封装上面4个事件对应的回调方法
    class NetworkAPIImplemention(object):

    def __init__(self):
       self.request_dict = {}
       # 首个请求开始时间
       self.start = None

    def request_will_be_sent(self, **kwargs):
       if self.start is None:
           self.start = time.time()
       dict_http = {
           'url':kwargs.get('request').get('url'),
           'start':kwargs.get('timestamp')
       }
       self.request_dict[kwargs.get('requestId')]=dict_http
       #print "loading:%s" % kwargs.get('request').get('url')

    def loading_finished(self, **kwargs):
       # 服务器返回code 例如404也是finished
       self.request_dict[kwargs.get('requestId')]['end'] = kwargs.get('timestamp')
       self.request_dict[kwargs.get('requestId')]['size'] = kwargs.get('encodedDataLength')

    def response_received(self, **kwargs):
       self.request_dict[kwargs.get('requestId')]['type'] = kwargs.get('type')
       self.request_dict[kwargs.get('requestId')]['response'] = kwargs.get('response')

    def loading_failed(self, **kwargs):
       self.request_dict[kwargs.get('requestId')]['end'] = kwargs.get('timestamp')
       self.request_dict[kwargs.get('requestId')]['error_text'] = kwargs.get('errorText')
    network_api = NetworkAPIImplemention()
    browser = pychrome.Browser('http://127.0.0.1:%d' % 9222)
    tab = browser.new_tab()


    # 绑定回调函数
    tab.Network.requestWillBeSent = network_api.request_will_be_sent
    tab.Network.responseReceived = network_api.response_received
    tab.Network.loadingFinished = network_api.loading_finished
    tab.Network.loadingFailed = network_api.loading_failed
    tab.start()
    tab.Network.enable()
    tab.Runtime.enable()


    # 是否禁用缓存
    if disable_cache:
    tab.Network.setCacheDisabled(cacheDisabled=True)
    tab.Page.navigate(url={你的页面地址})
    tab.wait(10)
    tab.stop()
    self.browser.close_tab(tab)


    # 获取的所有url详细信息
    print network_api.request_dict

监听页面事件

有时候特别是一些复杂的页面,页面依赖js和后端资源数据,并不是通常意义上页面loadEventEnd事件触发完就表示页面加载完成,这种情况可能需要依赖开发打点。
这里以开发设计了一个Loaded事件为例

# 具体事件注册方式和注册时机询问开发,所谓注册时机即要求在js对象生成后注册,我们项目中page是在一个js文件中声明的,需要等这个js文件请求完成后再注册
# 这边使用Promise方式,这种方式awaitPromise参数必须是True
js = """
   new Promise((resolve, reject) => {
       page.getController().getPageEvent().addEventListener("
Loaded",
               function(){
                   resolve(new Date().getTime());
               });
       });
  """

custom_result = tab.Runtime.evaluate(
   expression=js,
   awaitPromise=True,
   timeout=timeout * 1000
)
print custom_result.get('result').get('value')

有个坑peformance.now()获取与chrome开发者工具协议一样类型的时间时,这个时间不准确,只好用new Date().getTime()

写在最后

一开始是使用nodejs的chrome-remote-interface,但是发现Page.loadEventFired回调后不会再记录请求,事实上有些页面仍然有请求没有完成,不懂是不是我使用姿势不对


附赠W3C的一幅图


以上是关于chrome devtools protocol——Web 性能自动化实践介绍的主要内容,如果未能解决你的问题,请参考以下文章

chrome devtools protocol——Web 性能自动化

chrome devtools protocol——Web 性能自动化实践介绍

使用Chrome Headless 快速实现java版数据的抓取

Chrome 无头打印到 PDF 中的其他选项

使用谷歌浏览器远程调试协议

chrome 远程调试