使用自定义驱动程序进行自动化测试

为Electron应用编写自动测试, 你需要一种 “驱动” 应用程序的方法。 Spectron 是一种常用的解决方案, 它允许您通过 WebDriver 模拟用户行为。 当然,也可以使用node的内建IPC STDIO来编写自己的自定义驱动。 自定义驱动的优势在于,它往往比Spectron需要更少的开销,并允许你向测试套件公开自定义方法。

我们将用 Node.js 的 child_process API 来创建一个自定义驱动。 测试套件将生成 Electron 子进程,然后建立一个简单的消息传递协议。

  1. var childProcess = require('child_process')
  2. var electronPath = require('electron')
  3. // 生成进程
  4. var env = { /* ... */ }
  5. var stdio = ['inherit', 'inherit', 'inherit', 'ipc']
  6. var appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
  7. // 从应用侦听IPC消息
  8. appProcess.on('message', (msg) => {
  9. // ...
  10. })
  11. // 向应用发送IPC消息
  12. appProcess.send({ my: 'message' })

在 Electron 应用程序中,您可以侦听消息或者使用 Node.js 的 process API 发送回复:

  1. // 从测试套件进程侦听IPC消息
  2. process.on('message', (msg) => {
  3. // ...
  4. })
  5. // 向测试套件进程发送IPC消息
  6. process.send({ my: 'message' })

现在,我们可以使用appProcess 对象从测试套件到Electron应用进行通讯。

为了方便起见,你可能需要封装appProcess到一个驱动对象,以便提供更多高级函数。 下面是一个如何这样做的示例:

  1. class TestDriver {
  2. constructor ({ path, args, env }) {
  3. this.rpcCalls = []
  4. // start child process
  5. env.APP_TEST_DRIVER = 1 // let the app know it should listen for messages
  6. this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })
  7. // handle rpc responses
  8. this.process.on('message', (message) => {
  9. // pop the handler
  10. var rpcCall = this.rpcCalls[message.msgId]
  11. if (!rpcCall) return
  12. this.rpcCalls[message.msgId] = null
  13. // reject/resolve
  14. if (message.reject) rpcCall.reject(message.reject)
  15. else rpcCall.resolve(message.resolve)
  16. })
  17. // wait for ready
  18. this.isReady = this.rpc('isReady').catch((err) => {
  19. console.error('Application failed to start', err)
  20. this.stop()
  21. process.exit(1)
  22. })
  23. }
  24. // simple RPC call
  25. // to use: driver.rpc('method', 1, 2, 3).then(...)
  26. async rpc (cmd, ...args) {
  27. // send rpc request
  28. var msgId = this.rpcCalls.length
  29. this.process.send({ msgId, cmd, args })
  30. return new Promise((resolve, reject) => this.rpcCalls.push({ resolve, reject }))
  31. }
  32. stop () {
  33. this.process.kill()
  34. }
  35. }

在应用中, 你需要为 RPC 回调编写一个简单的处理程序:

  1. if (process.env.APP_TEST_DRIVER) {
  2. process.on('message', onMessage)
  3. }
  4. async function onMessage ({ msgId, cmd, args }) {
  5. var method = METHODS[cmd]
  6. if (!method) method = () => new Error('Invalid method: ' + cmd)
  7. try {
  8. var resolve = await method(...args)
  9. process.send({ msgId, resolve })
  10. } catch (err) {
  11. var reject = {
  12. message: err.message,
  13. stack: err.stack,
  14. name: err.name
  15. }
  16. process.send({ msgId, reject })
  17. }
  18. }
  19. const METHODS = {
  20. isReady () {
  21. // do any setup needed
  22. return true
  23. }
  24. // define your RPC-able methods here
  25. }

然后, 在测试套件中, 可以按如下方式使用测试驱动程序:

  1. var test = require('ava')
  2. var electronPath = require('electron')
  3. var app = new TestDriver({
  4. path: electronPath,
  5. args: ['./app'],
  6. env: {
  7. NODE_ENV: 'test'
  8. }
  9. })
  10. test.before(async t => {
  11. await app.isReady
  12. })
  13. test.after.always('cleanup', async t => {
  14. await app.stop()
  15. })