2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								# Automated Testing with a Custom Driver
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								To write automated tests for your Electron app, you will need a way to "drive" your application. [Spectron ](https://electronjs.org/spectron ) is a commonly-used solution which lets you emulate user actions via [WebDriver ](http://webdriver.io/ ). However, it's also possible to write your own custom driver using node's builtin IPC-over-STDIO. The benefit of a custom driver is that it tends to require less overhead than Spectron, and lets you expose custom methods to your test suite.
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2019-06-21 16:19:21 -05:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								To create a custom driver, we'll use Node.js' [child_process ](https://nodejs.org/api/child_process.html ) API. The test suite will spawn the Electron process, then establish a simple messaging protocol:
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```js
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								const childProcess = require('child_process')
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								const electronPath = require('electron')
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								// spawn the process
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								let env = { /* ... */ }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								let stdio = ['inherit', 'inherit', 'inherit', 'ipc']
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								let appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								// listen for IPC messages from the app
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								appProcess.on('message', (msg) => {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  // ...
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								})
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								// send an IPC message to the app
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								appProcess.send({ my: 'message' })
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								```
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2019-06-21 16:19:21 -05:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								From within the Electron app, you can listen for messages and send replies using the Node.js [process ](https://nodejs.org/api/process.html ) API:
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```js
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								// listen for IPC messages from the test suite
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								process.on('message', (msg) => {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  // ...
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								})
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								// send an IPC message to the test suite
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								process.send({ my: 'message' })
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								```
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								We can now communicate from the test suite to the Electron app using the `appProcess`  object.
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								For convenience, you may want to wrap `appProcess`  in a driver object that provides more high-level functions. Here is an example of how you can do this:
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```js
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								class TestDriver {
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								  constructor ({ path, args, env }) {
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								    this.rpcCalls = []
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    // start child process
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    env.APP_TEST_DRIVER = 1 // let the app know it should listen for messages
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								    this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    // handle rpc responses
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    this.process.on('message', (message) => {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      // pop the handler
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								      let rpcCall = this.rpcCalls[message.msgId]
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								      if (!rpcCall) return
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      this.rpcCalls[message.msgId] = null
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      // reject/resolve
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      if (message.reject) rpcCall.reject(message.reject)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      else rpcCall.resolve(message.resolve)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    })
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    // wait for ready
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    this.isReady = this.rpc('isReady').catch((err) => {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      console.error('Application failed to start', err)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      this.stop()
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      process.exit(1)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    })
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  // simple RPC call
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  // to use: driver.rpc('method', 1, 2, 3).then(...)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  async rpc (cmd, ...args) {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    // send rpc request
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								    let msgId = this.rpcCalls.length
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								    this.process.send({ msgId, cmd, args })
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    return new Promise((resolve, reject) => this.rpcCalls.push({ resolve, reject }))
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								  }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  stop () {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    this.process.kill()
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								}
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								In the app, you'd need to write a simple handler for the RPC calls:
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```js
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								if (process.env.APP_TEST_DRIVER) {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  process.on('message', onMessage)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								}
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								async function onMessage ({ msgId, cmd, args }) {
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								  let method = METHODS[cmd]
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								  if (!method) method = () => new Error('Invalid method: ' + cmd)
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  try {
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								    let resolve = await method(...args)
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								    process.send({ msgId, resolve })
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								  } catch (err) {
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								    let reject = {
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								      message: err.message,
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      stack: err.stack,
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      name: err.name
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    }
							 
						 
					
						
							
								
									
										
										
										
											2018-09-14 02:10:51 +10:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								    process.send({ msgId, reject })
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								  }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								}
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								const METHODS = {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  isReady () {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    // do any setup needed
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    return true
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  // define your RPC-able methods here
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								}
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								Then, in your test suite, you can use your test-driver as follows:
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```js
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								const test = require('ava')
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								const electronPath = require('electron')
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
									
										
										
										
											2020-01-13 02:29:46 +01:00 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								let app = new TestDriver({
							 
						 
					
						
							
								
									
										
										
										
											2018-04-20 11:39:13 -05:00 
										
									 
								 
							 
							
								
							 
							
								 
							 
							
							
								  path: electronPath,
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  args: ['./app'],
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  env: {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    NODE_ENV: 'test'
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  }
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								})
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								test.before(async t => {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  await app.isReady
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								})
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								test.after.always('cleanup', async t => {
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  await app.stop()
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								})
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								```