跳至主要内容

WebdriverIO v9 发布

·阅读 15 分钟

整个 Webdriverio 开发团队都非常激动和自豪地宣布今天发布 WebdriverIO v9!

这标志着所有使用 WebdriverIO 作为其测试自动化工具的项目的激动人心的新时代的开始。借助浏览器团队(例如 Chrome 和 Firefox 团队)的出色工作,我们现在进入了一个新的时代,由于新的WebDriver Bidi 协议,它提供了比以往任何时候都强大的自动化功能。在 WebdriverIO v9 中,我们一直在努力成为这个新时代的采用者先锋,并让您首先利用协议的功能。

让我们探索此版本中的新功能、更新和优化。

新功能

v9 中的大多数新功能都是由浏览器中现在可用的 WebDriver Bidi 功能启用的。升级到 v9 后,所有会话都将自动使用 Bidi,除非您通过新的 wdio:enforceWebDriverClassic 功能显式禁用它。

注意

如果您远程环境不支持 WebDriver Bidi,则不幸的是您将无法使用这些功能。您可以通过查看 browser.isBidi 属性来检查您的会话中是否支持 Bidi。

新的 url 命令参数

url 命令已从简单的导航工具发展成为功能强大的功能丰富的命令。

传入自定义标头

您现在可以传递自定义标头,这些标头将在浏览器发出请求时应用。这对于设置会话 Cookie 以自动登录很有用。

await browser.url('https://webdriverio.node.org.cn', {
headers: {
Authorization: 'Bearer XXXXX'
}
});

请注意,虽然您可以通过mock 命令修改浏览器处理所有请求的方式,但应用于url 命令的更改仅持续到特定页面加载,并在导航完成后重置。

克服基本身份验证

通过基本身份验证 自动化用户身份验证从未如此简单

await browser.url('https://the-internet.herokuapp.com/basic_auth', {
auth: {
user: 'admin',
pass: 'admin'
}
});
await expect($('p=Congratulations! You must have the proper credentials.').toBeDisplayed();

运行初始化脚本

使用 beforeLoad 参数在网站加载任何内容之前注入 JavaScript。这对于操作 Web API 很有用,例如:

// navigate to a URL and mock the battery API
await browser.url('https://pazguille.github.io/demo-battery-api/', {
onBeforeLoad (win) {
// mock "navigator.battery" property
// returning mock charge object
win.navigator.getBattery = () => Promise.resolve({
level: 0.5,
charging: false,
chargingTime: Infinity,
dischargingTime: 3600, // seconds
})
}
})
// now we can assert actual text - we are charged at 50%
await expect($('.battery-percentage')).toHaveText('50%')
// and has enough juice for 1 hour
await expect($('.battery-remaining')).toHaveText('01:00)

在此示例中,我们覆盖了 Navigator 接口的getBattery 方法。

新的 addInitScript 命令

addInitScript 命令使您能够将脚本注入浏览器,该脚本在每次打开新的浏览上下文时都会触发。这包括导航到 URL 或在应用程序中加载 iframe 等操作。您传递给此命令的脚本以回调作为其最后一个参数接收,允许您将值从浏览器发送回您的 Node.js 环境。

例如,要随时了解应用程序中节点添加或删除元素的情况,您可以使用以下示例

const script = await browser.addInitScript((myParam, emit) => {
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
emit(mutation.target.nodeName)
}
})
observer.observe(document, { childList: true, subtree: true })
})

script.on('data', (data) => {
console.log(data) // prints: BODY, DIV, P, ...
})

此初始化脚本可以修改全局变量并覆盖内置的 Web API 原语,使您能够配置测试环境以满足您的特定需求。

跨浏览器请求模拟

WebdriverIO 在v6.3.0 中引入了请求模拟,仅限于 Chromium 浏览器。在 v9 中,我们现在使用 WebDriver Bidi,将支持扩展到所有浏览器。此增强功能允许在请求发送到网络之前修改请求

// mock all API requests
const mock = await browser.mock('**/api/**')
// apply auth token to each request
mock.request({
headers: { 'Authorization': 'Bearer XXXXXX' }
})

自动穿透 Shadow Root

使用 Web Components 测试应用程序现在非常简单,因为它具有自动穿透 Shadow Root 的功能。WebdriverIO 现在跟踪所有ShadowRoot 节点并在其中搜索。

例如,假设我们想自动化以下包含多个嵌套 Shadow Root 的日期选择器组件,在应用程序中的任何元素上“右键点击 > 检查”,并观察它们在不同 Shadow Root 中嵌套的深度。

为了更改日期,您现在只需调用:

await browser.url('https://ionicframework.cn/docs/usage/v8/datetime/basic/demo.html?ionic:mode=md')
await browser.$('aria/Sunday, August 4]').click()
await browser.$('.aux-input').getValue() // outputs "2024-08-04T09:00:00"

