自动化测试
测试自动化是一种验证您的应用是否如你预期那样正常工作的有效方法。 Electron已然不再维护自己的测试解决方案, 本指南将会介绍几种方法,让您可以在 Electron 应用上进行端到端自动测试。
使用 WebDriver 接口
引自 ChromeDriver - WebDriver for Chrome:
WebDriver 是一款开源的支持多浏览器的自动化测试工具。 它提供了操作网页、用户输入、JavaScript 执行等能力。 ChromeDriver 是一个实现了 WebDriver 与 Chromium 联接协议的独立服务。 它也是由开发了 Chromium 和 WebDriver 的团队开发的。
有几种方法可以使用 WebDriver 设置测试。
使用 WebdriverIO
WebdriverIO (WDIO) 是一个自动化测试框架,它提供了一个 Node.js 软件包用于测试 Web 驱动程序。 它的生态系统还包括各种插件(例如报告器和服务) ,可以帮助你把测试设置放在一起。
Install the test runner
首先,你需要在项目根目录中运行 WebdriverIO 启动工具包:
- npm
- Yarn
npx wdio . --yes
npx wdio . --yes
这将安装所有必要的软件包,并生成一个 wdio.conf.js
配置文件。
将 WDIO 连接到 Electron 应用程序
更新配置文件中的选项以指向 Electron 应用二进制文件:
exports.config = {
// ...
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
binary: '/path/to/your/electron/binary', // Electron 二进制文件的路径
args: [/* 命令行参数 */] // 可选, 比如 'app=' + /path/to/your/app/
}
}]
// ...
}
运行测试
执行命令:
$ npx wdio run wdio.conf.js
使用 Selenium
Selenium 是一个Web自动化框架,以多种语言公开与 WebDriver API 的绑定方式。 Node.js 环境下, 可以通过 NPM 安装 selenium-webdriver
包来使用此框架。
运行 ChromeDriver 服务
为了与 Electron 一起使用 Selenium ,你需要下载 electron-chromedriver
二进制文件并运行它:
- 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
这个端口号,我们后面会用到.
将 Selenium 连接到 ChromeDriver
接下来,把 Selenium 安装到你的项目中:
- npm
- Yarn
npm install --save-dev selenium-webdriver
yarn add --dev selenium-webdriver
在 Electron 下使用 selenium-webdriver
和其平时的用法并没有大的差异,只是你需要手动设置如何连接 ChromeDriver,以及 Electron 应用的查找路径:
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') // 注意: 使用 .forBrowser('electron') for selenium-webdriver <= 3.6.0
.build()
driver.get('http://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 是一个端到端的测试框架,使用浏览器特定的远程调试协议架构,类似于 Puppeteer 的无头 Node.js API,但面向端到端测试。 Playwright 通过 Electron 支持 [Chrome DevTools 协议][] (CDP) 获得实验性的 Electron 支持。
安装依赖项
您可以通过 Node.js 包管理器安装 Playwright。 Playwright团队推荐使用 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD
环境变量来避免在测试 Electron 软件时进行不必要的浏览器下载。
- npm
- Yarn
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --save-dev playwright
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn add --dev playwright
Playwright 同时也有自己的测试运行器( Playwright Test ),可用作端到端(E2E)测试。 你也可以在项目中作为开发以来来安装它:
- npm
- Yarn
npm install --save-dev @playwright/test
yarn add --dev @playwright/test
::: 依赖注意事项
本教程的编写基于 playwright@1.16.3
和 @playwright/test@1.16.3
查看Playwright 的版本更新 页面以知晓可能会影响到的代码更改。
:::
:::使用第三方测试运行程序的信息
如果您有兴趣使用其他测试运行其(例如 Jest 或 Mocha),请查看 Playwright 的 第三方测试运行器 指南。
:::
编写测试
Playwright通过 _electron.launch
API在开发模式下启动您的应用程序。 要将此 API 指向 Electron 应用,可以将路径传递到主进程入口点(此处为 main.js
)。
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('launch app', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
// close app
await electronApp.close()
})
在此之后,您将可以访问到 Playwright 的 ElectronApp
类的一个实例。 这是一个功能强大的类,可以访问主进程模块,例如:
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('get isPackaged', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
const isPackaged = await electronApp.evaluate(async ({ app }) => {
// 在 Electron 的主进程运行,这里的参数总是
// 主程序代码中 require('electron') 的返回结果。
return app.isPackaged
})
console.log(isPackaged) // false(因为我们处在开发环境)
// 关闭应用程序
await electronApp.close()
})
它还可以从 Electron BrowserWindow 实例创建单独的 Page 对象。 例如,获取第一个 BrowserWindow 并保存一个屏幕截图:
const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')
test('save screenshot', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// 关闭应用程序
await electronApp.close()
})
使用 PlayWright 测试运行器将所有这些组合到一起,让我们创建一个有单个测试和断言的 example.spec.js
测试文件:
const { _electron: electron } = require('playwright')
const { test, expect } = require('@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)
// 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
。
☁ $ 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 配置选项 中自定义这个正则表达式。
:::延伸阅读
查看 Playwright完整的 Electron 和 ElectronApplication class API。
:::
使用自定义测试驱动
当然,也可以使用node的内建IPC STDIO来编写自己的自定义驱动。 自定义测试驱动程序需要您写额外的应用代码,但是有较低的开销,让您 在您的测试套装上显示自定义方法。
我们将用 Node.js 的 child_process
API 来创建一个自定义驱动。 测试套件将生成 Electron 子进程,然后建立一个简单的消息传递协议。
const childProcess = require('child_process')
const electronPath = require('electron')
// 启动子进程
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 监听消息并发送回复:
// 监听测试套件发送过来的消息
process.on('message', (msg) => {
// ...
})
// 发送一条消息到测试套件
process.send({ my: 'message' })
现在,我们可以使用appProcess
对象从测试套件到Electron应用进行通讯。
为方便起见,您可能希望将 appProcess
包装在一个提供更高级功能的驱动程序对象中。 下面是一个示例。 让我们从创建一个 TestDriver
类开始:
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)
else rpcCall.resolve(message.resolve)
})
// 等待准备完毕
this.isReady = this.rpc('isReady').catch((err) => {
console.error('Application failed to start', err)
this.stop()
process.exit(1)
})
}
// 简单 RPC 回调
// 可以使用: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 调用:
const 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 也可以:
const test = require('ava')
const electronPath = require('electron')
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()
})
[Chrome DevTools 协议]: https://chromedevtools. github. io/devtools-protocol/