在过去十年中,普通网站的丰富性和互动性发生了巨大变化,但同样,使用网站的用户对其期望也发生了变化。 此页面 列出了报告,这些报告展示了企业如何能够在网站性能与转化/收入数据之间建立直接关联。例如,英国金融时报的工程团队 进行了一项测试,结果表明,加载时间每增加一秒,文章浏览量就会下降 4.9%。
根本原因非常简单,它会影响所有规模的项目(是的,包括您的项目):用户变得更加挑剔、耐心减少,并且无法容忍缓慢的网站或应用程序。如果您的内容加载时间过长,用户就会去其他地方。访问一个打开和浏览都需要花费很长时间的网站是一种糟糕的用户体验,尤其是在以移动设备为主的环境中,即时性至关重要,电池寿命也很宝贵。
因此,网站性能优化在任何在线资产的成功中都扮演着越来越重要的角色。所有主要浏览器都附带工具,允许开发人员在构建过程中密切关注一些重要的性能指标,但这些指标是从开发人员自己的角度测量的,这不足以看到全貌。
诸如地理位置、连接类型、设备、浏览器供应商或操作系统等因素会严重影响感知加载时间,因此测试所有这些变量是获得网站被更广泛受众体验方式(稍微)准确表示的唯一方法。
有 各种工具和服务 可以解决此问题,但这篇文章将专门关注 WebPageTest。我们将从开发人员的角度来审视它,特别是使用其 RESTful API 来提取可用于优化网站性能的重要信息。
关于WebPageTest
WebPageTest 是一款开源性能测试工具,主要由 Google 维护。它由一个或多个服务器组成,这些服务器充当 Web 浏览机器人,访问网站并自动收集有关体验的数据,并以详细的性能报告的形式提供。
有一个公共实例,它拥有一个大型服务器池,任何人都可以免费使用,这正是我们在本文的示例中将要使用的。或者,平台的源代码可在 GitHub 上获取,如果您想在自己的基础设施上私下托管它。
运行测试
当您打开 WebPageTest 网站 时,您会看到允许您立即运行测试的界面。您首先需要测试页面的 URL。最重要的是,可以配置许多参数,主要参数包括
- 连接速度
- 测试位置
- 是否捕获测试视频
- 要运行的测试次数(进行单次运行意味着测试结果更容易受到网络或服务器异常的影响,因此 WebPageTest 认为最佳实践是多次运行测试并使用平均值作为最具代表性的结果)
- 单次查看或重复查看(您可以选择是每项测试加载一次页面还是两次;因为在每次测试之前都会清除浏览器缓存,这实际上意味着决定您是否只对初始的、未缓存的视图感兴趣,或者您是否希望第二次利用浏览器缓存)

在本例中,我们使用默认设置测试 https://css-tricks.org.cn:来自弗吉尼亚州杜勒斯的电缆连接上的 Chrome,执行一次运行并进行首次和重复查看,并启用视频捕获。
请求测试后,您将进入等待期,因为设备池由所有使用 WebPageTest 公共实例的用户共享。因此,测试完成所需的时间是不可预测的,具体取决于使用设备的人数和测试的复杂性。
此链接 包含所有可用资源及其在任何给定时间的容量列表。
阅读结果
测试报告中显示的信息量可能有点多,因此值得查看一些返回的关键指标及其含义
- 加载时间:初始请求与浏览器加载事件之间的时间
- 第一个字节:服务器响应第一个响应字节所需的时间(换句话说,后端加载所需的时间)
- 开始渲染:浏览器开始将内容绘制到屏幕上的时间
- 速度指数:WebPageTest 引入的自定义指标,用于根据页面视觉填充的速度对页面进行评分(有关该指标的完整详细信息,请参阅 此处)
- DOM 元素:页面中的 DOM 元素数量
- 文档完成:与浏览器加载事件发生之前的时间相关的指标集,其中时间、请求和传入字节分别表示加载时间、请求数和接收到的字节数
- 完全加载:类似于文档完成,但指标与 WebPageTest 确定页面已完全完成加载内容的时间相关。这与上面提到的内容相关且有所不同,因为页面可能会在浏览器加载事件之后决定加载其他内容

