2025-12-03

性能测试入门:使用 Playwright 测量关键 Web 性能指标

如果你正在寻找一种现代、可靠的方式来测量网站性能,Playwright 可能正是你需要的工具。虽然它主要以自动化测试闻名,但其强大的性能监控能力却常常被忽视。在这篇文章中,我将分享如何利用 Playwright 来测量那些影响用户体验的关键性能指标。

为什么选择 Playwright 进行性能测试?

你可能会问:“已经有 Lighthouse 和 WebPageTest 这样的专用工具,为什么还要用 Playwright?” 原因很简单:灵活性和集成度。Playwright 允许你将性能测试无缝集成到现有的自动化测试流程中,可以在多种浏览器环境下运行,并且能够模拟真实的用户交互场景。

我最初是在为一个需要登录后才能测试的页面寻找性能监控方案时发现了 Playwright 的这个能力。其他工具难以处理身份验证,而 Playwright 轻松解决了这个问题。

环境搭建与基础配置

首先,确保你已经安装了 Node.js(建议版本 14 或更高)。创建一个新目录并初始化项目:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">mkdir playwright-performance cd playwright-performance npm init -y npm install playwright </pre>

接下来,创建一个基本脚本文件 performance-test.js

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">const { chromium } = require('playwright'); (async () => { // 启动浏览器,建议使用无头模式以提高性能 const browser = await chromium.launch({ headless: true }); const context = await browser.newContext(); const page = await context.newPage(); // 在这里添加性能测量代码 await browser.close(); })();</pre>

测量核心 Web 性能指标

1. 页面加载时间

最基本的指标是页面完全加载所需的时间:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 开始计时 const startTime = Date.now(); // 导航到目标页面 await page.goto('https://example.com', { waitUntil: 'load' // 等待页面完全加载 }); // 计算加载时间 const loadTime = Date.now() - startTime; console.log(页面加载时间: ${loadTime}ms); </pre>

waitUntil: 'load' 可能不够准确,因为它不一定会等待所有异步内容完成。我通常使用 'networkidle' 选项,它会等待网络活动基本停止:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">await page.goto('https://example.com', { waitUntil: 'networkidle' // 等待网络空闲 }); </pre>

2. 核心 Web 指标(Core Web Vitals)

Google 提出的核心 Web 指标对用户体验至关重要。通过 Playwright 我们可以测量其中的几项:

最大内容绘制(LCP)

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 测量LCP(最大内容绘制) const lcp = await page.evaluate(() => {returnnewPromise((resolve) => { const observer = new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); const lastEntry = entries[entries.length - 1]; resolve(lastEntry.renderTime || lastEntry.loadTime); }); observer.observe({ type: 'largest-contentful-paint', buffered: true }); // 如果LCP已经发生,直接获取 const po = performance.getEntriesByType('largest-contentful-paint'); if (po.length > 0) { resolve(po[po.length - 1].renderTime || po[po.length - 1].loadTime); } });});console.log(LCP: ${lcp}ms);// 良好标准:小于2.5秒 </pre>

累积布局偏移(CLS)

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 测量CLS(累积布局偏移) const cls = await page.evaluate(() => {let clsValue = 0;const observer = new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value; } } }); observer.observe({ type: 'layout-shift', buffered: true });// 返回最终的CLS值returnnewPromise((resolve) => { setTimeout(() => { resolve(clsValue); }, 5000); // 等待5秒以捕获可能的延迟布局变化 });});console.log(CLS: ${cls});// 良好标准:小于0.1 </pre>

3. 资源加载分析

了解各个资源的加载性能有助于定位问题:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 获取所有资源的加载时间 const resources = await page.evaluate(() => { const resources = performance.getEntriesByType('resource'); return resources.map(resource => ({ name: resource.name, duration: resource.duration, type: resource.initiatorType })); }); // 找出加载最慢的资源 const slowestResources = resources.sort((a, b) => b.duration - a.duration).slice(0, 5); console.log('加载最慢的5个资源:', slowestResources); </pre>

4. 交互响应时间

对于单页应用(SPA),交互响应时间尤为重要:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">// 测量按钮点击响应时间 const button = await page.$('[#submit](javascript:;)-button'); const clickStartTime = Date.now(); await button.click(); // 等待某个表示交互完成的变化 await page.waitForSelector('.success-message', { timeout: 5000 }); const clickResponseTime = Date.now() - clickStartTime; console.log(交互响应时间: ${clickResponseTime}ms);</pre>

实战:完整的性能测试脚本

