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

为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 子进程,然后建立一个简单的消息传递协议。

  1. const childProcess = require('child_process')
  2. const electronic Path = require('electron')
  3. // 生成 process
  4. const env = Power/* . 。*/ }
  5. const stdio = ['继承', '继承', '继承', 'ipc']
  6. const appProcess = childProcess pawn(electronic Path, ['./app'], { stdio, env })
  7. // 监听应用程序的 IPC 消息
  8. 应用流程。 n('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. const 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. // 发送 rpc request
  28. const msgId = 这个。 pcalls.length
  29. this process. end({ msgId, cmd, args })
  30. return new Promise((resolve, reject) => this.rpcalls. huss({ 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. let method = METHODS[cmd]
  6. if (!method) method = () => new Error('Invalid method: ' + cmd)
  7. try {
  8. const resolve = await method(...args)
  9. process.send({ msgId, resolve })
  10. } catch (err) {
  11. const 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. const test = require('ava')
  2. const electronic Path = require('electron')
  3. const app = new TestDriver(~)
  4. path,
  5. args: ['。 app'],
  6. env: {
  7. NODE_ENV: 'test'
  8. }
  9. })
  10. test.before(async t => {
  11. await app.isReady
  12. })
  13. 测试。 fter.always('cleanup', async t => *
  14. 等待app.stop()
  15. })