Skip to main content

Automated Testing自动化测试

Test automation is an efficient way of validating that your application code works as intended. 测试自动化是验证应用程序代码是否按预期工作的有效方法。While Electron doesn't actively maintain its own testing solution, this guide will go over a couple ways you can run end-to-end automated tests on your Electron app.虽然Electron没有主动维护自己的测试解决方案,但本指南将介绍几种在Electron应用程序上运行端到端自动测试的方法。

Using the WebDriver interface使用WebDriver界面

From ChromeDriver - WebDriver for Chrome:来自ChromeDriver-Chrome的WebDriver

WebDriver is an open source tool for automated testing of web apps across many browsers. WebDriver是一个开放源码工具,用于跨多个浏览器自动测试web应用程序。It provides capabilities for navigating to web pages, user input, JavaScript execution, and more. 它提供了导航到网页、用户输入、JavaScript执行等功能。ChromeDriver is a standalone server which implements WebDriver's wire protocol for Chromium. ChromeDriver是一个独立的服务器,它为Chromium实现WebDriver的有线协议。It is being developed by members of the Chromium and WebDriver teams.它由Chromium和WebDriver团队的成员开发。

There are a few ways that you can set up testing using WebDriver.有几种方法可以使用WebDriver设置测试。

With 使用WebdriverIO

WebdriverIO (WDIO) is a test automation framework that provides a Node.js package for testing with WebDriver. 是一个测试自动化框架,它为使用WebDriver进行测试提供了Node.js包。Its ecosystem also includes various plugins (e.g. reporter and services) that can help you put together your test setup.它的生态系统还包括各种插件(例如报告器和服务),可以帮助您整合测试设置。

Install the testrunner安装testrunner

First you need to run the WebdriverIO starter toolkit in your project root directory:首先,您需要在项目根目录中运行WebdriverIO starter工具箱:

npx wdio . --yes

This installs all necessary packages for you and generates a wdio.conf.js configuration file.这将为您安装所有必要的软件包,并生成一个wdio.conf.js配置文件。

Connect WDIO to your Electron app将WDIO连接到Electron应用程序

Update the capabilities in your configuration file to point to your Electron app binary:更新配置文件中的功能以指向Electron应用程序二进制文件:

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

To 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. 是一个web自动化框架,它以多种语言公开对WebDriver API的绑定。Their Node.js bindings are available under the selenium-webdriver package on NPM.它们的Node.js绑定位于NPM上的selenium-webdriver包下。

Run a ChromeDriver server运行ChromeDriver服务器

In order to use Selenium with Electron, you need to download the electron-chromedriver binary, and run it:为了将Selenium与Electron结合使用,您需要下载electro-chromedriver二进制文件,并运行它:

npm install --save-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.记住端口号9515,稍后将使用它。

Connect Selenium to ChromeDriver将硒连接到ChromeDriver

Next, install Selenium into your project:接下来,将Selenium安装到项目中:

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:Electron使用selenium-webdriver与普通网站相同,只是您必须手动指定如何连接ChromeDriver以及在何处查找Electron应用程序的二进制文件:

test.js
const webdriver = require('selenium-webdriver')
const driver = new webdriver.Builder()
// The "9515" is the port opened by ChromeDriver.
.usingServer('http://localhost:9515')
.withCapabilities({
'goog:chromeOptions': {
// Here is the path to your Electron binary.
binary: '/Path-to-Your-App.app/Contents/MacOS/Electron'
}
})
.forBrowser('chrome') // note: use .forBrowser('electron') for selenium-webdriver <= 3.6.0
.build()
driver.get('http://www.google.com')
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver')
driver.findElement(webdriver.By.name('btnG')).click()
driver.wait(() => {
return driver.getTitle().then((title) => {
return title === 'webdriver - Google Search'
})
}, 1000)
driver.quit()

Using 使用Playwright

Microsoft Playwright is an end-to-end testing framework built using browser-specific remote debugging protocols, similar to the Puppeteer headless Node.js API but geared towards end-to-end testing. 是一个使用特定于浏览器的远程调试协议构建的端到端测试框架,类似于Puppeteer无头Node.js API,但面向端到端的测试。Playwright has experimental Electron support via Electron's support for the Chrome DevTools Protocol (CDP).剧作家通过Electron对Chrome DevTools协议(CDP)的支持,获得了Electron的实验性支持。

Install dependencies安装依赖项

You can install Playwright through your preferred Node.js package manager. 您可以通过首选的Node.js包管理器安装Playwright。The Playwright team recommends using the PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD environment variable to avoid unnecessary browser downloads when testing an Electron app.剧作家团队建议在测试Electron应用程序时使用PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD环境变量,以避免不必要的浏览器下载。

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 install --save-dev @playwright/test
Dependencies

This tutorial was written playwright@1.16.3 and @playwright/test@1.16.3. 本教程是编写的playwright@1.16.3@playwright/test@1.16.3Check out Playwright's releases page to learn about changes that might affect the code below.查看Playwright的发布页面,了解可能影响下面代码的更改。

Using third-party test runners使用第三方测试运行程序