虽然 WebdriverIO 之前支持"深度选择器",但此功能仅限于 CSS 选择器。新的选择器引擎现在支持所有选择器类型,包括辅助功能标签。

借助增强的定位器引擎,可以轻松找到多个嵌套 Shadow Root 中的元素。WebdriverIO 是第一个支持 openclosed 模式下 Shadow Root 的框架。

改进的参数序列化

使用 WebDriver Classic,将数据对象从测试移动到浏览器环境的能力相当有限,仅限于 DOM 元素和可序列化的对象和类型。使用 WebDriver Bidi,我们现在能够更好地将不可序列化的数据对象转换为在浏览器中用作其本身的对象。除了 MapSet 等已知的 JavaScript 原语之外,该协议还允许序列化 InfinitynullundefinedBigInt 等值。

这是一个在 Node.js 中组合 JavaScript Map 对象并将其传递给浏览器,在浏览器中它会自动反序列化回 Map,反之亦然的示例

const data = new Map([
['username', 'Tony'],
['password', 'secret']
])
const output = await browser.execute(
(data) => `${data.size} entrie(s), username: ${data.get('username')}, password: ${data.get('secret')}`,
data
)

console.log(output)
// outputs: "1 entrie(s), username: Tony, password: secret"

这将使传递数据并使用自定义脚本变得更容易,这些脚本现在可以返回丰富的对象数据,这将有助于更好地观察应用程序的状态。它将允许像 WebdriverIO 这样的框架更深入地与浏览器环境集成,并在未来构建更多有用的功能。

设置视口

虽然 WebdriverIO 允许您在桌面和移动浏览器中测试您的应用程序,但通过调整浏览器视口来模拟移动用户通常更容易,以检查应用程序是否在响应式模式下正确呈现。使用 WebDriver Classic,这可能具有挑战性,因为浏览器窗口无法缩放到非常小的尺寸,并且通常保持 500px 的最小宽度。例如

await browser.url('https://webdriverio.node.org.cn')
await browser.setWindowSize(393, 659)
console.log(await browser.execute(() => window.innerWidth)) // returns `500`

WebdriverIO setWindowSize Result

WebdriverIO v9 引入了 setViewport 命令,使您能够将应用程序的视口调整到任何尺寸,包括修改 devicePixelRatio。与更改浏览器窗口整体尺寸的 setWindowSize 不同,setViewport 特别是调整渲染应用程序的画布大小,而浏览器窗口尺寸保持不变。

为了简化移动设备模拟,我们增强了 emulate 命令。此新功能允许您通过简单地指定移动设备的名称来同时调整视口大小、设备像素比和用户代理。例如

await browser.url('https://webdriverio.node.org.cn')
await browser.emulate('device', 'iPhone 15')
console.log(await browser.execute(() => window.innerWidth)) // returns `393`
console.log(await browser.execute(() => navigator.userAgent)) // returns `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36`

WebdriverIO setViewportSize Result

虽然我们建议在实际的移动设备上运行移动测试,因为移动浏览器引擎与用于桌面浏览器的引擎不同,但这可能是一个简单的应急方案,如果我们只想快速验证应用程序如何在移动视口中呈现。

伪计时器支持

想要更改浏览器中的时间?使用 WebdriverIO v9,现在可以在浏览器中伪造测试的时间了。我们增强了 emulate 命令,添加了一个新属性:clock。这允许您将日期和时间设置为所需的任何值,并控制时间何时前进。以下是它的工作原理

const clock = await browser.emulate('clock', { now: new Date(2021, 3, 14) })
console.log(await browser.execute(() => new Date().getTime())) // returns 1618383600000

await clock.tick(1000)
console.log(await browser.execute(() => new Date().getTime())) // returns 1618383601000

新的 clock 模拟返回一个 Clock 对象,该对象包含 ticksetSystemTimerestore 等方法,以便在测试中精确控制时间。

自动对话框处理

如果您的应用程序使用原生浏览器对话框,例如 alertconfirm,当这些提示意外出现时,有时会很棘手。在以前的版本中,如果未正确处理这些对话框,所有命令都将失败。在 WebdriverIO v9 中,我们将开始自动抑制对话框,除非您显式地为此注册了一个监听器,例如:

await browser.url('https://webdriverio.node.org.cn')
browser.on('dialog', async (dialog) => {
console.log(dialog.message()) // outputs: "Hello Dialog"
await dialog.dismiss()
})

await browser.execute(() => alert('Hello Dialog'))

新的 dialog 事件会传入一个 dialog 对象,允许您在其上调用 acceptdismiss,获取对话框的类型或消息以及其默认值。我们希望这将在未来减少因浏览器意外警报导致的测试失败。

自动等待元素变得可交互

