Devtools 服务
一个 WebdriverIO 服务,允许您在测试中运行 Chrome DevTools 命令
从 Chrome v63 及更高版本开始,浏览器开始支持多客户端,允许任意客户端访问Chrome DevTools 协议。这为超越WebDriver 协议 自动化 Chrome 提供了有趣的机会。通过此服务,您可以增强 wdio 浏览器对象以利用该访问权限,并在测试中调用 Chrome DevTools 命令,例如拦截请求、限制网络功能或获取 CSS/JS 覆盖率。
从 Firefox 86 开始,Chrome DevTools 协议的一个子集 已通过传递功能 "moz:debuggerAddress": true
实现。
注意:此服务目前仅支持 Chrome v63 及更高版本、Chromium 以及 Firefox 86 及更高版本!鉴于云供应商不公开访问 Chrome DevTools 协议,因此此服务通常也仅在本地运行测试或通过Selenium Grid v4 或更高版本运行测试时有效。
安装
最简单的方法是将 @wdio/devtools-service
作为开发依赖项保留在您的 package.json
中,通过
npm install @wdio/devtools-service --save-dev
有关如何安装 WebdriverIO
的说明,请参见此处。
配置
为了使用该服务,您只需将该服务添加到 wdio.conf.js
中的服务列表中,例如
// wdio.conf.js
export const config = {
// ...
services: ['devtools'],
// ...
};
用法
@wdio/devtools-service
提供了多种功能,可帮助您超越 WebDriver 协议自动化 Chrome。它使您可以访问 Chrome DevTools 协议以及Puppeteer 实例,您可以使用该实例通过 Puppeteer 自动化界面自动化 Chrome。
性能测试
DevTools 服务允许您捕获每次页面加载或由点击引起的页面转换的性能数据。要启用它,请调用 browser.enablePerformanceAudits(<options>)
。完成捕获所有必要的性能数据后,将其禁用以恢复节流设置,例如
import assert from 'node:assert'
describe('JSON.org page', () => {
before(async () => {
await browser.enablePerformanceAudits()
})
it('should load within performance budget', async () => {
/**
* this page load will take a bit longer as the DevTools service will
* capture all metrics in the background
*/
await browser.url('http://json.org')
let metrics = await browser.getMetrics()
assert.ok(metrics.speedIndex < 1500) // check that speedIndex is below 1.5ms
let score = await browser.getPerformanceScore() // get Lighthouse Performance score
assert.ok(score >= .99) // Lighthouse Performance score is at 99% or higher
$('=Esperanto').click()
metrics = await browser.getMetrics()
assert.ok(metrics.speedIndex < 1500)
score = await browser.getPerformanceScore()
assert.ok(score >= .99)
})
after(async () => {
await browser.disablePerformanceAudits()
})
})
您可以通过使用 emulateDevice
命令、节流 CPU 和网络以及将 mobile
设置为外形尺寸来模拟移动设备
await browser.emulateDevice('iPhone X')
await browser.enablePerformanceAudits({
networkThrottling: 'Good 3G',
cpuThrottling: 4,
formFactor: 'mobile'
})
以下命令及其结果可用
getMetrics
获取最常用的性能指标。
console.log(await browser.getMetrics())
/**
* { timeToFirstByte: 566,
* serverResponseTime: 566,
* domContentLoaded: 3397,
* firstVisualChange: 2610,
* firstPaint: 2822,
* firstContentfulPaint: 2822,
* firstMeaningfulPaint: 2822,
* largestContentfulPaint: 2822,
* lastVisualChange: 15572,
* interactive: 6135,
* load: 8429,
* speedIndex: 3259,
* totalBlockingTime: 31,
* maxPotentialFID: 161,
* cumulativeLayoutShift: 2822 }
*/
getDiagnostics
获取有关页面加载的一些有用诊断信息。
console.log(await browser.getDiagnostics())
/**
* { numRequests: 8,
* numScripts: 0,
* numStylesheets: 0,
* numFonts: 0,
* numTasks: 237,
* numTasksOver10ms: 5,
* numTasksOver25ms: 2,
* numTasksOver50ms: 2,
* numTasksOver100ms: 0,
* numTasksOver500ms: 0,
* rtt: 147.20600000000002,
* throughput: 47729.68474448835,
* maxRtt: 176.085,
* maxServerLatency: 1016.813,
* totalByteWeight: 62929,
* totalTaskTime: 254.07899999999978,
* mainDocumentTransferSize: 8023 }
*/
getMainThreadWorkBreakdown
返回一个列表,其中包含所有主线程任务及其总持续时间的细分。
console.log(await browser.getMainThreadWorkBreakdown())
/**
* [ { group: 'styleLayout', duration: 130.59099999999998 },
* { group: 'other', duration: 44.819 },
* { group: 'paintCompositeRender', duration: 13.732000000000005 },
* { group: 'parseHTML', duration: 3.9080000000000004 },
* { group: 'scriptEvaluation', duration: 2.437999999999999 },
* { group: 'scriptParseCompile', duration: 0.20800000000000002 } ]
*/
getPerformanceScore
返回Lighthouse 性能得分,它是以下指标的加权平均值:firstContentfulPaint
、speedIndex
、largestContentfulPaint
、cumulativeLayoutShift
、totalBlockingTime
、interactive
、maxPotentialFID
或 cumulativeLayoutShift
。
console.log(await browser.getPerformanceScore())
/**
* 0.897826278457836
*/
enablePerformanceAudits
为所有由调用 url
命令或点击链接或任何导致页面加载的操作引起的页面加载启用自动性能审计。您可以传入一个配置对象来确定一些节流选项。默认节流配置文件是具有 4 倍 CPU 节流的 Good 3G
网络。
await browser.enablePerformanceAudits({
networkThrottling: 'Good 3G',
cpuThrottling: 4,
cacheEnabled: true,
formFactor: 'mobile'
})
以下网络节流配置文件可用:offline
、GPRS
、Regular 2G
、Good 2G
、Regular 3G
、Good 3G
、Regular 4G
、DSL
、Wifi
和 online
(无节流)。
设备模拟
该服务允许您模拟特定的设备类型。如果设置,浏览器视口将被修改以适应设备功能,并且用户代理将根据设备用户代理设置。要设置预定义的设备配置文件,您可以运行
await browser.emulateDevice('iPhone X')
// or `browser.emulateDevice('iPhone X', { inLandscape: true })` if you want to be in landscape mode
// or `browser.emulateDevice('iPhone X', { osVersion: "15.0" })` if you want to use emulated device with custom OS version
可用的预定义设备配置文件有:Blackberry PlayBook
、BlackBerry Z30
、Galaxy Note 3
、Galaxy Note II
、Galaxy S III
、Galaxy S5
、iPad
、iPad Mini
、iPad Pro
、iPhone 4
、iPhone 5
、iPhone 6
、iPhone 6 Plus
、iPhone 7
、iPhone 7 Plus
、iPhone 8
、iPhone 8 Plus
、iPhone SE
、iPhone X
、JioPhone 2
、Kindle Fire HDX
、LG Optimus L70
、Microsoft Lumia 550
、Microsoft Lumia 950
、Nexus 10
、Nexus 4
、Nexus 5
、Nexus 5X
、Nexus 6
、Nexus 6P
、Nexus 7
、Nokia Lumia 520
、Nokia N9
、Pixel 2
、Pixel 2 XL
您还可以通过提供一个对象作为参数来定义自己的设备配置文件,如下例所示
await browser.emulateDevice({
viewport: {
width: 550, // <number> page width in pixels.
height: 300, // <number> page height in pixels.
deviceScaleFactor: 1, // <number> Specify device scale factor (can be thought of as dpr). Defaults to 1
isMobile: true, // <boolean> Whether the meta viewport tag is taken into account. Defaults to false
hasTouch: true, // <boolean> Specifies if viewport supports touch events. Defaults to false
isLandscape: true // <boolean> Specifies if viewport is in landscape mode. Defaults to false
},
userAgent: 'my custom user agent'
})
注意
这仅在您不使用 capabilities['goog:chromeOptions']
中的 mobileEmulation
时有效。如果存在 mobileEmulation
,则对 browser.emulateDevice()
的调用将不会执行任何操作。
PWA 测试
使用 checkPWA
命令,您可以验证您的 Web 应用程序在渐进式 Web 应用程序方面是否符合最新的 Web 标准。它检查
- 您的应用程序是否可安装
- 是否提供服务工作线程
- 是否具有启动画面
- 是否提供 Apple Touch 和可屏蔽图标
- 是否可以在移动设备上提供服务
如果您对其中一项检查不感兴趣,则可以传入您要运行的检查列表。如果所有检查都通过,则 passed
属性将返回 true
。如果它们失败,您可以使用 details
属性使用失败的详细信息丰富您的失败消息。
// open page first
await browser.url('https://webdriverio.node.org.cn')
// validate PWA
const result = await browser.checkPWA()
expect(result.passed).toBe(true)
捕获代码覆盖率
该服务允许您捕获被测应用程序的代码覆盖率。为此,您需要在服务设置中启用此功能
// wdio.conf.js
services: [
['devtools', {
coverageReporter: {
enable: true,
type: 'html', // lcov, json, text
logDir: __dirname + '/coverage',
exclude: [/resources/]
}
}]
]
然后,您可以访问一个命令,该命令可以计算已覆盖代码行和分支的比率,以便您在测试中进行断言
const coverage = await browser.getCoverageReport()
expect(coverage.lines.total).toBeAbove(0.9)
expect(coverage.statements.total).toBeAbove(0.9)
expect(coverage.functions.total).toBeAbove(0.9)
expect(coverage.branches.total).toBeAbove(0.9)
Chrome DevTools 访问
目前,该服务允许两种不同的方法来访问 Chrome DevTools 协议
cdp
命令
cdp
命令是添加到浏览器作用域的自定义命令,允许您直接调用协议中的命令。
browser.cdp(<domain>, <command>, <arguments>)
例如,如果您想获取页面上的 JavaScript 代码覆盖率,您可以执行以下操作
it('should take JS coverage', async () => {
/**
* enable necessary domains
*/
await browser.cdp('Profiler', 'enable')
await browser.cdp('Debugger', 'enable')
/**
* start test coverage profiler
*/
await browser.cdp('Profiler', 'startPreciseCoverage', {
callCount: true,
detailed: true
})
await browser.url('http://google.com')
/**
* capture test coverage
*/
const { result } = await browser.cdp('Profiler', 'takePreciseCoverage')
const coverage = result.filter((res) => res.url !== '')
console.log(coverage)
})
getNodeId(selector)
和 getNodeIds(selector)
命令
辅助方法,用于获取页面中元素的 nodeId。NodeId 类似于 WebDriver 节点 ID,是节点的标识符。它可以用作其他 Chrome DevTools 方法的参数,例如 DOM.focus
。
const nodeId = await browser.getNodeId('body')
console.log(nodeId) // outputs: 4
const nodeId = await browser.getNodeIds('img')
console.log(nodeId) // outputs: [ 40, 41, 42, 43, 44, 45 ]
startTracing(categories, samplingFrequency)
命令
开始跟踪浏览器。您可以选择传入自定义跟踪类别(默认为 此列表)和采样频率(默认为 10000
)。
await browser.startTracing()
endTracing
命令
停止跟踪浏览器。
await browser.endTracing()
getTraceLogs
命令
返回在跟踪期间捕获的跟踪日志。您可以使用此命令将跟踪日志存储在文件系统上,以便通过 Chrome DevTools 接口分析跟踪。
import fs from 'node:fs/promises'
await browser.startTracing()
await browser.url('http://json.org')
await browser.endTracing()
await fs.writeFile('/path/to/tracelog.json', JSON.stringify(browser.getTraceLogs()))
getPageWeight
命令
返回上次页面加载的页面权重信息。
await browser.startTracing()
await browser.url('https://webdriverio.node.org.cn')
await browser.endTracing()
console.log(await browser.getPageWeight())
// outputs:
// { pageWeight: 2438485,
// transferred: 1139136,
// requestCount: 72,
// details: {
// Document: { size: 221705, encoded: 85386, count: 11 },
// Stylesheet: { size: 52712, encoded: 50130, count: 2 },
// Image: { size: 495023, encoded: 482433, count: 36 },
// Script: { size: 1073597, encoded: 322854, count: 15 },
// Font: { size: 84412, encoded: 84412, count: 5 },
// Other: { size: 1790, encoded: 1790, count: 2 },
// XHR: { size: 509246, encoded: 112131, count: 1 } }
// }
设置浏览器的下载路径
cdp
命令可用于调用 Devtools 协议的 Page.setDownloadBehavior
命令来设置下载文件时的行为。确保 downloadPath
是绝对路径,并且在下载文件之前调用 browser.cdp()
。
await browser.cdp('Page', 'setDownloadBehavior', {
behavior: 'allow',
downloadPath: '/home/root/webdriverio-project/',
});
访问 Puppeteer 实例
该服务在后台使用 Puppeteer 进行自动化。您可以通过调用 getPuppeteer
命令来访问所使用的实例。**注意:**Puppeteer 命令是异步的,需要在 call
命令中调用或通过 async/await
处理。
describe('use Puppeteer', () => {
it('by wrapping commands with call', () => {
await browser.url('http://json.org')
const puppeteer = await browser.getPuppeteer()
const page = await browser.call(() => puppeteer.pages())[0]
console.log(await browser.call(() => page.title()))
})
})
事件监听器
为了捕获浏览器中的网络事件,您可以向 Chrome DevTools 注册事件监听器。完整的可用 CDP 网络事件 列表。
it('should listen on network events', () => {
await browser.cdp('Network', 'enable')
await browser.on('Network.requestWillBeSent', (event) => {
console.log(`Request: ${event.request.method} ${event.request.url}`);
});
await browser.on('Network.responseReceived', (event) => {
console.log(`Response: ${event.response.status} ${event.response.url}`);
});
await browser.on('Network.loadingFailed', (event) => {
console.log(`Request failed: ${event.errorText}`);
});
await browser.url('https://www.google.com')
})
有关 WebdriverIO 的更多信息,请参阅 主页。