Automated Testing
From ChromeDriver - WebDriver for Chrome:
There are a few ways that you can set up testing using WebDriver.
(WDIO) is a test automation framework that provides a Node.js package for testing with WebDriver. Its ecosystem also includes various plugins (e.g. reporter and services) that can help you put together your test setup.
Install the testrunner
First you need to run the WebdriverIO starter toolkit in your project root directory:
- npm
- Yarn
This installs all necessary packages for you and generates a wdio.conf.js
configuration file.
Connect WDIO to your Electron app
Update the capabilities in your configuration file to point to your Electron app binary:
wdio.conf.js
export.config = {
// ...
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
binary: '/path/to/your/electron/binary', // Path to your Electron binary.
args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/
}
}]
// ...
}
Run your tests
$ npx wdio run wdio.conf.js
With Selenium
Selenium is a web automation framework that exposes bindings to WebDriver APIs in many languages. Their Node.js bindings are available under the selenium-webdriver
package on NPM.
Run a ChromeDriver server
In order to use Selenium with Electron, you need to download the electron-chromedriver
binary, and run it:
- npm
- Yarn
yarn add --dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.
Remember the port number 9515
, which will be used later.
Connect Selenium to ChromeDriver
Next, install Selenium into your project:
- npm
- Yarn
npm install --save-dev selenium-webdriver
yarn add --dev selenium-webdriver
Usage of selenium-webdriver
with Electron is the same as with normal websites, except that you have to manually specify how to connect ChromeDriver and where to find the binary of your Electron app:
test.js
Using a custom test driver
It’s also possible to write your own custom driver using Node.js’ built-in IPC-over-STDIO. Custom test drivers require you to write additional app code, but have lower overhead and let you expose custom methods to your test suite.
To create a custom driver, we’ll use Node.js’ child_process API. The test suite will spawn the Electron process, then establish a simple messaging protocol:
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 })
// listen for IPC messages from the app
appProcess.on('message', (msg) => {
// ...
})
// send an IPC message to the app
appProcess.send({ my: 'message' })
From within the Electron app, you can listen for messages and send replies using the Node.js API:
main.js
// listen for messages from the test suite
process.on('message', (msg) => {
// ...
})
// send a message to the test suite
process.send({ my: 'message' })
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. Let’s start by creating a TestDriver
class:
testDriver.js
class TestDriver {
constructor ({ path, args, env }) {
this.rpcCalls = []
// start child process
env.APP_TEST_DRIVER = 1 // let the app know it should listen for messages
this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })
// handle rpc responses
this.process.on('message', (message) => {
// pop the handler
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()
})
}
// simple RPC call
// to use: driver.rpc('method', 1, 2, 3).then(...)
async rpc (cmd, ...args) {
// send rpc request
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()
}
}
module.exports = { TestDriver };
In your app code, can then write a simple handler to receive RPC calls:
main.js
Then, in your test suite, you can use your TestDriver
class with the test automation framework of your choosing. The following example uses ava, but other popular choices like Jest or Mocha would work as well:
const test = require('ava')
const electronPath = require('electron')
const { TestDriver } = require('./testDriver')
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 => {