You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Playwright测试中通过new_cdp_session测量Total Blocking Time?

解决Playwright中获取Web Vitals(TBT、LCP、CLS)的问题

你遇到的问题核心在于:TBT、LCP、CLS属于Web Vitals指标,它们并非Chrome DevTools Protocol(CDP)Performance.getMetrics接口直接返回的原生指标,而是基于浏览器底层性能数据计算得出的衍生值。下面提供两种可行的解决方法:

方法一:通过CDP的PerformanceObserver监听指标

利用CDP的性能事件监听能力,直接捕获Web Vitals相关的性能条目,再进行计算:

from playwright.sync_api import Page

def test_web_vitals_via_cdp(page: Page):
    client = page.context.new_cdp_session(page)
    
    # 启用性能监控,设置时间域为时间戳
    client.send('Performance.enable')
    client.send('Performance.setTimeDomain', {'timeDomain': 'timeStamp'})
    
    # 存储指标结果
    lcp_value = None
    cls_value = 0.0
    
    # 监听LCP事件
    def handle_lcp(event):
        nonlocal lcp_value
        entry = event['params']['entry']
        if entry['name'] == 'largest-contentful-paint':
            lcp_value = entry['startTime'] + entry['duration']
    
    client.on('Performance.entryAdded', handle_lcp)
    
    # 监听CLS事件(忽略用户输入后的位移)
    def handle_layout_shift(event):
        nonlocal cls_value
        entry = event['params']['entry']
        if entry['name'] == 'layout-shift' and not entry.get('hadRecentInput', False):
            cls_value += entry['value']
    
    client.on('Performance.entryAdded', handle_layout_shift)
    
    # 导航到页面并等待加载稳定
    page.goto('https://www.google.com', wait_until='networkidle')
    
    # 计算TBT:统计所有长任务中超过50ms的阻塞部分
    def calculate_tbt():
        long_tasks = client.send('Performance.getEntries', {'type': 'longtask'})
        total_blocking_time = 0.0
        for task in long_tasks:
            blocking_part = task['duration'] - 50
            if blocking_part > 0:
                total_blocking_time += blocking_part
        return total_blocking_time
    
    tbt_value = calculate_tbt()
    
    # 输出结果
    print(f"LCP: {lcp_value:.2f}ms")
    print(f"CLS: {cls_value:.4f}")
    print(f"TBT: {tbt_value:.2f}ms")
    
    client.send('Performance.disable')

方法二:直接使用Playwright的页面evaluate方法(更简洁)

无需手动操作CDP,直接通过page.evaluate调用浏览器原生Performance API获取并计算指标:

from playwright.sync_api import Page

def test_web_vitals_simpler(page: Page):
    page.goto('https://www.google.com', wait_until='networkidle')
    
    # 获取LCP
    lcp = page.evaluate("""() => {
        return new Promise(resolve => {
            const observer = new PerformanceObserver((list) => {
                const entries = list.getEntries();
                const lastEntry = entries[entries.length - 1];
                resolve(lastEntry.startTime + lastEntry.duration);
            });
            observer.observe({ type: 'largest-contentful-paint', buffered: true });
        });
    }""")
    
    # 获取CLS
    cls = page.evaluate("""() => {
        return new Promise(resolve => {
            let totalCls = 0;
            const observer = new PerformanceObserver((list) => {
                for (const entry of list.getEntries()) {
                    if (!entry.hadRecentInput) totalCls += entry.value;
                }
            });
            observer.observe({ type: 'layout-shift', buffered: true });
            // 等待页面稳定后返回结果
            requestIdleCallback(() => resolve(totalCls));
        });
    }""")
    
    # 获取TBT
    tbt = page.evaluate("""() => {
        const longTasks = performance.getEntriesByType('longtask');
        return longTasks.reduce((total, task) => {
            const blocking = task.duration - 50;
            return total + (blocking > 0 ? blocking : 0);
        }, 0);
    }""")
    
    print(f"LCP: {lcp:.2f}ms")
    print(f"CLS: {cls:.4f}")
    print(f"TBT: {tbt:.2f}ms")

关键说明

  • LCP:取最后一个largest-contentful-paint条目的开始时间与时长之和,代表页面最大内容渲染完成的时间。
  • CLS:累加所有无用户输入触发的layout-shift条目值,反映页面布局的稳定性。
  • TBT:统计所有长任务(时长>50ms)中超出50ms的部分,这些是导致页面卡顿的阻塞时间。

内容的提问来源于stack exchange,提问作者Toki D

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 09:37:48