ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Electron 自动化测试实战:WebDriver、Playwright 与自定义测试驱动全指南

Electron 自动化测试实战:WebDriver、Playwright 与自定义测试驱动全指南 Electron 自动化测试实战WebDriver、Playwright 与自定义测试驱动全指南【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron自动化测试是验证 Electron 应用代码行为是否符合预期的高效手段。Electron 官方并未维护一套独立的测试框架而是依托 Chromium 生态的 WebDriver、Chrome DevTools ProtocolCDP等标准协议为开发者提供了 WebdriverIO、Selenium、Playwright 以及基于 Node.js IPC 的自定义测试驱动等多种端到端测试方案。读完本文你将掌握各方案的环境搭建、配置要点与可运行的测试代码能够结合当前仓库的源码证据理解它们的底层原理并为自己的 Electron 应用选型落地。Electron 测试的总体思路在 Electron 中运行端到端自动化测试本质上是让测试框架去驱动一个真实的 Electron 应用进程像操作浏览器页面一样操作其中的 DOM同时还要能触达 Electron 特有的主进程能力如BrowserWindow、dialog等。由于 Electron 应用是「Chromium Node.js」的混合体社区主流的做法有两条技术路线WebDriver 协议路线由 Chromium 项目维护的 ChromeDriver 为 Electron 提供 WebDriver 服务端实现上层再套用 WebdriverIO 或 Selenium 框架CDPChrome DevTools Protocol路线Electron 对 Chrome DevTools Protocol 的支持让 Playwright 这类基于 CDP 的框架可以直接对 Electron 应用做端到端测试自研驱动路线利用 Node.js 内置的 IPC-over-STDIO 能力在测试套件与被测应用之间建立自定义消息协议开销低、可控性强。官方说明Electron 并不主动维护自有的测试解决方案Electron doesnt actively maintain its own testing solution因此本条指南docs/tutorial/automated-testing.md介绍的是几种在 Electron 应用上运行端到端测试的通用方式。路线一使用 WebDriver 接口WebDriver 是跨浏览器自动化测试的开源工具标准提供网页导航、用户输入模拟、JavaScript 执行等能力。ChromeDriver 是 Chromium 项目与 WebDriver 团队共同开发的独立服务器实现了 Chromium 上的 WebDriver wire 协议。Electron 同样能通过它被驱动因为 Chromium 渲染层在协议层面是一致的。采用 WebDriver 方案搭建测试有两种主流选择下面分别展开。子方案 A使用 WebdriverIOWDIOWebdriverIO 是一个基于 Node.js 的测试自动化框架生态中提供大量 reporter、service 等插件帮助组装测试环境。如果你已经有一套现成的 WebdriverIO 配置建议按 WebdriverIO 官方文档中 Electron 桌面测试的配置方式升级依赖并校验现有配置。安装测试运行器在项目根目录运行 starter 工具包即可完成初始化npm init wdiolatest ./该命令会启动一个配置向导帮你拼装出合适的测试配置、安装全部依赖并生成wdio.conf.js配置文件。在向导第一个问题What type of testing would you like to do?中务必选择Desktop Testing - of Electron Applications选项。将 WDIO 连接到 Electron 应用向导结束后wdio.conf.js应大致包含以下内容export const config { // ... services: [electron], capabilities: [{ browserName: electron, wdio:electronServiceOptions: { // 如果你使用 Electron Forge 或 electron-builderWebdriverIO // 可以自动找到打包后的应用否则需要在此显式指定例如 // appBinaryPath: ./path/to/bundled/application.exe, appArgs: [foo, barbaz] } }] // ... }要点说明services: [electron]启用 WDIO 的 Electron 服务插件由它负责拉起和关闭你的应用进程appBinaryPath用于指定打包后的应用二进制路径使用 Electron Forge / electron-builder 时通常无需手填appArgs会作为命令行参数透传给应用进程适合用来注入测试专用参数如上面示例中的foo、barbaz。应用主进程可通过process.argv读取这一机制在你的应用里同样适用于区分测试模式与生产模式。编写测试测试中可直接使用 WebdriverIO API 与屏幕上的元素交互。框架提供了丰富的自定义 matchers断言匹配器让应用状态的断言非常简单。例如模拟键盘输入并断言页面统计值import { browser, $, expect } from wdio/globals describe(keyboard input, () { it(should detect keyboard input, async () { await browser.keys([y, o]) await expect($(keypress-count)).toHaveText(YO) }) })更进一步WebdriverIO 允许你通过browser.electron.execute进入Electron 主进程调用 Electron API 获取应用静态信息或触发主进程能力。回调函数运行在主进程中参数依次为require(electron)的返回对象以及你传入的额外参数import { browser } from wdio/globals describe(trigger message modal, async () { it(message modal can be triggered from a test, async () { await browser.electron.execute( (electron, param1, param2, param3) { const appWindow electron.BrowserWindow.getFocusedWindow() electron.dialog.showMessageBox(appWindow, { message: Hello World!, detail: ${param1} ${param2} ${param3} ${param1 param2 param3} }) }, 1, 2, 3 ) }) })这段测试直接复用了仓库中 BrowserWindow 相关文档 与 dialog API 描述的能力先通过BrowserWindow.getFocusedWindow()拿到当前聚焦窗口再以它为父窗口弹出dialog.showMessageBox。也就是说测试层可以用与渲染层完全等价的方式去验证主进程 UI 行为。运行测试$ npx wdio run wdio.conf.jsWebdriverIO 会自动帮你启动和关闭被测应用无需手工清理进程。关于 Mock Electron API 以及更多有用资源的说明可进一步查阅 WebdriverIO 官方桌面测试Electron文档。子方案 B使用 SeleniumSelenium 是面向多种语言暴露 WebDriver API 绑定的网页自动化框架其 Node.js 绑定发布为 NPM 上的selenium-webdriver包。运行 ChromeDriver 服务器要配合 Electron 使用 Selenium需要先拿到 Electron 对应的electron-chromedriver二进制并启动它npm install --save-dev electron-chromedriver ./node_modules/.bin/chromedriver Starting ChromeDriver (v2.10.291558) on port 9515 Only local connections are allowed.记下终端输出的端口号9515后续步骤要用。electron-chromedriver并非临时概念它是 Electron 发布流程中随版本一起产出的正式制品。在本仓库中可以看到相关证据script/release/release-assets.json 定义了各平台发布产物清单其中就包含chromedriver-${version}-darwin-x64.zip、chromedriver-${version}-linux-arm64.zip、chromedriver-${version}-win32-x64.zip等覆盖 macOS、Linux、Windows 的 x64 / arm64 / mas 变体script/verify-chromedriver.py 是 CI 中用于验证产物可用的脚本它以子进程方式拉起构建目录下的chromedriverdarwin/win32/linux 平台名不同Windows 上为chromedriver.exe并断言其首行输出匹配正则^Starting ChromeDriver [0-9].[0-9].[0-9].[0-9] .* on port [0-9]$即能正常初始化监听端口。因此在版本匹配上建议安装与你的 Electron 版本一致的electron-chromedriver其版本号与 Electron 版本一一对应。将 Selenium 连接到 ChromeDriver接下来把 Selenium 装进项目npm install --save-dev selenium-webdriverselenium-webdriver配合 Electron 的用法与普通网站基本一致唯一区别是你必须手动指定 ChromeDriver 的地址以及Electron 应用二进制的位置const webdriver require(selenium-webdriver) const driver new webdriver.Builder() // The 9515 is the port opened by ChromeDriver. .usingServer(http://localhost:9515) .withCapabilities({ goog:chromeOptions: { // Here is the path to your Electron binary. binary: /Path-to-Your-App.app/Contents/MacOS/Electron } }) .forBrowser(chrome) // note: use .forBrowser(electron) for selenium-webdriver 3.6.0 .build() driver.get(https://www.google.com) driver.findElement(webdriver.By.name(q)).sendKeys(webdriver) driver.findElement(webdriver.By.name(btnG)).click() driver.wait(() { return driver.getTitle().then((title) { return title webdriver - Google Search }) }, 1000) driver.quit()两个关键点需要注意goog:chromeOptions.binary指向 Electron 可执行文件。示例中是 macOS 上打包应用解包后的二进制路径在开发模式下也可以指向node_modules/electron/dist/electron可执行文件并配合入口参数使用forBrowser(electron)仅适用于selenium-webdriver 3.6.0的旧版本新版本请统一使用forBrowser(chrome)。成功连接后Selenium 的所有 find / click / wait / quit 等 WebDriver 操作都能照常在 Electron 窗口中工作因为协议语义完全一致。路线二使用 PlaywrightMicrosoft Playwright 是基于浏览器专属远程调试协议构建的端到端测试框架类似 Puppeteer headless API但更偏向端到端测试场景。Playwright 对 Electron 提供实验性支持其支撑正是Electron 对 Chrome DevTools ProtocolCDP的支持。从源码层面可以印证这一机制在主进程初始化逻辑 shell/browser/electron_browser_main_parts.cc 中Electron 检测到--remote-debugging-pipe开关时会调用content::DevToolsAgentHost::StartRemoteDebuggingPipeHandler(...)而检测到--remote-debugging-port开关时则调用DevToolsManagerDelegate::StartHttpHandler()从而在指定端口暴露 CDP HTTP 调试端点。对应的命令行开关在 docs/api/command-line-switches.md 中有明确记录--remote-debugging-portport会在指定端口启用基于 HTTP 的远程调试。此外仓库自身的测试套件如 spec/chromium-spec.ts中就大量使用了ChildProcess.spawn(electronPath, [--remote-debugging-pipe], ...)与--remote-debugging-port0这样的方式拉起被测实例从实践侧再次验证了这两条 CDP 通道的可用性。安装依赖Playwright 自带专为端到端测试设计的测试运行器通过你惯用的 Node.js 包管理器安装即可npm install --save-dev playwright/test依赖提示本教程编写时对应playwright/test1.52.0。Playwright 的 API 演进较快升级版本前请关注其 release notes确认下方代码是否受影响。编写测试Playwright 通过_electron.launchAPI以开发模式启动你的应用。要让该 API 指向你的应用只需传入主进程入口文件的路径这里的main.js即 tutorial-2-first-app.md 中创建的应用入口import { test, _electron as electron } from playwright/test test(launch app, async () { const electronApp await electron.launch({ args: [.] }) // close app await electronApp.close() })启动后你将拿到一个 Playwright 的ElectronApp实例。该类非常强大例如可以访问主进程模块在应用的主进程中执行任意代码import { test, _electron as electron } from playwright/test test(get isPackaged, async () { const electronApp await electron.launch({ args: [.] }) const isPackaged await electronApp.evaluate(async ({ app }) { // This runs in Electrons main process, parameter here is always // the result of the require(electron) in the main app script. return app.isPackaged }) console.log(isPackaged) // false (because were in development mode) // close app await electronApp.close() })electronApp.evaluate的回调运行在 Electron 主进程中其参数永远是主进程脚本require(electron)的结果。这里开发模式下app.isPackaged返回false这一事实对应 docs/tutorial/application-distribution.md 与 app 模块文档 中关于打包状态的说明。ElectronApp还能从 ElectronBrowserWindow实例创建独立的Page对象。例如抓取第一个窗口并保存截图import { test, _electron as electron } from playwright/test test(save screenshot, async () { const electronApp await electron.launch({ args: [.] }) const window await electronApp.firstWindow() await window.screenshot({ path: intro.png }) // close app await electronApp.close() })把上面的能力综合起来用 Playwright 测试运行器创建一个带断言的单测文件example.spec.jsimport { test, expect, _electron as electron } from playwright/test test(example test, async () { const electronApp await electron.launch({ args: [.] }) const isPackaged await electronApp.evaluate(async ({ app }) { // This runs in Electrons main process, parameter here is always // the result of the require(electron) in the main app script. return app.isPackaged }) expect(isPackaged).toBe(false) // Wait for the first BrowserWindow to open // and return its Page object const window await electronApp.firstWindow() await window.screenshot({ path: intro.png }) // close app await electronApp.close() })随后运行npx playwright test你应当能在终端看到测试通过并在文件系统上看到生成的intro.png截图☁ $ npx playwright test Running 1 test using 1 worker ✓ example.spec.js:4:1 › example test (1s)两条实用的工程信息Playwright Test 会自动匹配所有符合.*(test|spec)\.(js|ts|mjs)正则的文件你可以在 Playwright Test 配置中自定义testMatch它开箱即用地支持 TypeScript无需额外转译配置。路线三自定义测试驱动如果你追求更低的开销并希望向测试套件暴露自定义方法可以绕过 WebDriver / CDP利用 Node.js 内置的IPC-over-STDIO机制自研一个轻量测试驱动。代价是需要为应用额外编写少量配套代码。这一方案特别适合仓库内部自带大量集成测试的场景——本仓库的 spec 目录见下正是这类思路的规模化实践。进程拉起与消息协议测试套件通过 Node.js 的child_processAPI 拉起 Electron 进程并利用stdio数组中最后一个ipc通道与子进程通信const electronPath require(electron) const childProcess require(node:child_process) // spawn the process const env { /* ... */ } const stdio [inherit, inherit, inherit, ipc] const appProcess childProcess.spawn(electronPath, [./app], { stdio, env }) // listen for IPC messages from the app appProcess.on(message, (msg) { // ... }) // send an IPC message to the app appProcess.send({ my: message })应用侧主进程脚本main.js通过 Node.js 的processAPI 监听消息并回复// listen for messages from the test suite process.on(message, (msg) { // ... }) // send a message to the test suite process.send({ my: message })至此测试套件与 Electron 应用之间已经能借助appProcess对象双向通信。封装一个高层的 TestDriver裸的appProcess用起来不便通常的做法是把它包装成一个提供高层函数的驱动对象。先创建TestDriver类内部实现一个简单的 RPC 机制class TestDriver { constructor ({ path, args, env }) { this.rpcCalls [] // start child process env.APP_TEST_DRIVER 1 // let the app know it should listen for messages this.process childProcess.spawn(path, args, { stdio: [inherit, inherit, inherit, ipc], env }) // handle rpc responses this.process.on(message, (message) { // pop the handler const rpcCall this.rpcCalls[message.msgId] if (!rpcCall) return this.rpcCalls[message.msgId] null // reject/resolve if (message.reject) rpcCall.reject(message.reject) else rpcCall.resolve(message.resolve) }) // wait for ready this.isReady this.rpc(isReady).catch((err) { console.error(Application failed to start, err) this.stop() process.exit(1) }) } // simple RPC call // to use: driver.rpc(method, 1, 2, 3).then(...) async rpc (cmd, ...args) { // send rpc request const msgId this.rpcCalls.length this.process.send({ msgId, cmd, args }) return new Promise((resolve, reject) this.rpcCalls.push({ resolve, reject })) } stop () { this.process.kill() } } module.exports { TestDriver }设计要点构造时通过env.APP_TEST_DRIVER 1环境变量通知应用进入被测试驱动模式rpc(cmd, ...args)为每个请求分配自增msgId把resolve/reject按msgId暂存进rpcCalls数组消息回来时按msgId取出对应处理器构造函数末尾立刻发起isReadyRPC把返回的 Promise 保存在this.isReady上方便测试框架before阶段等待应用就绪若应用启动失败则打印错误、停止进程并以退出码 1 结束。应用侧接收 RPC 调用在应用的main.js中编写一个简单的 RPC 分发处理器const METHODS { isReady () { // do any setup needed return true } // define your RPC-able methods here } const onMessage async ({ msgId, cmd, args }) { let method METHODS[cmd] if (!method) method () new Error(Invalid method: cmd) try { const resolve await method(...args) process.send({ msgId, resolve }) } catch (err) { const reject { message: err.message, stack: err.stack, name: err.name } process.send({ msgId, reject }) } } if (process.env.APP_TEST_DRIVER) { process.on(message, onMessage) }METHODS表即可 RPC 的方法注册表新测试能力只需在这里追加方法即可。process.env.APP_TEST_DRIVER守卫保证了只有带该环境变量启动时即测试场景下才会挂载消息监听日常运行不受影响。异常时会把message、stack、name序列化后随reject回传保证测试侧的报错信息完整可读。接入任意测试框架最后在测试套件里把TestDriver与任意测试自动化框架组合即可。下面的例子使用ava换成 Jest 或 Mocha 等主流框架也完全可行const electronPath require(electron) const test require(ava) const { TestDriver } require(./testDriver) const app new TestDriver({ path: electronPath, args: [./app], env: { NODE_ENV: test } }) test.before(async t { await app.isReady }) test.after.always(cleanup, async t { await app.stop() })仓库中的规模化实践自研驱动并非纸上谈兵。查看本仓库的 spec 目录其中约上百个api-*-spec.ts测试文件构成了 Electron 自身的庞大测试套件api-app-spec.ts、api-ipc-main-spec.ts、api-menu-spec.ts 等。这些用例的启动方式如 spec/chromium-spec.ts 中直接ChildProcess.spawn(electronPath, [--remote-debugging-pipe], ...)拉起被测进程与上文展示的自研驱动思路一脉相承可作为深入阅读如何给 Electron 应用写大规模端到端测试的真实范本。四种方案的横向对比与选型建议方案测试入口是否需要额外服务主进程可访问性额外应用代码推荐场景WebdriverIOnpx wdio run wdio.conf.js由 electron service 自动管理browser.electron.execute可进主进程少打包后可自动发现应用已有 WDIO 生态、需要丰富 reporter/plugin 的团队Selenium自写 Node 脚本 / 测试框架需手动启动electron-chromedriver并记住端口不可直接进主进程面向页面层少已有 Selenium 语言绑定 / 多语言团队Playwrightnpx playwright test自动经 CDP 管道启动electronApp.evaluate/firstWindow()少直接开发模式启动偏好开箱即用 runner、需要截图/页面对象模型自定义驱动接 ava / Jest / Mocha无IPC-over-STDIO通过 RPC 完全自定义需写TestDriver与主进程 handler需要自定义方法、追求低开销、大规模内部测试几个实用的通用建议开发模式下 Playwright 的app.isPackaged false、Selenium 指向打包二进制等行为差异决定了测试写的是开发态还是发布态路径如需覆盖打包后行为请基于 application-distribution.md 所述流程产出应用包后再测无论走哪条路线建议在测试脚本中显式注入与常规运行不同的参数或环境变量如appArgs、NODE_ENV: test、APP_TEST_DRIVER1以避免测试污染真实数据Electron 各版本对 CDP 的支持细节与 Playwright 的实验性适配会随版本演进锁定版本并随 Electron 升级同步回归测试是最稳妥的做法。小结自动化测试能高效验证 Electron 应用代码行为符合预期WebdriverIO 与 Selenium 走 WebDriver ChromeDriver 路线聚焦页面交互并可触及主进程Playwright 借道 Electron 对 CDP 的内建支持源码见 shell/browser/electron_browser_main_parts.cc提供流畅的 runner 体验自定义测试驱动则以 IPC-over-STDIO 换来最低开销与最大定制空间。本文四种方案均配有可直接运行的示例代码与对应的仓库源码佐证你可以据此为自己的 Electron 项目搭建一套可持续演进的端到端测试体系。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表