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

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

    为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. })