If you're interested in using an alternative test runner (e.g. Jest or Mocha), check out Playwright's Third-Party Test Runner guide.如果您有兴趣使用其他测试运行程序(例如Jest或Mocha),请查看Playwright的第三方测试运行程序指南。

Write your tests编写测试

Playwright launches your app in development mode through the _electron.launch API. Playwright通过_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).要将此API指向Electron应用程序,可以将路径传递到主进程入口点(此处为main.js)。

const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')

test('launch app', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
// close app
await electronApp.close()
})

After that, you will access to an instance of Playwright's ElectronApp class. 之后,您将访问Playwright的ElectronApp类的实例。This is a powerful class that has access to main process modules for example:这是一个功能强大的类,可以访问主要流程模块,例如:

const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')

test('get isPackaged', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
const isPackaged = await electronApp.evaluate(async ({ app }) => {
// This runs in Electron's main process, parameter here is always
// the result of the require('electron') in the main app script.
return app.isPackaged
})
console.log(isPackaged) // false (because we're in development mode)
// close app
await electronApp.close()
})

It can also create individual Page objects from Electron BrowserWindow instances. 它还可以从Electron BrowserWindow实例创建单独的Page对象。For example, to grab the first BrowserWindow and save a screenshot:例如,要抓取第一个BrowserWindow并保存屏幕截图,请执行以下操作:

const { _electron: electron } = require('playwright')
const { test } = require('@playwright/test')

test('save screenshot', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// close app
await electronApp.close()
})

Putting all this together using the PlayWright Test runner, let's create a example.spec.js test file with a single test and assertion:使用PlayWright Test runner将所有这些放在一起,让我们创建一个包含单个测试和断言的example.spec.js测试文件:

example.spec.js
const { _electron: electron } = require('playwright')
const { test, expect } = require('@playwright/test')

test('example test', async () => {
const electronApp = await electron.launch({ args: ['.'] })
const isPackaged = await electronApp.evaluate(async ({ app }) => {
// This runs in Electron's main process, parameter here is always
// the result of the require('electron') in the main app script.
return app.isPackaged;
});

expect(isPackaged).toBe(false);

// Wait for the first BrowserWindow to open
// and return its Page object
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })

// close app
await electronApp.close()
});

Then, run Playwright Test using npx playwright test. 然后,使用npx playwright test运行剧作家测试。You should see the test pass in your console, and have an intro.png screenshot on your filesystem.您应该在控制台中看到测试通过,并在文件系统上有一个intro.png屏幕截图。

☁  $ npx playwright test

Running 1 test using 1 worker

✓ example.spec.js:4:1 › example test (1s)
info信息

Playwright Test will automatically run any files matching the .*(test|spec)\.(js|ts|mjs) regex. Playwright Test将自动运行与.*(test|spec)\.(js|ts|mjs)正则表达式匹配的任何文件。You can customize this match in the Playwright Test configuration options.您可以在“剧作家测试”配置选项中自定义此匹配。

Further reading进一步阅读

Check out Playwright's documentation for the full Electron and ElectronApplication class APIs.查看Playwright的文档,了解完整的ElectronElectronApplication类API。

Using a custom test driver使用自定义测试驱动程序

It's also possible to write your own custom driver using Node.js' built-in IPC-over-STDIO. 还可以使用Node.js内置的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. 要创建自定义驱动程序,我们将使用Node.js的child_processAPI。The test suite will spawn the Electron process, then establish a simple messaging protocol:测试套件将生成Electron流程,然后建立一个简单的消息传递协议:

testDriver.js
const childProcess = require('child_process')
const electronPath = require('electron')

// spawn the process
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 process API:在Electron应用程序中,您可以使用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.我们现在可以使用appProcess对象从测试套件与Electron应用程序通信。

For convenience, you may want to wrap appProcess in a driver object that provides more high-level functions. 为了方便起见,您可能希望将appProcess包装在提供更高级功能的驱动程序对象中。Here is an example of how you can do this. 下面是一个如何做到这一点的示例。Let's start by creating a TestDriver class:让我们从创建TestDriver类开始:

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

// 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
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:然后,可以在应用程序代码中编写一个简单的处理程序来接收RPC调用:

main.js
const METHODS = {
isReady () {
// do any setup needed
return true
}
// define your RPC-able methods here
}

const onMessage = async ({ msgId, cmd, args }) => {
let method = METHODS[cmd]
if (!method) method = () => new Error('Invalid method: ' + cmd)
try {
const resolve = await method(...args)
process.send({ msgId, resolve })
} catch (err) {
const reject = {
message: err.message,
stack: err.stack,
name: err.name
}
process.send({ msgId, reject })
}
}

if (process.env.APP_TEST_DRIVER) {
process.on('message', onMessage)
}

Then, in your test suite, you can use your TestDriver class with the test automation framework of your choosing. 然后,在测试套件中,您可以将TestDriver类与您选择的测试自动化框架一起使用。The following example uses ava, but other popular choices like Jest or Mocha would work as well:下面的例子使用了ava,但其他流行的选择,如Jest或Mocha也可以:

test.js
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 => {
await app.stop()
})