
WebdriverIO Multiremote 多会话测试完全指南单次执行协调多个浏览器与移动设备【免费下载链接】webdriverioNext-gen browser and mobile automation test framework for Node.js项目地址: https://gitcode.com/GitHub_Trending/we/webdriverio本文围绕 WebdriverIO 官方文档《Multiremote》展开系统讲解如何在一个测试中同时创建并协调多个浏览器/移动设备会话从 standalone 模式下的multiremote()调用、WDIO Testrunner 中 capabilities 对象的配置到getInstance()、select()等实例访问方式、TypeScript 类型扩展以及底层命令分发与并行执行原理。读完本文你将掌握编写聊天应用、WebRTC 等多用户协同场景集成测试的完整实战方案。Multiremote 是什么单测试内的多会话协调WebdriverIO 允许你在一个测试中运行多个自动化会话这一能力被称作Multiremote。它面向的是那些需要多个用户同时参与的特性测试——最典型的场景是聊天应用一个用户发消息、另一个用户收消息并断言或WebRTC类实时通信应用。在没有 Multiremote 之前开发者需要手动创建多个 remote 实例并逐个在实例上重复执行newSession、url这类公共命令。Multiremote 则允许你创建一个多路远程实例然后对所有浏览器同时下达命令。正如 packages/webdriverio/src/index.ts 中multiremote()的 JSDoc 所言Instead of creating a couple of remote instances where you need to execute common commands like newSession() or url() on each instance, you can simply create a multiremote instance and control all browsers at the same time.重要边界不是并行测试框架必须先厘清一个容易误用的点——官方文档在 Multiremote 页顶部就给出提示:::info块Multiremote不是用来并行执行你所有测试的。它旨在帮助协调多个浏览器和/或移动设备以完成特殊的集成测试例如聊天应用。也就是说Multiremote 的定位是同一个测试内部的跨会话协同而不是把不同 spec 分发到不同浏览器上并行跑那是 WDIO Testrunner 常规的并行能力。从 packages/wdio-config/src/lib/config.ts 相关的配置结构详见 Capabilities.ts 的TestrunnerCapabilities类型可以看到RequestedMultiremoteCapabilities与普通 capabilities 数组是两种并列形态。返回值约定按声明顺序返回结果数组所有 multiremote 实例执行命令后返回的都是数组第一个结果对应 capabilities 对象中第一个定义的 capability第二个对应第二个以此类推。这个约定从文档到源码、测试都保持一致——例如 packages/webdriverio/tests/multiremote.test.ts 中执行browser.execute(() foobar)断言返回[foobar, foobar]。使用 standalone 模式创建 Multiremote 实例在 standalone独立脚本模式下核心 API 就是从webdriverio包导入的multiremote()函数。它接收一个以实例名为 key、capabilities 为 value 的对象import { multiremote } from webdriverio (async () { const browser await multiremote({ myChromeBrowser: { capabilities: { browserName: chrome } }, myFirefoxBrowser: { capabilities: { browserName: firefox } } }) // 两个浏览器同时打开 URL await browser.url(http://json.org) // 同时调用命令返回结果数组 const title await browser.getTitle() expect(title).toEqual([JSON, JSON]) // 同时在两个浏览器上定位并点击元素 const elem await browser.$(#someElem) await elem.click() // 只让某一个浏览器Firefox点击 await elem.getInstance(myFirefoxBrowser).click() })()这段代码体现了 Multiremote 的三个关键用法并行命令browser.url()、browser.getTitle()一次调用同时作用于所有实例元素级并行通过browser.$()得到的 multiremote 元素其click()等命令默认在所有实例上执行单实例操作通过elem.getInstance(myFirefoxBrowser)精确指定某个实例单独执行命令。底层实现实例创建与会话封装从源码看multiremote()的实际执行路径非常清晰packages/webdriverio/src/index.tsconst multibrowser new MultiRemote() const browserNames Object.keys(params) await Promise.all( browserNames.map(async (browserName) { const instance await remote(params[browserName]) return multibrowser.addInstance(browserName, instance) }) )它并行地为每个命名 capability 调用remote()创建独立会话再通过addInstance()注册到MultiRemote实例的instances字典中packages/webdriverio/src/multiremote.ts。随后用ProtocolDriver.attachToSession()把一个空会话包装在所有实例之上形成统一的多路远程浏览器对象。命令如何分发到每个实例当你在多路浏览器对象上调用任何命令时最终都会进入MultiRemote.commandWrapper()packages/webdriverio/src/multiremote.tsconst result await Promise.all( scopeEntries.map( ([, instance]) instancecommandName ) )命令通过Promise.all在所有实例上同时执行对于$命令会通过MultiRemote.elementWrapper()把各实例返回的元素包装成多路元素对象packages/webdriverio/src/multiremote.ts这样后续click()、getText()等元素级命令也能继续自动广播到每个实例。而元素命令的自动重试、stale element 处理等则由 middlewares.ts 中的multiremoteHandler承接——它对元素调用同样遍历this.instances并用Promise.all汇总结果。使用 WDIO Testrunner 配置 Multiremote在 Testrunner 模式下你不需要手动调用multiremote()——只需把wdio.conf.js里的capabilities从数组改为以浏览器名称为 key 的对象export const config { // ... capabilities: { myChromeBrowser: { capabilities: { browserName: chrome } }, myFirefoxBrowser: { capabilities: { browserName: firefox } } } // ... }这样 WebdriverIO 会自动创建两个 WebDriver 会话Chrome Firefox。而且远不止浏览器借助 Appium 你还可以同时启动两台移动设备或者一台移动设备 一个浏览器的混合组合。在数组内并行运行多组 Multiremote如果你希望多组多路会话并行执行可以把 capabilities 对象放进一个数组——每个数组元素代表一组 multiremote 会话。关键约束是每组内部的每个浏览器都必须包含capabilities字段WebdriverIO 正是依据这一字段来区分普通多能力数组与多路远程组export const config { // ... capabilities: [{ myChromeBrowser0: { capabilities: { browserName: chrome } }, myFirefoxBrowser0: { capabilities: { browserName: firefox } } }, { myChromeBrowser1: { capabilities: { browserName: chrome } }, myFirefoxBrowser1: { capabilities: { browserName: firefox } } }] // ... }此配置会并行创建两组会话组 0ChromeFirefox组 1ChromeFirefox。这一点与仓库内 e2e 配置的形态一致——e2e/wdio/wdio-multiRemote.conf.ts 中capabilities就是包含browserA、browserB、browserC三个实例的对象数组每个实例各自声明了 Chrome含goog:chromeOptionsheadless 参数与 Firefox含moz:firefoxOptions的浏览器级 options。混搭云端服务与本地后端Multiremote 甚至可以同时使用不同的后端例如一个本地 Chrome 一个云端浏览器或者本地 WebDriver/Appium Selenium Standalone。WebdriverIO 会自动检测 capabilities 中是否包含特定云端选项bstack:optionsBrowserStacksauce:optionsSauceLabstb:optionsTestingBot只要检测到这些字段就自动路由到对应云服务。下面是一个本地 Chrome BrowserStack Firefox的混搭配置export const config { // ... user: process.env.BROWSERSTACK_USERNAME, key: process.env.BROWSERSTACK_ACCESS_KEY, capabilities: { myChromeBrowser: { capabilities: { browserName: chrome } }, myBrowserStackFirefoxBrowser: { capabilities: { browserName: firefox, bstack:options: { // ... } } } }, services: [ [browserstack, selenium-standalone] ], // ... }任意 OS/浏览器的组合都是可行的移动端、桌面端皆可。你在测试中通过browser变量调用的所有命令都会在每个实例上并行执行从而简化集成测试的编写并加速执行。命令执行的同步语义与结果汇总所有 multiremote 命令默认是逐个实例按顺序同步推进的每个命令都会等所有浏览器都执行完毕后才返回。官方文档特别指出这一点——each command is executed one by one. This means that the command finishes once all browsers have executed it. 这种语义的好处是各个浏览器始终保持动作同步你始终清楚当前发生了什么不会出现一个浏览器已执行完、另一个还在半途的竞态混乱。调用 URL 后每个命令的返回结果都是以浏览器名为 key、命令结果为 value的对象形式对元素命令则是数组形式按声明顺序排列。WDIO Testrunner 中的实例如下// wdio testrunner 示例 await browser.url(https://www.whatismybrowser.com) const elem await $(.string-major) const result await elem.getText() console.log(result[0]) // 返回: Chrome 40 on Mac OS X (Yosemite) console.log(result[1]) // 返回: Firefox 35 on Mac OS X (Yosemite)这一行为在仓库测试中被反复验证——例如 packages/webdriverio/tests/multiremote.test.ts 对browser.$(#foo)得到的元素调用getSize()断言返回[{ width: 50, height: 30 }, { width: 50, height: 30 }]e2e/wdio/headless/multiRemoteTest.e2e.ts 中的多实例 e2e 测试也断言multiRemoteBrowser.getTitle()返回三个相同的标题数组。让每个浏览器各做各的getInstance 与全局实例虽然同步并行很方便但有些场景必须让各浏览器做不同的事。比如测试聊天应用时一个浏览器负责发送消息另一个浏览器等待接收消息再断言。使用 WDIO Testrunner 时WebdriverIO 会把各浏览器实例注册到全局作用域你在测试中可以通过全局变量如myChromeBrowser或browser.getInstance(myChromeBrowser)获取单个实例const myChromeBrowser browser.getInstance(myChromeBrowser) await myChromeBrowser.$(#message).setValue(Hi, I am Chrome) await myChromeBrowser.$(#send).click() // 等待消息到达 await $(.messages).waitForExist() // 检查某条消息中是否包含 Chrome 发出的内容 assert.true( ( await $$(.messages).map((m) m.getText()) ).includes(Hi, I am Chrome) )在这个示例中当myChromeBrowser点击#send之后myFirefoxBrowser的waitForExist才开始等待新消息出现——这正是一个发送、一个接收的协同测试范式。从 packages/wdio-runner/src/index.ts 可见runner 会把浏览器对象以multiremotebrowser为名注入全局作用域以提供更好的类型支持而实例的获取在 multiremote.ts 中就是一个简单的字典查找propertiesObject.getInstance { value: (browserName: string) this.instances[browserName] }在 standalone 模式下则通过elem.getInstance(myFirefoxBrowser)或在modifier()中把每个实例直接挂载到客户端对象上client[identifier] instance见 multiremote.ts因此你甚至可以直接写browser.myChromeBrowser。用字符串经 browser 对象访问实例除了全局变量你还可以通过browser对象本身以字符串下标访问实例例如browser[myChromeBrowser]或browser[myFirefoxBrowser]用browser.instances可以拿到所有实例名的列表。这在编写任一浏览器都能复用的测试步骤时尤其有用。典型的落地组合是Cucumber 数据驱动的用户角色。假设你的 capability 定义为// wdio.conf.js capabilities: { userA: { capabilities: { browserName: chrome } }, userB: { capabilities: { browserName: chrome } } }对应的.feature文件与 step 定义如下When User A types a message into the chatWhen(/^User (.) types a message into the chat/, async (userId) { await browser.getInstance(user${userId}).$(#message).setValue(Hi, I am Chrome) await browser.getInstance(user${userId}).$(#send).click() })这样User A、User B等角色就会被映射到对应的userA、userB实例彻底避免了在 step 里写死浏览器名的重复代码。用 select() 缩小多路范围仓库源码中还提供了select()方法需设置环境变量WDIO_ENABLE_MULTI_REMOTE_SELECTtrue启用它可以从多路对象中筛选出部分实例继续链式调用例如只对browserA查询元素。其底层实现在 multiremote.ts通过实例名列表构建新的MultiRemote并复用原属性描述符若所有请求的实例名都无效则抛出None of the following requested instances are valid: ...。相关的select行为含元素级select、链式select、筛选后自定义命令的保留等在 multiRemoteTest.e2e.ts 中有大量 e2e 断言覆盖。使用 TypeScript 扩展 Multiremote 类型如果你使用 TypeScript并且希望直接从多路远程对象上点出各实例可以扩展 multiremote 的类型定义。假设你的配置如下注意MultiremoteConfig类型// wdio.conf.ts export const config: WebdriverIO.MultiremoteConfig { // ... capabilities: { myAppiumDriver: { // ... }, myChromeDriver: { // ... } } // ... }然后在一个全局.d.ts文件中扩展MultiRemoteBrowser接口声明你自己的驱动实例名// wdio.d.ts declare namespace WebdriverIO { interface MultiRemoteBrowser { myAppiumDriver: WebdriverIO.Browser myChromeDriver: WebdriverIO.Browser } }之后即可在测试中获得完整的类型提示与直接访问能力multiRemoteBrowser.myAppiumDriver.$$(...) multiRemoteBrowser.myChromeDriver.$(...)这一机制背后的类型基础在 packages/webdriverio/src/types.ts 中定义MultiRemoteBrowser继承自MultiRemoteBase后者通过[key: string]: WebdriverIO.Browser之类的索引签名允许动态实例名具体见 types.ts 中MultiRemoteBase、MultiRemoteBrowserType、MultiRemoteElement等接口。扩展声明就相当于把索引签名里的动态名收紧成你项目里真实存在的实例从而获得精确的类型推导。源码级全景Multiremote 的完整调用链综合以上内容一条多路命令的完整生命周期可以归纳如下创建multiremote(params)或 Testrunner 根据 capabilities 对象实例化MultiRemote并行对每个命名 capability 调用remote()创建子会话并addInstance()index.ts包装用ProtocolDriver.attachToSession()把空会话modifier包装到所有实例之上得到统一的多路对象modifier为每个命令名绑定commandWrapper并提供getInstance、select等专属方法multiremote.ts分发调用命令时commandWrapper用Promise.all让每个实例各自执行同名命令返回结果数组multiremote.ts元素包装$/$$的结果通过MultiRemote.elementWrapper()变成多路元素对象元素上的后续命令同样广播到各实例元素命令的容错隐式等待、stale 元素重取、不可交互重试由 middlewares.ts 的elementErrorHandler配合multiremoteHandler完成事件与自定义命令MultiRemoteDriver把on/once/emit等事件 API 广播到每个子实例multiremote.tsaddCommand与overwriteCommand也会被转发到每个子实例并保留在 multiremote 原型上index.ts。此外仓库的 e2e 测试还展示了真实可跑的 headless 多路配置在 e2e/wdio/wdio-multiRemote.conf.ts 中browserA为 headless Chrome、browserB为 headless Firefox、browserC为 headless ChromiumLinux 下额外追加no-sandbox参数以规避容器内会话创建失败并通过WebdriverIO.MultiremoteConfig类型标注对应测试 multiRemoteTest.e2e.ts 覆盖了标题并行断言、元素链式查询、select()筛选、共享存储服务sharedStore、自定义命令保留等场景。这套配置可以作为你自己搭建多路测试的现成蓝本。小结与适用边界Multiremote 是 WebdriverIO 面向多用户协同集成测试提供的开箱即用方案它用一个统一对象驱动多个会话并行执行命令、以结果数组按声明顺序返回并提供getInstance/ 全局变量 / 字符串下标 /select()多种方式定位单个实例还能无缝混搭本地与云端后端、浏览器与移动设备。使用时请务必记住它的定位边界——它是会话内的协调工具而非测试用例的并行分发器如果你的目标是加速大批量 spec 的执行应该使用 Testrunner 原生的 capabilities 数组并行机制而不是 Multiremote。【免费下载链接】webdriverioNext-gen browser and mobile automation test framework for Node.js项目地址: https://gitcode.com/GitHub_Trending/we/webdriverio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考