自动化测试
🌐 Automated Testing
测试自动化是验证你的应用代码是否按预期工作的高效方法。虽然 Electron 并没有主动维护自己的测试解决方案,但本指南将介绍几种在 Electron 应用上运行端到端自动化测试的方法。
🌐 Test automation is an efficient way of validating that your application code works as intended. While Electron doesn't actively maintain its own testing solution, this guide will go over a couple ways you can run end-to-end automated tests on your Electron app.
使用 WebDriver 接口
🌐 Using the WebDriver interface
来自 ChromeDriver - Chrome 的 WebDriver:
🌐 From ChromeDriver - WebDriver for Chrome:
WebDriver 是一个开源工具,用于在多个浏览器上对 Web 应用进行自动化测试。它提供了导航到网页、用户输入、JavaScript 执行等功能。ChromeDriver 是一个独立的服务器,实现了 WebDriver 的 Chromium 协议。它由 Chromium 和 WebDriver 团队的成员开发。
你可以通过多种方式使用 WebDriver 设置测试。
🌐 There are a few ways that you can set up testing using WebDriver.
使用 WebdriverIO
🌐 With WebdriverIO
WebdriverIO(WDIO)是一个测试自动化框架,提供了用于 WebDriver 测试的 Node.js 包。它的生态系统还包括各种插件(例如报告器和服务),可以帮助你组建测试环境。
如果你已经有现有的 WebdriverIO 设置,建议更新你的依赖,并根据文档中描述的方式验证你现有的配置 文档链接。
🌐 If you already have an existing WebdriverIO setup, it is recommended to update your dependencies and validate your existing configuration with how it is outlined in the docs.
安装测试运行器
🌐 Install the test runner
如果你的项目中尚未使用 WebdriverIO,你可以通过运行项目根目录中的入门工具包来添加它:
🌐 If you don't use WebdriverIO in your project yet, you can add it by running the starter toolkit in your project root directory:
- npm
- Yarn
npm init wdio@latest ./
yarn create wdio@latest ./
这将启动一个配置向导,帮助你完成正确的设置,安装所有必要的软件包,并生成一个 wdio.conf.js 配置文件。请确保在最初几个问题之一中选择 “桌面测试 - 针对 Electron 应用”,该问题为 “你想进行哪种类型的测试?”。
🌐 This starts a configuration wizard that helps you put together the right setup, installs all necessary packages, and generates a wdio.conf.js configuration file. Make sure to select "Desktop Testing - of Electron Applications" on one of the first questions asking "What type of testing would you like to do?".
将 WDIO 连接到你的 Electron 应用
🌐 Connect WDIO to your Electron app
运行配置向导后,你的 wdio.conf.js 应包含大致如下内容:
🌐 After running the configuration wizard, your wdio.conf.js should include roughly the following content:
export const config = {
// ...
services: ['electron'],
capabilities: [{
browserName: 'electron',
'wdio:electronServiceOptions': {
// WebdriverIO can automatically find your bundled application
// if you use Electron Forge or electron-builder, otherwise you
// can define it here, e.g.:
// appBinaryPath: './path/to/bundled/application.exe',
appArgs: ['foo', 'bar=baz']
}
}]
// ...
}
编写你的测试
🌐 Write your tests
使用 WebdriverIO API 与屏幕上的元素进行交互。该框架提供了自定义“匹配器”,使断言应用状态变得简单,例如:
🌐 Use the WebdriverIO API to interact with elements on the screen. The framework provides custom "matchers" that make asserting the state of your application easy, e.g.:
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 以获取有关应用的静态信息:
🌐 Furthermore, WebdriverIO allows you to access Electron APIs to get static information about your application:
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
)
})
})
运行你的测试
🌐 Run your tests
运行测试:
🌐 To run your tests:
$ npx wdio run wdio.conf.js
WebdriverIO 可帮助你启动和关闭应用。
🌐 WebdriverIO helps launch and shut down the application for you.
更多文档
🌐 More documentation
在官方 WebdriverIO 文档中查找有关模拟 Electron API 和其他有用资源的更多文档。
🌐 Find more documentation on Mocking Electron APIs and other useful resources in the official WebdriverIO documentation.
含硒
🌐 With Selenium
Selenium 是一个网页自动化框架,它为多种语言提供了对 WebDriver API 的绑定。它们的 Node.js 绑定可以通过 NPM 上的 selenium-webdriver 包获得。
运行 ChromeDriver 服务器
🌐 Run a ChromeDriver server
为了在 Electron 中使用 Selenium,你需要下载 electron-chromedriver 二进制文件,并运行它:
🌐 In order to use Selenium with Electron, you need to download the electron-chromedriver
binary, and run it:
- npm
- Yarn
npm 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,稍后会用到。
🌐 Remember the port number 9515, which will be used later.
将 Selenium 连接到 ChromeDriver
🌐 Connect Selenium to ChromeDriver
接下来,将 Selenium 安装到你的项目中:
🌐 Next, install Selenium into your project:
- npm
- Yarn
npm install --save-dev selenium-webdriver
yarn add --dev selenium-webdriver
在 Electron 中使用 selenium-webdriver 与在普通网站中使用相同,只是你需要手动指定如何连接 ChromeDriver 以及在哪里可以找到你的 Electron 应用的二进制文件:
🌐 Usage of selenium-webdriver with Electron is the same as with
normal websites, except that you have to manually specify how to connect
ChromeDriver and where to find the binary of your Electron app:
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()
使用 Playwright
🌐 Using Playwright
Microsoft Playwright 是一个端到端测试框架,使用特定浏览器的远程调试协议构建,类似于 木偶师 的无头 Node.js API,但面向端到端测试。Playwright 通过 Electron 对 Chrome 开发者工具协议 (CDP) 的支持,提供了实验性的 Electron 支持。
安装依赖
🌐 Install dependencies
你可以通过你喜欢的 Node.js 包管理器安装 Playwright。它自带自己的 测试运行器,专为端到端测试而构建:
🌐 You can install Playwright through your preferred Node.js package manager. It comes with its own test runner, which is built for end-to-end testing:
- npm
- Yarn
npm install --save-dev @playwright/test
yarn add --dev @playwright/test
本教程是使用 @playwright/test@1.52.0 编写的。请查看 剧作家的作品发布 页面,了解可能影响以下代码的更改。
🌐 This tutorial was written with @playwright/test@1.52.0. Check out
Playwright's releases page to learn about
changes that might affect the code below.
编写你的测试
🌐 Write your tests
Playwright 通过 _electron.launch API 在开发模式下启动你的应用。要将此 API 指向你的 Electron 应用,你可以传入主进程入口的路径(这里是 main.js)。
🌐 Playwright launches your app in development mode through the _electron.launch API.
To point this API to your Electron app, you can pass the path to your main process
entry point (here, it is main.js).
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 类的一个实例。这个类非常强大,可以访问主进程模块,例如:
🌐 After that, you will have access to an instance of Playwright's ElectronApp class. This
is a powerful class that has access to main process modules for example:
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 Electron's 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 we're in development mode)
// close app
await electronApp.close()
})
它还可以从 Electron 的 BrowserWindow 实例创建单独的 页面 对象。例如,要获取第一个 BrowserWindow 并保存截图:
🌐 It can also create individual Page objects from Electron BrowserWindow instances. For example, to grab the first BrowserWindow and save a screenshot:
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.js 测试文件:
🌐 Putting all this together using the Playwright test-runner, let's create an example.spec.js
test file with a single test and assertion:
import { 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 Electron's 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 运行 Playwright 测试。你应该能在控制台中看到测试通过,并在你的文件系统中有一个 intro.png 截图。
🌐 Then, run Playwright Test using npx playwright test. You should see the test pass in your
console, and have an intro.png screenshot on your filesystem.
☁ $ npx playwright test
Running 1 test using 1 worker
✓ example.spec.js:4:1 › example test (1s)
Playwright 测试会自动运行任何匹配 .*(test|spec)\.(js|ts|mjs) 正则的文件。你可以在 Playwright 测试配置选项 中自定义这个匹配。它也可以开箱即用地支持 TypeScript。
🌐 Playwright Test will automatically run any files matching the .*(test|spec)\.(js|ts|mjs) regex.
You can customize this match in the Playwright Test configuration options.
It also works with TypeScript out of the box.
查看 Playwright 的文档以了解完整的 电子 和 电子应用 类 API。
🌐 Check out Playwright's documentation for the full Electron and ElectronApplication class APIs.
使用自定义测试驱动程序
🌐 Using a custom test driver
也可以使用 Node.js 内置的基于 STDIO 的 IPC 编写自定义驱动程序。自定义测试驱动程序要求你编写额外的应用代码,但开销较低,并且可以向测试套件暴露自定义方法。
🌐 It's also possible to write your own custom driver using Node.js' built-in IPC-over-STDIO. Custom test drivers require you to write additional app code, but have lower overhead and let you expose custom methods to your test suite.
要创建自定义驱动程序,我们将使用 Node.js 的 child_process API。测试套件将启动 Electron 进程,然后建立一个简单的消息传递协议:
🌐 To create a custom driver, we'll use Node.js' child_process API.
The test suite will spawn the Electron process, then establish a simple messaging protocol:
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' })
在 Electron 应用内部,你可以使用 Node.js process API 监听消息并发送回复:
🌐 From within the Electron app, you can listen for messages and send replies using the Node.js
process API:
// listen for messages from the test suite
process.on('message', (msg) => {
// ...
})
// send a message to the test suite
process.send({ my: 'message' })
我们现在可以使用 appProcess 对象从测试套件与 Electron 应用进行通信。
🌐 We can now communicate from the test suite to the Electron app using the appProcess object.
为了方便起见,你可能想将 appProcess 封装到一个提供更多高级功能的驱动对象中。下面是一个示例,说明你如何做到这一点。让我们从创建一个 TestDriver 类开始:
🌐 For convenience, you may want to wrap appProcess in a driver object that provides more
high-level functions. Here is an example of how you can do this. Let's start by creating
a TestDriver class:
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 }
在你的应用代码中,你可以编写一个简单的处理程序来接收 RPC 调用:
🌐 In your app code, you can then write a simple handler to receive RPC calls:
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)
}
然后,在你的测试套件中,你可以将你的 TestDriver 类与你选择的测试自动化框架一起使用。以下示例使用 ava,但其他流行的选择如 Jest 或 Mocha 也同样适用:
🌐 Then, in your test suite, you can use your TestDriver class with the test automation
framework of your choosing. The following example uses
ava, but other popular choices like Jest
or Mocha would work as well:
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()
})