进阶 electronjs.org 2026-10-07 23:51:43 · 6 阅读
第7章 Electron 应用自动化测试指南
自动测试
自动化测试是一种高效验证应用代码是否符合预期行为的手段。
虽然 Electron 官方并未维护自己的测试方案,但本指南将介绍几种在 Electron 应用中运行端到端自动化测试的方法。
使用 WebDriver 接口
引自 ChromeDriver - WebDriver for Chrome:
WebDriver 是一款开源工具,用于在多种浏览器上对 Web 应用进行自动化测试。它支持导航到网页、用户输入、JavaScript 执行等功能。ChromeDriver 是一个独立服务器,实现了 Chromium 的 WebDriver 线协议。该工具由 Chromium 和 WebDriver 团队的成员开发。
使用 WebDriver 进行测试有几种配置方式。
借助 WebdriverIO
WebdriverIO(WDIO)是一个测试自动化框架,提供了一个用于配合 WebDriver 进行测试的 Node.js 包。其生态系统还包括多种插件(例如报告器和服务),可以帮助你搭建测试环境。
如果你已经有一套现有的 WebdriverIO 配置,建议更新依赖项,并参照文档核对现有配置。
安装测试运行器
如果项目中尚未使用 WebdriverIO,可以在项目根目录运行 starter toolkit 来添加它:
npmYarnnpm init wdio@latest ./
yarn create wdio@latest ./
这将启动一个配置向导,帮助你搭建合适的配置,安装所有必要的包,并生成 wdio.conf.js 配置文件。在回答“你想进行哪种类型的测试?”这一早期问题时,务必选择“桌面测试 - Electron 应用程序测试”。 将 WDIO 连接到你的 Electron 应用 运行完配置向导后,wdio.conf.js 应大致包含以下内容: wdio.conf.jsexport const config = {
// ...
services: ['electron'],
capabilities: [
{
browserName: 'electron',
'wdio:electronServiceOptions': {
// WebdriverIO 通常能自动找到你打包好的应用
// 如果你使用 Electron Forge 或 electron-builder,否则你
// 可以在此定义,例如:
// appBinaryPath: './path/to/bundled/application.exe',
appArgs: ['foo', 'bar=baz']
}
}
]
// ...
} # 编写测试 使用 WebdriverIO API 与屏幕上的元素交互。框架提供了自定义"匹配器"(matcher),让断言应用状态变得很简单,例如: 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 还允许你访问 Electron API,以获取应用的静态信息: 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
)
})
)
# 运行测试 运行测试的命令如下: $ npx wdio run wdio.conf.js
WebdriverIO 会帮你自动启动和关闭应用。 # 更多文档 关于 Mock Electron API 以及其他有用的资源,请参阅 WebdriverIO 官方文档。 # 使用 Selenium Selenium 是一个 Web 自动化框架,为 WebDriver API 提供了多种语言的绑定。其 Node.js 绑定可以通过 NPM 上的 selenium-webdriver 包获取。 # 启动 ChromeDriver 服务器 要在 Electron 中使用 Selenium,你需要下载 electron-chromedriver 二进制文件并运行它: npmYarnnpm install --save-dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.
yarn add --dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.
记住端口号 9515,后面会用到。 # 将 Selenium 连接到 ChromeDriver 接下来,在项目中安装 Selenium: npmYarnnpm install --save-dev selenium-webdriver
yarn add --dev selenium-webdriver
selenium-webdriver 在 Electron 中的应用与在普通网站上相同,区别在于你需要手动指定如何连接 ChromeDriver,以及在哪里找到 Electron 应用的二进制文件: ``` test.js const webdriver = require('selenium-webdriver') const driver = new webdriver.Builder() // "9515" 是 ChromeDriver 打开的端口。 .usingServer('http://localhost:9515') .withCapabilities({ 'goog:chromeOptions': { // 这里是你的 Electron 二进制文件的路径。 binary: '/Path-to-Your-App.app/Contents/MacOS/Electron' } }) .forBrowser('chrome') // 注意:在 selenium-webdriver <= 3.6.0 中请使用 .forBrowser('electron') .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() ``` 使用 Playwright Microsoft Playwright 是一个基于浏览器特定的远程调试协议构建的端到端测试框架,类似面向无头 Node.js 应用的 Puppeteer API,但更侧重于端到端测试。Playwright 通过 Electron 对 Chrome DevTools Protocol (CDP) 的支持,提供了实验性的 Electron 支持。 安装依赖 你可以通过首选的 Node.js 包管理器安装 Playwright。它自带专为端到端测试设计的测试运行器: ``` npm Yarn npm install --save-dev @playwright/test yarn add --dev @playwright/test ``` 依赖说明 本教程基于 `@playwright/test@1.52.0` 编写。请查阅 Playwright 的发布页面,了解可能影响以下代码的变更。 编写测试 Playwright 通过 `_electron.launch` API 以开发模式启动你的应用。若要让此 API 指向你的 Electron 应用,可以传递主进程入口点的路径(在此示例中为 `main.js`): ``` import { test, _electron as electron } from '@playwright/test' test('launch app', async () => { const electronApp = await electron.launch({ args: ['.'] }) // 关闭应用 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 }) => {
// 此段代码运行在 Electron 主进程中,参数始终
// 为主应用脚本中 require('electron') 的返回值。
return app.isPackaged
})
console.log(isPackaged) // false(因为处于开发模式)
// 关闭应用
await electronApp.close()
})
它还可以基于 Electron BrowserWindow 实例创建独立的 Page 对象。 例如,获取第一个 BrowserWindow 并保存截图: 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' })
// 关闭应用
await electronApp.close()
})
利用 Playwright 测试运行器将上述功能整合起来,让我们创建一个名为 example.spec.js 的测试文件,其中包含一个测试用例和断言: 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 }) => {
// 此段代码运行在 Electron 主进程中,参数始终
// 为主应用脚本中 require('electron') 的返回值。
return app.isPackaged
})
expect(isPackaged).toBe(false)
// 等待第一个 BrowserWindow 打开
// 并返回其 Page 对象
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// 关闭应用
await electronApp.close()
})
然后,使用 npx playwright test 运行 Playwright 测试。你应该能在控制台中看到测试通过,并在文件系统中生成 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 的配置选项中自定义这个匹配规则,而且它原生支持 TypeScript。 延伸阅读:完整 API 可查阅 Playwright 文档中的 Electron 和 ElectronApplication 类。 ## 使用自定义测试驱动 你也可以基于 Node.js 内置的 IPC-over-STDIO 机制编写自己的驱动。自定义测试驱动需要额外编写应用代码,但开销更低,还能向测试套件暴露自定义方法。 下面用 Node.js 的 `child_process` API 创建自定义驱动:测试套件先启动 Electron 进程,再建立一套简单的消息协议。 `testDriver.js` ```js const electronPath = require('electron') const childProcess = require('node:child_process') // 启动进程 const env = { /* ... */ } const stdio = ['inherit', 'inherit', 'inherit', 'ipc'] const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env }) // 监听来自应用的 IPC 消息 appProcess.on('message', (msg) => { // ... }) // 向应用发送 IPC 消息 appProcess.send({ my: 'message' }) ``` 在 Electron 应用内部,则用 Node.js 的 `process` API 监听消息并回复: `main.js` ```js // 监听来自测试套件的消息 process.on('message', (msg) => { // ... }) // 向测试套件发送消息 process.send({ my: 'message' }) ``` 现在测试套件就可以通过 `appProcess` 对象与 Electron 应用通信了。为方便起见,建议把 `appProcess` 封装成一个提供更高层接口的驱动对象。下面是一个示例——先创建一个 `TestDriver` 类: `testDriver.js` ```js class TestDriver { constructor({ path, args, env }) { this.rpcCalls = [] // 启动子进程 env.APP_TEST_DRIVER = 1 // 告知应用开始监听消息 this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env }) // 处理 RPC 响应 this.process.on('message', (message) => { // 取出对应的处理器 const rpcCall = this.rpcCalls[message.msgId] if (!rpcCall) return this.rpcCalls[message.msgId] = null // reject/resolve if (message.reject) rpcCall.reject(message.reject) ``` 否则,解决 RPC 调用:rpcCall.resolve(message.resolve)
})
// 等待应用就绪
this.isReady = this.rpc('isReady').catch((err) => {
console.error('应用启动失败', err)
this.stop()
process.exit(1)
})
}
// 简单的 RPC 调用
// 使用方式:driver.rpc('method', 1, 2, 3).then(...)
async rpc(cmd, ...args) {
// 发送 RPC 请求
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 }
在你的应用代码中,可以编写一个简单处理器来接收 RPC 调用: main.jsconst METHODS = {
isReady() {
// 执行必要的初始化设置
return true
}
// 在此处定义可被 RPC 调用的方法
}
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)
}
随后,在测试套件中,你可以将 TestDriver 类与你选择的测试自动化框架结合使用。以下示例使用了 ava,但 Jest 或 Mocha 等流行的框架同样适用: test.jsconst 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()
})
yarn create wdio@latest ./
这将启动一个配置向导,帮助你搭建合适的配置,安装所有必要的包,并生成 wdio.conf.js 配置文件。在回答“你想进行哪种类型的测试?”这一早期问题时,务必选择“桌面测试 - Electron 应用程序测试”。 将 WDIO 连接到你的 Electron 应用 运行完配置向导后,wdio.conf.js 应大致包含以下内容: wdio.conf.jsexport const config = {
// ...
services: ['electron'],
capabilities: [
{
browserName: 'electron',
'wdio:electronServiceOptions': {
// WebdriverIO 通常能自动找到你打包好的应用
// 如果你使用 Electron Forge 或 electron-builder,否则你
// 可以在此定义,例如:
// appBinaryPath: './path/to/bundled/application.exe',
appArgs: ['foo', 'bar=baz']
}
}
]
// ...
} # 编写测试 使用 WebdriverIO API 与屏幕上的元素交互。框架提供了自定义"匹配器"(matcher),让断言应用状态变得很简单,例如: 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 还允许你访问 Electron API,以获取应用的静态信息: 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
)
})
)
# 运行测试 运行测试的命令如下: $ npx wdio run wdio.conf.js
WebdriverIO 会帮你自动启动和关闭应用。 # 更多文档 关于 Mock Electron API 以及其他有用的资源,请参阅 WebdriverIO 官方文档。 # 使用 Selenium Selenium 是一个 Web 自动化框架,为 WebDriver API 提供了多种语言的绑定。其 Node.js 绑定可以通过 NPM 上的 selenium-webdriver 包获取。 # 启动 ChromeDriver 服务器 要在 Electron 中使用 Selenium,你需要下载 electron-chromedriver 二进制文件并运行它: npmYarnnpm install --save-dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.
yarn add --dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.
记住端口号 9515,后面会用到。 # 将 Selenium 连接到 ChromeDriver 接下来,在项目中安装 Selenium: npmYarnnpm install --save-dev selenium-webdriver
yarn add --dev selenium-webdriver
selenium-webdriver 在 Electron 中的应用与在普通网站上相同,区别在于你需要手动指定如何连接 ChromeDriver,以及在哪里找到 Electron 应用的二进制文件: ``` test.js const webdriver = require('selenium-webdriver') const driver = new webdriver.Builder() // "9515" 是 ChromeDriver 打开的端口。 .usingServer('http://localhost:9515') .withCapabilities({ 'goog:chromeOptions': { // 这里是你的 Electron 二进制文件的路径。 binary: '/Path-to-Your-App.app/Contents/MacOS/Electron' } }) .forBrowser('chrome') // 注意:在 selenium-webdriver <= 3.6.0 中请使用 .forBrowser('electron') .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() ``` 使用 Playwright Microsoft Playwright 是一个基于浏览器特定的远程调试协议构建的端到端测试框架,类似面向无头 Node.js 应用的 Puppeteer API,但更侧重于端到端测试。Playwright 通过 Electron 对 Chrome DevTools Protocol (CDP) 的支持,提供了实验性的 Electron 支持。 安装依赖 你可以通过首选的 Node.js 包管理器安装 Playwright。它自带专为端到端测试设计的测试运行器: ``` npm Yarn npm install --save-dev @playwright/test yarn add --dev @playwright/test ``` 依赖说明 本教程基于 `@playwright/test@1.52.0` 编写。请查阅 Playwright 的发布页面,了解可能影响以下代码的变更。 编写测试 Playwright 通过 `_electron.launch` API 以开发模式启动你的应用。若要让此 API 指向你的 Electron 应用,可以传递主进程入口点的路径(在此示例中为 `main.js`): ``` import { test, _electron as electron } from '@playwright/test' test('launch app', async () => { const electronApp = await electron.launch({ args: ['.'] }) // 关闭应用 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 }) => {
// 此段代码运行在 Electron 主进程中,参数始终
// 为主应用脚本中 require('electron') 的返回值。
return app.isPackaged
})
console.log(isPackaged) // false(因为处于开发模式)
// 关闭应用
await electronApp.close()
})
它还可以基于 Electron BrowserWindow 实例创建独立的 Page 对象。 例如,获取第一个 BrowserWindow 并保存截图: 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' })
// 关闭应用
await electronApp.close()
})
利用 Playwright 测试运行器将上述功能整合起来,让我们创建一个名为 example.spec.js 的测试文件,其中包含一个测试用例和断言: 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 }) => {
// 此段代码运行在 Electron 主进程中,参数始终
// 为主应用脚本中 require('electron') 的返回值。
return app.isPackaged
})
expect(isPackaged).toBe(false)
// 等待第一个 BrowserWindow 打开
// 并返回其 Page 对象
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// 关闭应用
await electronApp.close()
})
然后,使用 npx playwright test 运行 Playwright 测试。你应该能在控制台中看到测试通过,并在文件系统中生成 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 的配置选项中自定义这个匹配规则,而且它原生支持 TypeScript。 延伸阅读:完整 API 可查阅 Playwright 文档中的 Electron 和 ElectronApplication 类。 ## 使用自定义测试驱动 你也可以基于 Node.js 内置的 IPC-over-STDIO 机制编写自己的驱动。自定义测试驱动需要额外编写应用代码,但开销更低,还能向测试套件暴露自定义方法。 下面用 Node.js 的 `child_process` API 创建自定义驱动:测试套件先启动 Electron 进程,再建立一套简单的消息协议。 `testDriver.js` ```js const electronPath = require('electron') const childProcess = require('node:child_process') // 启动进程 const env = { /* ... */ } const stdio = ['inherit', 'inherit', 'inherit', 'ipc'] const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env }) // 监听来自应用的 IPC 消息 appProcess.on('message', (msg) => { // ... }) // 向应用发送 IPC 消息 appProcess.send({ my: 'message' }) ``` 在 Electron 应用内部,则用 Node.js 的 `process` API 监听消息并回复: `main.js` ```js // 监听来自测试套件的消息 process.on('message', (msg) => { // ... }) // 向测试套件发送消息 process.send({ my: 'message' }) ``` 现在测试套件就可以通过 `appProcess` 对象与 Electron 应用通信了。为方便起见,建议把 `appProcess` 封装成一个提供更高层接口的驱动对象。下面是一个示例——先创建一个 `TestDriver` 类: `testDriver.js` ```js class TestDriver { constructor({ path, args, env }) { this.rpcCalls = [] // 启动子进程 env.APP_TEST_DRIVER = 1 // 告知应用开始监听消息 this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env }) // 处理 RPC 响应 this.process.on('message', (message) => { // 取出对应的处理器 const rpcCall = this.rpcCalls[message.msgId] if (!rpcCall) return this.rpcCalls[message.msgId] = null // reject/resolve if (message.reject) rpcCall.reject(message.reject) ``` 否则,解决 RPC 调用:rpcCall.resolve(message.resolve)
})
// 等待应用就绪
this.isReady = this.rpc('isReady').catch((err) => {
console.error('应用启动失败', err)
this.stop()
process.exit(1)
})
}
// 简单的 RPC 调用
// 使用方式:driver.rpc('method', 1, 2, 3).then(...)
async rpc(cmd, ...args) {
// 发送 RPC 请求
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 }
在你的应用代码中,可以编写一个简单处理器来接收 RPC 调用: main.jsconst METHODS = {
isReady() {
// 执行必要的初始化设置
return true
}
// 在此处定义可被 RPC 调用的方法
}
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)
}
随后,在测试套件中,你可以将 TestDriver 类与你选择的测试自动化框架结合使用。以下示例使用了 ava,但 Jest 或 Mocha 等流行的框架同样适用: test.jsconst 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()
})