瀑布图是报告的另一个关键部分。它显示了网络活动随时间的可视化表示,其中每个水平条代表一个 HTTP 请求。条形图中的颜色代表请求的五个阶段:DNS 查找(青绿色)、初始连接(橙色)、SSL 协商(紫色)、第一个字节时间(绿色)和内容下载(蓝色)。
它还显示垂直线以标记页面生命周期中的关键事件,例如浏览器绘制第一个像素所需的时间(绿色)、DOM 树准备就绪的时间(粉红色)或文档加载的时间(蓝色)。最后,它显示重定向(以黄色突出显示)和错误(以红色突出显示)。
我们在其余部分请求了视频录制,因此 WebPageTest 为我们提供了一组帧,这些帧以视觉方式显示页面随时间推移在屏幕上绘制。我们可以使用这些数据生成胶片条视图或实际视频。

WebPageTest API
在介绍了平台的基本知识后,让我们深入了解如何以编程方式与它进行交互。
WebPageTest 提供了一个供公众使用的 RESTful API。因为它是一个共享实例,所以使用次数限制为每天 200 次页面加载——重复查看计为单独的页面加载,这意味着具有两次运行和重复查看的测试将计为四次页面加载。
还值得一提的是,测试结果仅在服务器上保留 30 天,因此请确保将任何可能需要保存以备后用的数据(包括图像和视频)保存到您自己的基础设施中。
您在 WebPageTest UI 上可以执行的任何操作,也可以以编程方式执行,因为网站本身使用了 RESTful API。您可以请求测试并获取结果,然后将其馈送到各种输出中,例如数据可视化工具、持续集成流程、触发 Slack 或电子邮件警报,或者几乎任何东西。
本文中显示的代码示例是用 ES5 JavaScript 为 Node.js 环境编写的,使用了 WebPageTest API 包装器。但由于 API 是 RESTful 的,因此可以使用任何能够发送 HTTP 请求的语言或环境访问它,因此您在此处看到的所有内容都可以移植到您选择的语言。
设置
第一步是申请一个 API 密钥。填写完您的信息后,您应该会立即获得一个密钥。
完成此操作后,我们可以设置一个新的 Node.js 项目并安装 WebPageTest API 封装库。
npm install webpagetest --save
var WebPageTest = require('WebPageTest')
var wpt = new WebPageTest('https://www.webpagetest.org/', 'your-api-key')
WebPageTest
构造函数接受两个参数
- WebPageTest 实例的 URL(除非您使用的是私有实例,否则将为https://www.webpagetest.org/)
- API 密钥
以编程方式运行测试
我们将重复之前进行的测试,但这次将使用 API 以编程方式进行。我们需要 runTest
函数,它接受两个参数
- 正在测试的网站的 URL
- 一个包含配置测试选项列表的对象(有关所有可用选项的列表,请参见此处)
wpt.runTest('https://css-tricks.org.cn', {
connectivity: 'Cable',
location: 'Dulles:Chrome',
firstViewOnly: false,
runs: 1,
video: true
}, function processTestRequest(err, result) {
console.log(err || result)
})
请记住,请求测试会将您放入等待列表,因此您从运行以上代码获得的响应不是实际的测试结果,而更像是一张收据,您可以使用它来检查测试的进度并在结果准备就绪时获取结果。
{
"statusCode": 200,
"statusText": "Ok",
"data": {
"testId": "160814_W7_960",
"ownerKey": "ad50468e0d69d1e6d0cda22f38d7511cc4284e40",
"jsonUrl": "https://www.webpagetest.org/jsonResult.php?test=160814_W7_960",
"xmlUrl": "https://www.webpagetest.org/xmlResult/160814_W7_960/",
"userUrl": "https://www.webpagetest.org/result/160814_W7_960/",
"summaryCSV": "https://www.webpagetest.org/result/160814_W7_960/page_data.csv",
"detailCSV": "https://www.webpagetest.org/result/160814_W7_960/requests.csv"
}
}
我们特别关注 data.testId
,因为它包含一个唯一标识我们测试的字符串。我们可以将其传递给 getTestStatus
方法以检查测试是否已准备就绪。
wpt.getTestStatus('160814_W7_960', function processTestStatus(err, result) {
console.log(err || result)
})
最终(取决于平台的繁忙程度),您将收到包含以下内容的响应
{
"statusCode": 200,
"statusText": "Test Complete"
}
此时,我们知道测试结果已准备就绪,我们可以使用 getTestResults
获取它们。
wpt.getTestResults('160814_W7_960', function processTestResult(err, result) {
console.log(err || result)
})
这种获取结果的方法需要我们手动操作,因为我们需要不断调用 getTestStatus
直到获得 200
响应,然后才能调用 getTestResults
。有两种替代(更方便)的方法可以做到这一点
- 您可以将
pingback
选项传递给runTest
,其中包含一个 WebPageTest 测试完成后要调用的 URL。这可能是您 Web 服务器中专门用于处理测试结果的路由。测试 ID 将作为id
查询参数传递,您可以使用它来调用getTestResults
。 - 您可以通过提供
pollResults
选项,将请求、轮询和检索这三个步骤合并到对runTest
的调用中。它的值(以秒为单位)表示用于轮询 API 以获取测试状态的间隔。只有在返回测试结果后,执行才会结束。
以下示例(使用选项 2)结合了我们看到的所有步骤,并使用单个对 runTest
的调用来请求测试、每 5 秒轮询一次 API 直到结果准备就绪,最后输出结果。
我不会在此处包含完整的响应,因为它非常庞大(375KB 的数据!),但您可以在此处查看完整内容。相反,我们将了解如何深入研究它以查找我们之前描述的一些指标。
wpt.runTest('https://css-tricks.org.cn', {
connectivity: 'Cable',
location: 'Dulles:Chrome',
firstViewOnly: false,
runs: 1,
pollResults: 5,
video: true
}, function processTestResult(err, result) {
// First view — use `repeatView` for repeat view
console.log('Load time:', result.data.average.firstView.loadTime)
console.log('First byte:', result.data.average.firstView.TTFB)
console.log('Start render:', result.data.average.firstView.render)
console.log('Speed Index:', result.data.average.firstView.SpeedIndex)
console.log('DOM elements:', result.data.average.firstView.domElements)
console.log('(Doc complete) Requests:', result.data.average.firstView.requestsDoc)
console.log('(Doc complete) Bytes in:', result.data.average.firstView.bytesInDoc)
console.log('(Fully loaded) Time:', result.data.average.firstView.fullyLoaded)
console.log('(Fully loaded) Requests:', result.data.average.firstView.requestsFull)
console.log('(Fully loaded) Bytes in:', result.data.average.firstView.bytesIn)
console.log('Waterfall view:', result.data.runs[1].firstView.images.waterfall)
})
自定义指标
上面显示的指标只是 WebPageTest 捕获的所有内容的一小部分,您可以通过分解完整的结果有效负载来查看。但有时需要衡量其他内容,例如仅与正在测试的特定网站相关的指标。
使用 WebPageTest,我们可以使用自定义指标来实现这一点,这是一项允许我们在测试结束时执行任意 JavaScript 代码的功能。
例如,我们可能对跟踪由加载的 iframe 数量或特定提供商提供的任何广告引起的性能影响感兴趣。让我们看看如何衡量这一点。
var customMetrics = [
'[iframes]',
'return document.getElementsByTagName("iframe").length',
'[ads]',
'return Array.prototype.slice.call(document.getElementsByTagName("a")).filter(function (node) { return node.getAttribute("href").indexOf("ad.doubleclick.net") !== -1 }).length'
]
wpt.runTest('https://css-tricks.org.cn', {
custom: customMetrics.join('\n'),
connectivity: 'Cable',
location: 'Dulles:Chrome',
firstViewOnly: false,
runs: 1,
pollResults: 5
}, function processTestResult(err, result) {
console.log('Iframes:', result.data.average.firstView.iframes)
console.log('Ads:', result.data.average.firstView.ads)
})
每个指标都定义为一段以方括号中的标识符开头的 JavaScript 代码,并用换行符分隔。
要获取 iframe 的数量,我们只需在 DOM 中查找并计算所有 <iframe>
元素即可。对于广告,我们查看所有包含 ads.doubleclick.net
的 <a>
节点,这些节点位于 href
属性中。为了简洁起见,这些是简化的示例,但您可以根据需要定义任意长且复杂的例程指标。
本文深入描述了自定义指标,并提供了有关哪些指标值得衡量的真正有趣的见解。
脚本编写
默认情况下,测试包括 WebPageTest 访问某个站点并捕获数据,直到该站点完全加载。然后,它提取任何自定义指标(如果已定义)并完成测试。虽然这适用于大多数情况,但 WebPageTest 提供了一种脚本编写功能,允许您创建更复杂的多步骤测试例程。
例如,您可以创建一个测试来模拟用户点击按钮、在登录表单中填写详细信息或您通常可以与页面进行的任何物理交互。
脚本包含一系列包含自定义命令的指令(有关可用命令的列表,请参见此链接),这些指令编码在一个对象数组中,然后使用 scriptToString
方法转换为字符串。
在运行脚本化测试时,将目标 URL 传递给 runTest
的常规方法无效。相反,脚本本身将指示 WebPageTest 应遵循的链接,因此您必须包含至少一个 navigate
命令,后跟一个 URL(值以制表符分隔)。
根据设计,只有一个命令可以输出测试结果,因此多步骤测试应使用 logData
根据您希望捕获的命令来打开和关闭结果的生成。
在下面的示例中,我们将创建一个测试,该测试导航到https://css-tricks.org.cn并在搜索框中填写术语flexbox。此时,我们打开输出,提交表单并等待其完成。此多步骤测试将捕获使用站点搜索功能的体验。
var script = wpt.scriptToString([
{logData: 0},
{navigate: 'https://css-tricks.org.cn'},
{setValue: ['id=q', 'flexbox']},
{logData: 1},
{submitForm: 'id=search-form'},
'waitForComplete'
])
wpt.runTest(script, {
location: 'Dulles:Chrome.Cable',
firstViewOnly: false,
runs: 1,
video: true
}, function (err, result) {
console.log('Video frames:', result.data.runs[1].firstView.steps[0].videoFrames)
})
单点故障测试
网站通常依赖第三方来源来提供其关键数据(例如字体或脚本),因此必须考虑这些服务可能在某些时候出现故障的可能性。
当加载的资源是渲染阻塞时(即不是异步加载),这一点尤其重要,因为尝试从不可用的源加载文件可能会导致站点挂起一段时间,直到请求超时。下面的视频显示了 Twitter API 出现故障对 Business Insider 网站加载时间造成的影响。
为了使我们的测试考虑这一点,必须正确设置故障模式,如 Patrick Meenan 在本文中所述。
由于不可用的服务通常不会立即显示错误消息而失败,因此设置一个尝试从a-host-that-doesnt-exist.com加载内容的测试是不够的,因为它无法准确反映实际情况中发生的情况。
相反,Patrick 在blackhole.webpagetest.org上设置了一个特殊的服务器:顾名思义,它将对它的任何请求路由到任何地方。如果我们拦截对我们尝试测试的主机的所有请求并将它们重定向到此黑洞服务器,那么我们就有了对真实故障场景的非常好的模拟。
下面的示例显示了如何运行一个模拟code.jquery.com故障的测试,许多站点都使用它来加载 jQuery。我们可以将获得的结果与正常测试的结果进行比较,以了解此单点故障对各种性能指标的影响。
var script = wpt.scriptToString([
// Redirecting 'jquery.com' to the black hole server
{setDnsName: ['jquery.com', 'blackhole.webpagetest.org']},
{navigate: 'https://css-tricks.org.cn'}
])
wpt.runTest(script, {
location: 'Dulles:Chrome.Cable',
firstViewOnly: false,
runs: 1,
video: true
}, function (err, result) {
// Extract your metrics here
})
总结
本文并非试图全面介绍 WebPageTest,因为这需要一本完整的书籍!相反,我们重点关注平台是什么、它提供的信息以及更具体地如何以编程方式与之交互。
但是提取数据仅仅是开始——我们如何使用它才能影响我们网站的性能。我很快将继续讨论如何利用此处显示的内容来构建定制的性能监控工具,该工具能够可视化一段时间内的性能指标、建立性能预算并在未满足性能预算时收到警报。
值得注意的是,公共 WebPageTest 上的 API 使用限制故意设置得很低,因为代理是由各种公司捐赠的。如果您需要大量测试,则可以使用像SpeedCurve这样的商业服务,它运行在 WebPageTest 的私有版本之上。
免责声明:我是 SpeedCurve 的创始人!
感谢这篇文章。我在我的几个网站上运行了测试,并了解到我需要学习更多关于缓存的知识!
感谢您提供有见地的文章。我真的很感谢您为此付出的时间和精力。
对我来说,从这个测试中获得的指标以及从这些指标中可以获得的业务价值将是救星。
我希望获得您后续文章的更新。
太棒了!