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

举报
霍格沃兹测试 发表于 2025/12/03 11:24:26 2025/12/03
【摘要】 如果你正在寻找一种现代、可靠的方式来测量网站性能,Playwright 可能正是你需要的工具。虽然它主要以自动化测试闻名,但其强大的性能监控能力却常常被忽视。在这篇文章中,我将分享如何利用 Playwright 来测量那些影响用户体验的关键性能指标。 为什么选择 Playwright 进行性能测试?你可能会问:“已经有 Lighthouse 和 WebPageTest 这样的专用工具,为什么...

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

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

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

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

环境搭建与基础配置

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

mkdir playwright-performance
cd playwright-performance
npm init -y
npm install playwright

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

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

测量核心 Web 性能指标

1. 页面加载时间

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

// 开始计时
const startTime = Date.now();

// 导航到目标页面
await page.goto('https://example.com', {
  waitUntil: 'load'  // 等待页面完全加载
});

// 计算加载时间
const loadTime = Date.now() - startTime;
console.log(`页面加载时间: ${loadTime}ms`);

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

await page.goto('https://example.com', {
  waitUntil: 'networkidle'  // 等待网络空闲
});

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

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

最大内容绘制(LCP)

// 测量LCP(最大内容绘制)
const lcp = await page.evaluate(() => {
  return new Promise((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秒

累积布局偏移(CLS)

// 测量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值
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(clsValue);
    }, 5000); // 等待5秒以捕获可能的延迟布局变化
  });
});

console.log(`CLS: ${cls}`);
// 良好标准:小于0.1

3. 资源加载分析

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

// 获取所有资源的加载时间
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);

4. 交互响应时间

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

// 测量按钮点击响应时间
const button = await page.$('#submit-button');

const clickStartTime = Date.now();
await button.click();
// 等待某个表示交互完成的变化
await page.waitForSelector('.success-message', { timeout: 5000 });
const clickResponseTime = Date.now() - clickStartTime;

console.log(`交互响应时间: ${clickResponseTime}ms`);

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

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

const { chromium } = require('playwright');

async function runPerformanceTest(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);

进阶技巧与最佳实践

1. 模拟不同网络条件

const { chromium } = require('playwright');

async function testWithNetworkConditions(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();
}

2. 多次测试取平均值

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

async function runAverageTest(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) {
      await new Promise(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;
}

3. 生成可视化报告

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

const fs = require('fs');

async function generateReport(url) {
  const data = await runPerformanceTest(url);
  
  const report = {
    timestamp: new Date().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('报告已生成');
}

常见问题与解决方案

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

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

await page.evaluateOnNewDocument(() => {
  // 在页面任何代码执行前开始监控
  const observer = new PerformanceObserver((list) => {
    window.lcpEntry = list.getEntries().slice(-1)[0];
  });
  observer.observe({ type: 'largest-contentful-paint', buffered: true });
});

问题2:测试结果不稳定

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

  1. 每次测试前清除缓存
  2. 多次测试取平均值
  3. 在相对稳定的网络环境下运行测试
const context = await browser.newContext({
  bypassCSP: true,
  // 禁用缓存
  viewport: null
});

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

Playwright 的优势在这里体现:

async function testAuthenticatedPage() {
  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', 'your-username');
  await page.fill('#password', 'your-password');
  await page.click('#login-button');
  await page.waitForNavigation();
  
  // 现在测试需要认证的页面
  console.log('测试已登录状态下的性能...');
  await runPerformanceTest('https://example.com/dashboard');
  
  await browser.close();
}

总结

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

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

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

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

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。