当元素不可见、无法滚动到视口或被禁用时,则认为该元素不可交互,并且以前 WebdriverIO 在发生这种情况时会抛出错误。在 v8 中,我们已经改进了显示元素的 HTML,但在 v9 中,我们现在会在对元素执行直接操作(例如点击或使用 setValue!)时自动等待元素变得可交互。

这意味着您不再需要编写如下代码

const submitButton = await $('button[type="submit"]');
await submitButton.waitForEnabled(); // validation of the form takes time, during which the button is disabled
await submitButton.click();

现在您可以将其简化为

const submitButton = await $('button[type="submit"]');
await submitButton.click(); // automatically waits for the button to become enabled

Web Components 的快照测试

如果您的应用程序使用了大量的 Web Components,由于 WebdriverIO 难以查看 Shadow Root 内部,因此无法使用快照功能。在 v9 中,WebdriverIO 现在可以完全查看所有元素,并允许通过将 Web Components 转换为 Declarative Shadow DOM 来对打开和关闭的 Web Components 进行快照,例如:

await browser.url('https://ionicframework.cn/docs/usage/v8/button/basic/demo.html?ionic:mode=md')

// get snapshot of web component without its styles
const snapshot = await $('ion-button').getHTML({ excludeElements: ['style'] })

// assert snapshot
await expect(snapshot).toMatchInlineSnapshot(`
<ion-button class="md button button-solid ion-activatable ion-focusable hydrated">Default
<template shadowrootmode="open">
<button type="button" class="button-native" part="native">
<span class="button-inner">
<slot name="icon-only"></slot>
<slot name="start"></slot>
<slot></slot>
<slot name="end"></slot>
</span>
<ion-ripple-effect role="presentation" class="md hydrated">
<template shadowrootmode="open"></template>
</ion-ripple-effect>
</button>
</template>
</ion-button>
`)

为了启用此功能,我们增强了 getHTML 命令,并将其之前的布尔参数替换为一个对象,以便更好地控制命令行为。在 getHTML 命令文档中阅读有关新的 GetHTMLOptions 选项的更多信息。

值得注意的重大更改

我们努力最大程度地减少重大更改,以避免需要花费大量时间才能升级到最新版本的 WebdriverIO。但是,主要版本提供了删除我们不再建议使用的已弃用接口的机会。

访问元素属性

如您所知,WebdriverIO 允许您从浏览器中获取元素,以便在测试中与之交互。当您与元素交互时,WebdriverIO 会保存对该元素的引用以及其他元数据,以便将命令正确地定向到正确的元素,例如,如果元素变得陈旧则重新获取元素。您可能已经访问了一些这些元素属性,例如通过 $('elem').selector 评估选择器或通过 $('elem').elementId 获取其引用,以及 其他 属性。

这些属性不再可以通过这种方式访问。相反,您需要调用 getElement 来访问 WebdriverIO.Element 的属性,并调用 getElements 来访问 WebdriverIO.ElementArray 的属性。这是一个示例

// WebdriverIO v8 and older
const elem = await $('elem')
console.log(elem.selector) // ❌ returns a `Promise<string>` now

// WebdriverIO v9
const elem = await $('elem').getElement()
console.log(elem.selector) // ✅ returns "elem"

此更改修复了 IDE 中一个 非常恼人的错误,该错误误导 IDE 认为 $('elem') 返回一个 Promise,导致它们自动将链式元素调用包装在多个 await 语句中,例如 await (await $('elem')).getText()。此改进将使编写测试变得更加容易。

删除 XXXContaining 匹配器

expect-webdriverio 中,我们维护了为 WebdriverIO 元素和端到端测试场景定制的匹配器。之前,我们提供了 XXXContaining 版本的每个匹配器以允许部分值匹配。但是,底层的 expect 包已经通过 非对称匹配器 支持此功能。为了减少我们维护的匹配器数量,我们决定删除自定义的 XXXContaining 匹配器,转而使用这些非对称匹配器。

迁移到非对称匹配器非常简单。这是一个示例

- await expect($('elem')).toHaveTextContaining('Hello')
+ await expect($('elem')).toHaveText(expect.stringContaining('Hello'))

删除 JSON Wire 协议命令

JSON Wire 协议是 Selenium 开发的第一个自动化协议,它允许使用任何支持 HTTP 的语言进行远程浏览器自动化。2012 年,Selenium 的创建者开始着手正式标准化该协议,该协议现在已在所有浏览器中得到支持。这项工作最终导致 WebDriver 协议成为 W3C 推荐标准,从而导致浏览器驱动程序和云供应商迁移到此官方标准。

我们认为现在是告别旧的 JSON Wire 协议并拥抱 WebDriver 未来的时候了。如果您仍在较旧的浏览器上进行测试,并且自动化脚本需要这些协议命令,则可以安装 @wdio/jsonwp-service 以继续使用必要的命令。