下面是一个整合了多个指标的完整示例:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">const { chromium } = require('playwright');asyncfunctionrunPerformanceTest(url) {const browser = await chromium.launch({ headless: true });const context = await browser.newContext();const page = await context.newPage();console.log(正在测试: {url}`);// 监听性能指标await page.evaluateOnNewDocument(() => { // 这里可以注入性能监控代码 window.performanceMetrics = { lcp: null, cls: null, fid: null }; });// 导航到页面const startTime = Date.now();await page.goto(url, { waitUntil: 'networkidle' });const navigationTime = Date.now() - startTime;// 等待可能的内容加载await page.waitForTimeout(2000);// 收集性能指标const performanceData = await page.evaluate(() => { // 获取导航计时 const navigation = performance.getEntriesByType('navigation')[0]; // 获取绘制指标 const paintEntries = performance.getEntriesByType('paint'); const fcp = paintEntries.find(entry => entry.name === 'first-contentful-paint'); // 获取LCP const lcpEntries = performance.getEntriesByType('largest-contentful-paint'); const lcp = lcpEntries.length > 0 ? lcpEntries[lcpEntries.length - 1] : null; return { dnsTime: navigation.domainLookupEnd - navigation.domainLookupStart, tcpTime: navigation.connectEnd - navigation.connectStart, ttfb: navigation.responseStart - navigation.requestStart, domContentLoaded: navigation.domContentLoadedEventEnd, loadEvent: navigation.loadEventEnd, fcp: fcp ? fcp.startTime : null, lcp: lcp ? lcp.startTime : null }; });console.log('\n=== 性能测试结果 ===');console.log(`总导航时间:{navigationTime}ms);console.log(DNS查询: {performanceData.dnsTime}ms`);console.log(`TCP连接:{performanceData.tcpTime}ms);console.log(首字节时间(TTFB): {performanceData.ttfb}ms`);console.log(`首次内容绘制(FCP):{performanceData.fcp}ms);console.log(最大内容绘制(LCP): {performanceData.lcp}ms`);console.log(`DOM内容加载:{performanceData.domContentLoaded}ms);console.log(页面完全加载: {performanceData.loadEvent}ms`);// 检查是否达到性能阈值const thresholds = { lcp: 2500, ttfb: 800, fcp: 1800 };console.log('\n=== 性能评估 ===');if (performanceData.lcp > thresholds.lcp) { console.warn(`⚠️ LCP{performanceData.lcp}ms 超过阈值 ${thresholds.lcp}ms); } else { console.log(✅ LCP 符合标准); }await browser.close();return performanceData;}// 运行测试runPerformanceTest('https://example.com').catch(console.error);</pre>

进阶技巧与最佳实践

1. 模拟不同网络条件

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">const { chromium } = require('playwright');asyncfunctiontestWithNetworkConditions(url) {const browser = await chromium.launch();const context = await browser.newContext();// 模拟3G网络const slow3G = { offline: false, downloadThroughput: 500 * 1024 / 8, // 500 Kbps uploadThroughput: 500 * 1024 / 8, latency: 400 };const page = await context.newPage();// 设置网络节流const client = await context.newCDPSession(page);await client.send('Network.emulateNetworkConditions', slow3G);console.log('正在3G网络条件下测试...');await page.goto(url);await browser.close();} </pre>

2. 多次测试取平均值

性能测试结果可能会有波动,多次测试取平均值更加可靠:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">asyncfunctionrunAverageTest(url, iterations = 5) {const results = [];for (let i = 0; i < iterations; i++) { console.log({i + 1}/{iterations} 次测试...); const result = await runPerformanceTest(url); results.push(result); // 每次测试之间等待一会 if (i < iterations - 1) { awaitnewPromise(resolve => setTimeout(resolve, 2000)); } }// 计算平均值const averages = {};const metrics = Object.keys(results[0]); metrics.forEach(metric => { const sum = results.reduce((acc, result) => acc + (result[metric] || 0), 0); averages[metric] = sum / results.length; });console.log('\n=== 平均性能结果 ===');Object.entries(averages).forEach(([metric, value]) => { console.log({metric}:{Math.round(value)}ms); });return averages;} </pre>

3. 生成可视化报告

你可以将结果输出为JSON,然后使用其他工具(如Chart.js)生成可视化报告:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">const fs = require('fs');asyncfunctiongenerateReport(url) {const data = await runPerformanceTest(url);const report = { timestamp: newDate().toISOString(), url: url, metrics: data, thresholds: { good: { lcp: 2500, fcp: 1800, cls: 0.1 }, needsImprovement: { lcp: 4000, fcp: 3000, cls: 0.25 } } }; fs.writeFileSync(performance-report-${Date.now()}.json, JSON.stringify(report, null, 2) );console.log('报告已生成'); }</pre>

常见问题与解决方案

问题1:性能指标获取不到

如果某些性能指标返回null,可能是因为页面加载太快,性能条目已经被清除。可以尝试在页面加载前就注入性能观察器:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">await page.evaluateOnNewDocument(() => { // 在页面任何代码执行前开始监控 const observer = new PerformanceObserver((list) => { window.lcpEntry = list.getEntries().slice(-1)[0]; }); observer.observe({ type: 'largest-contentful-paint', buffered: true }); }); </pre>

问题2:测试结果不稳定

网络波动、缓存等因素可能导致测试结果不一致。解决方案:

  1. 每次测试前清除缓存
  2. 多次测试取平均值
  3. 在相对稳定的网络环境下运行测试

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">const context = await browser.newContext({ bypassCSP: true, // 禁用缓存 viewport: null }); </pre>

问题3:需要测试登录后的页面

Playwright 的优势在这里体现:

<pre data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent; margin: 10px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">asyncfunctiontestAuthenticatedPage() {const browser = await chromium.launch();const context = await browser.newContext();const page = await context.newPage();// 先登录await page.goto('https://example.com/login');await page.fill('[#username](javascript:;)', 'your-username');await page.fill('[#password](javascript:;)', 'your-password');await page.click('[#login](javascript:;)-button');await page.waitForNavigation();// 现在测试需要认证的页面console.log('测试已登录状态下的性能...');await runPerformanceTest('https://example.com/dashboard');await browser.close(); }</pre>

总结

Playwright 提供了一种灵活且强大的方式来测量网站性能。通过本文介绍的方法,你可以:

  1. 测量关键性能指标(LCP、CLS、FCP等)
  2. 模拟不同网络条件
  3. 集成到现有的测试流程中
  4. 生成详细的性能报告

虽然专用的性能测试工具仍然有其价值,但 Playwright 在灵活性和集成度方面的优势使其成为性能监控工具箱中一个值得拥有的补充。最重要的是,你可以使用相同的工具和技术栈来进行功能测试和性能测试,这大大简化了开发工作流程。

开始尝试将这些技术应用到你的项目中吧。你会发现,识别和解决性能瓶颈从未如此简单。记住,性能优化是一个持续的过程,定期监控比一次性测试更有价值。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容