使用自定义驱动程序进行自动化测试
为Electron应用编写自动测试, 你需要一种 "驱动" 应用程序的方法。 Spectron is a commonly-used solution which lets you emulate user actions via WebDriver. 当然,也可以使用node的内建IPC STDIO来编写自己的自定义驱动。 自定义驱动的优势在于,它往往比Spectron需要更少的开销,并允许你向测试套件公开自定义方法。
我们将用 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 发送回复:
// 从测试套件进程侦听IPC消息
process.on('message', (msg) => {
// ...
})
// 向测试套件进程发送IPC消息
process.send({ my: 'message' })
现在,我们可以使用appProcess
对象从测试套件到Electron应用进行通讯。
为了方便起见,你可能需要封装appProcess
到一个驱动对象,以便提供更多高级函数。 下面是一个如何这样做的示例:
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) {
// 发送 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()
}
}
在应用中, 你需要为 RPC 回调编写一个简单的处理程序:
if (process.env.APP_TEST_DRIVER) {
process.on('message', onMessage)
}
async function onMessage ({ 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 })
}
}
const METHODS = {
isReady () {
// 必要的配置操作等
return true
}
// 此处定义可被RPC回调的方法
}
然后, 在测试套件中, 可以按如下方式使用测试驱动程序:
const test = require('ava')
const electronPath = require('electron')
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()
})