删除 devtools@wdio/devtools-service

在 WebdriverIO v8.15 中,我们 引入了 浏览器驱动程序的自动设置作为核心功能。此更改使 devtools 包的最初目的变得过时。最初,devtools 包旨在通过 Puppeteer 实现 WebDriver 规范,Puppeteer 会为您处理浏览器设置。这允许在无需下载任何浏览器驱动程序的情况下使用 WebdriverIO。由于此功能不再需要,因此我们决定删除 devtools 包以及 automationProtocol 选项。

此外,随着 WebDriver Bidi 的采用,我们现在拥有了一个适用于大多数自动化需求的标准化协议。尽管如此,使用 Chrome Devtools 进行更复杂的浏览器内省一直是 WebdriverIO 中的一项热门功能。之前,我们推荐使用 @wdio/devtools-service 包来满足这些用例。但是,我们发现大多数用户更喜欢直接使用 Puppeteer 而不是我们的自定义 DevTools 接口。WebdriverIO 用户可以同时使用 WebdriverIO 原语和 Puppeteer 自动化浏览器。通过调用 getPuppeteer 命令,您可以获得正在自动化的浏览器的 Puppeteer 实例,从而可以通过更强大且维护良好的接口访问所有 Devtools 功能。因此,用户主要利用 @wdio/devtools-service 包来测试性能和其他 Google Lighthouse 功能。接下来,这些功能将在一个名为 `@wdio/lighthouse-service 的新服务包下提供。

请放心,我们已经找到了这些包的良好用例,并且不会放弃它们。

项目更新

虽然我们一直在努力添加新功能,使 WebdriverIO 能够更有效地测试 Web 和移动应用程序,但我们也一直在关注一些内部计划,以确保项目的未来发展。

pnpm 迁移

随着我们的核心包随着时间的推移而增长,我们在使用 NPM 时面临着越来越多的挑战,尤其是在我们的单体存储库中进行依赖项解析时。此外,自定义链接使我们的测试基础设施比必要的更加复杂。

鉴于不断发展的 JavaScript 工具生态系统,我们评估并采用了新技术来改善项目中的开发人员体验。通过迁移到 pnpm,我们消除了许多自定义脚本,简化了整体项目设置,并且现在可以享受更快速、更可靠的依赖项解析,并且在通过 Dependabot 进行定期更新时合并冲突更少。

Esbuild 迁移

自从在 v7 中引入 TypeScript 以来,我们一直使用 TypeScript 编译器将文件转换为 JavaScript 以发布到 NPM。但是,随着代码库的增长,我们在使用此设置时遇到了挑战。例如,webdriverio@wdio/browser-runner 包中的一些文件仅在浏览器环境中使用,但它们却像在 Node.js 中一样进行编译,从而导致兼容性问题。

在 WebdriverIO v9 中,所有项目文件都使用 Esbuild 进行编译。此功能强大且超快的捆绑器允许我们专门为目标环境编译代码,确保应用了正确的 polyfill。此外,每个包现在都捆绑到单个 JavaScript 文件中,从而略微提高了性能,尤其是在组件测试方面。

ts-node 迁移到 tsx

WebdriverIO 已从 ts-node 迁移到 tsx,用于在运行时将 TypeScript 测试编译为 JavaScript。此更改提供了更好的 ESM 支持、改进的性能以及 TypeScript 测试中错误的更准确的堆栈跟踪。

放弃 Node.js v16 支持

与每个版本一样,我们正在停止对旧的、未维护的 Node.js 版本的支持。WebdriverIO v9 将不再支持 Node.js v16 及以下版本。我们建议用户升级到 Node.js v20。

接下来是什么?

随着 WebdriverIO 庆祝其 13 年的发展历程,对新测试功能的需求也随着不断发展的测试行业和 Web 标准而不断增长。随着现代浏览器中 WebDriver Bidi 协议的采用,Selenium、Nightwatch 和 WebdriverIO 等工具可以与 Playwright 和 Cypress 的功能相匹配,而无需依赖专有的浏览器补丁或特定的执行环境。

WebdriverIO 将继续集成更多 WebDriver Bidi 功能以构建强大的新功能。我们对未来的发展感到兴奋,包括一个 WebdriverIO devtools 应用程序,它将通过提供一个强大的 UI 来检查和调试被测应用程序,从而显著增强测试体验。有关此计划的更多详细信息将很快分享!

致谢

我们向我们的高级赞助商 BrowserStackSauce Labs 以及所有为该项目做出贡献的人表示感谢。WebdriverIO 仍然是社区驱动且独立的,依赖于像您这样的人的贡献。加入我们的 开放办公时间 并考虑回馈项目。

祝愿我们未来数年继续创新与协作! 🚀

感谢您的阅读!

欢迎!我如何帮助您?

WebdriverIO AI Copilot