Automated Testing

    From :

    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 test runner

    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

    1. export.config = {
    2. // ...
    3. capabilities: [{
    4. browserName: 'chrome',
    5. 'goog:chromeOptions': {
    6. binary: '/path/to/your/electron/binary', // Path to your Electron binary.
    7. args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/
    8. }
    9. }]
    10. // ...
    11. }

    Run your tests

    To run your tests:

    1. $ npx wdio run wdio.conf.js

    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
    1. npm install --save-dev electron-chromedriver
    2. ./node_modules/.bin/chromedriver
    3. Starting ChromeDriver (v2.10.291558) on port 9515
    4. Only local connections are allowed.
    1. yarn add --dev electron-chromedriver
    2. ./node_modules/.bin/chromedriver
    3. Starting ChromeDriver (v2.10.291558) on port 9515
    4. 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
    1. npm install --save-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:

    1. const webdriver = require('selenium-webdriver')
    2. const driver = new webdriver.Builder()
    3. // The "9515" is the port opened by ChromeDriver.
    4. .usingServer('http://localhost:9515')
    5. .withCapabilities({
    6. 'goog:chromeOptions': {
    7. // Here is the path to your Electron binary.
    8. binary: '/Path-to-Your-App.app/Contents/MacOS/Electron'
    9. }
    10. })
    11. .forBrowser('chrome') // note: use .forBrowser('electron') for selenium-webdriver <= 3.6.0
    12. .build()
    13. driver.get('http://www.google.com')
    14. driver.findElement(webdriver.By.name('q')).sendKeys('webdriver')
    15. driver.findElement(webdriver.By.name('btnG')).click()
    16. driver.wait(() => {
    17. return driver.getTitle().then((title) => {
    18. return title === 'webdriver - Google Search'
    19. })
    20. }, 1000)
    21. driver.quit()

    Microsoft Playwright is an end-to-end testing framework built using browser-specific remote debugging protocols, similar to the headless Node.js API but geared towards end-to-end testing. Playwright has experimental Electron support via Electron’s support for the Chrome DevTools Protocol (CDP).

    You can install Playwright through your preferred Node.js package manager. The Playwright team recommends using the PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD environment variable to avoid unnecessary browser downloads when testing an Electron app.

    • npm
    • Yarn
    1. PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --save-dev playwright

      Playwright also comes with its own test runner, Playwright Test, which is built for end-to-end testing. You can also install it as a dev dependency in your project:

      • npm
      • Yarn
      1. npm install --save-dev @playwright/test
      1. yarn add --dev @playwright/test

      Dependencies

      This tutorial was written playwright@1.16.3 and @playwright/test@1.16.3. Check out Playwright’s releases page to learn about changes that might affect the code below.

      Automated Testing - 图2Using third-party test runners

      If you’re interested in using an alternative test runner (e.g. Jest or Mocha), check out Playwright’s guide.

      Playwright launches your app in development mode through the _electron.launch API. To point this API to your Electron app, you can pass the path to your main process entry point (here, it is main.js).

      1. const { _electron: electron } = require('playwright')
      2. const { test } = require('@playwright/test')
      3. test('launch app', async () => {
      4. const electronApp = await electron.launch({ args: ['main.js'] })
      5. // close app
      6. })

      After that, you will access to an instance of Playwright’s ElectronApp class. This is a powerful class that has access to main process modules for example:

      It can also create individual objects from Electron BrowserWindow instances. For example, to grab the first BrowserWindow and save a screenshot:

      1. const { _electron: electron } = require('playwright')
      2. const { test } = require('@playwright/test')
      3. test('save screenshot', async () => {
      4. const electronApp = await electron.launch({ args: ['main.js'] })
      5. const window = await electronApp.firstWindow()
      6. await window.screenshot({ path: 'intro.png' })
      7. // close app
      8. await electronApp.close()
      9. })

      Putting all this together using the PlayWright Test runner, let’s create a example.spec.js test file with a single test and assertion:

      example.spec.js

      1. const { _electron: electron } = require('playwright')
      2. const { test, expect } = require('@playwright/test')
      3. test('example test', async () => {
      4. const electronApp = await electron.launch({ args: ['.'] })
      5. const isPackaged = await electronApp.evaluate(async ({ app }) => {
      6. // This runs in Electron's main process, parameter here is always
      7. // the result of the require('electron') in the main app script.
      8. return app.isPackaged;
      9. });
      10. expect(isPackaged).toBe(false);
      11. // Wait for the first BrowserWindow to open
      12. // and return its Page object
      13. const window = await electronApp.firstWindow()
      14. await window.screenshot({ path: 'intro.png' })
      15. // close app
      16. await electronApp.close()
      17. });

      Then, run Playwright Test using npx playwright test. You should see the test pass in your console, and have an intro.png screenshot on your filesystem.

      1. $ npx playwright test
      2. Running 1 test using 1 worker
      3. example.spec.js:4:1 example test (1s)

      info

      Automated Testing - 图4Further reading

      Check out Playwright’s documentation for the full Electron and class APIs.

      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’ API. The test suite will spawn the Electron process, then establish a simple messaging protocol:

      testDriver.js

      1. const childProcess = require('child_process')
      2. const electronPath = require('electron')
      3. // spawn the process
      4. const env = { /* ... */ }
      5. const stdio = ['inherit', 'inherit', 'inherit', 'ipc']
      6. const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
      7. // listen for IPC messages from the app
      8. appProcess.on('message', (msg) => {
      9. // ...
      10. })
      11. // send an IPC message to the app
      12. appProcess.send({ my: 'message' })

      From within the Electron app, you can listen for messages and send replies using the Node.js process API:

      main.js

      1. // listen for messages from the test suite
      2. // ...
      3. })
      4. 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

      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. // send rpc request
      28. const 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. }
      36. 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 , but other popular choices like Jest or Mocha would work as well:

      test.js

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