跳转到主要内容

自动化测试

测试自动化是一种有效验证您的应用程序代码是否按预期工作的方式。虽然 Electron 并没有主动维护自己的测试解决方案,但本指南将介绍几种在您的 Electron 应用程序上运行端到端自动化测试的方法。

使用 WebDriver 接口

来自 ChromeDriver - Chrome 的 WebDriver

WebDriver 是一种用于跨多种浏览器自动化测试 Web 应用程序的开源工具。它提供了导航到网页、用户输入、JavaScript 执行等功能。ChromeDriver 是一个独立服务器,它为 Chromium 实现了 WebDriver 的线路协议。它由 Chromium 和 WebDriver 团队的成员开发。

有几种方法可以设置使用 WebDriver 的测试。

使用 WebdriverIO

WebdriverIO (WDIO) 是一个测试自动化框架,它提供了一个用于使用 WebDriver 进行测试的 Node.js 包。它的生态系统还包括各种插件(例如报告器和服务),可以帮助您构建您的测试设置。

如果您已经有现有的 WebdriverIO 设置,建议更新您的依赖项并验证您的现有配置是否与文档中概述的一致此处

安装测试运行器

如果您尚未在您的项目中使用的 WebdriverIO,您可以在项目根目录中运行启动工具包来添加它

npm init wdio@latest ./

这将启动一个配置向导,帮助您构建正确的设置,安装所有必要的包,并生成一个 wdio.conf.js 配置文件。请确保在第一个问题中选择“桌面测试 - Electron 应用程序”,该问题询问“您想进行哪种类型的测试?”

将 WDIO 连接到您的 Electron 应用程序

运行配置向导后,您的 wdio.conf.js 应该包含以下内容

wdio.conf.js
export const config = {
// ...
services: ['electron'],
capabilities: [{
browserName: 'electron',
'wdio:electronServiceOptions': {
// WebdriverIO can automatically find your bundled application
// if you use Electron Forge or electron-builder, otherwise you
// can define it here, e.g.:
// appBinaryPath: './path/to/bundled/application.exe',
appArgs: ['foo', 'bar=baz']
}
}]
// ...
}

编写您的测试

使用 WebdriverIO API 与屏幕上的元素进行交互。该框架提供了自定义“匹配器”,使断言应用程序的状态变得容易,例如:

import { browser, $, expect } from '@wdio/globals'

describe('keyboard input', () => {
it('should detect keyboard input', async () => {
await browser.keys(['y', 'o'])
await expect($('keypress-count')).toHaveText('YO')
})
})

此外,WebdriverIO 允许您访问 Electron API 以获取应用程序的静态信息

import { browser } from '@wdio/globals'

describe('trigger message modal', async () => {
it('message modal can be triggered from a test', async () => {
await browser.electron.execute(
(electron, param1, param2, param3) => {
const appWindow = electron.BrowserWindow.getFocusedWindow()
electron.dialog.showMessageBox(appWindow, {
message: 'Hello World!',
detail: `${param1} + ${param2} + ${param3} = ${param1 + param2 + param3}`
})
},
1,
2,
3
)
})
})

运行您的测试

要运行您的测试

$ npx wdio run wdio.conf.js

WebdriverIO 会帮助您启动和关闭应用程序。

更多文档

官方 WebdriverIO 文档中找到有关模拟 Electron API 和其他有用资源的更多文档。

使用 Selenium

Selenium 是一个 Web 自动化框架,它在许多语言中公开了 WebDriver API 的绑定。他们的 Node.js 绑定在 NPM 上的 selenium-webdriver 包下可用。

运行 ChromeDriver 服务器

为了在 Electron 中使用 Selenium,您需要下载 electron-chromedriver 二进制文件并运行它

npm install --save-dev electron-chromedriver
./node_modules/.bin/chromedriver
Starting ChromeDriver (v2.10.291558) on port 9515
Only local connections are allowed.

记住端口号 9515,稍后将使用它。

将 Selenium 连接到 ChromeDriver

接下来,将 Selenium 安装到您的项目中

npm install --save-dev selenium-webdriver

selenium-webdriver 在 Electron 中的用法与在普通网站中相同,只是您必须手动指定如何连接 ChromeDriver 以及在哪里找到您的 Electron 应用程序的二进制文件

test.js
const webdriver = require('selenium-webdriver')

const driver = new webdriver.Builder()
// The "9515" is the port opened by ChromeDriver.
.usingServer('https://: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('https://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()

使用 Playwright

Microsoft Playwright 是一个端到端测试框架,它使用浏览器特定的远程调试协议构建,类似于 Puppeteer 无头 Node.js API,但面向端到端测试。Playwright 通过 Electron 对 Chrome DevTools Protocol (CDP) 的支持,提供实验性的 Electron 支持。

安装依赖项

您可以通过首选的 Node.js 包管理器安装 Playwright。它自带自己的 测试运行器,该测试运行器是为端到端测试而构建的

npm install --save-dev @playwright/test
依赖项

本教程使用 @playwright/test@1.52.0 编写。请查看 Playwright 的发布说明页面,以了解可能影响以下代码的更改。

编写您的测试

Playwright 通过 _electron.launch API 以开发模式启动您的应用程序。要将此 API 指向您的 Electron 应用程序,您可以传递主进程入口点(此处为 main.js)的路径。

import { test, _electron as electron } from '@playwright/test'

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

之后,您将访问 Playwright 的 ElectronApp 类的实例。这是一个功能强大的类,可以访问主进程模块,例如

import { test, _electron as electron } from '@playwright/test'

test('get isPackaged', 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
})
console.log(isPackaged) // false (because we're in development mode)
// close app
await electronApp.close()
})

它还可以从 Electron BrowserWindow 实例创建单个 Page 对象。例如,要获取第一个 BrowserWindow 并保存屏幕截图

import { test, _electron as electron } from '@playwright/test'

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

将所有这些与 Playwright 测试运行器结合起来,让我们创建一个名为 example.spec.js 的测试文件,其中包含一个测试和一个断言

example.spec.js
import { test, expect, _electron as electron } from '@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()
})

然后,使用 npx playwright test 运行 Playwright Test。您应该在控制台中看到测试通过,并且文件系统中有一个 intro.png 屏幕截图。

☁  $ npx playwright test

Running 1 test using 1 worker

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

Playwright Test 将自动运行所有匹配 .*(test|spec)\.(js|ts|mjs) 正则表达式的文件。您可以在 Playwright Test 配置选项中自定义此匹配。它也开箱即用地支持 TypeScript。

进一步阅读

查看 Playwright 的文档,了解完整的 ElectronElectronApplication 类 API。

使用自定义测试驱动程序

也可以使用 Node.js 内置的 IPC-over-STDIO 编写自己的自定义驱动程序。自定义测试驱动程序需要您编写额外的应用程序代码,但具有较低的开销,并允许您将自定义方法暴露给您的测试套件。

要创建自定义驱动程序,我们将使用 Node.js 的 child_process API。测试套件将生成 Electron 进程,然后建立一个简单的消息传递协议

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

const childProcess = require('node:child_process')

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

在 Electron 应用程序内,您可以使用 Node.js process 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' })

现在,我们可以使用 appProcess 对象从测试套件与 Electron 应用程序进行通信。

为了方便起见,您可能希望将 appProcess 包装在一个驱动程序对象中,该对象提供更高级别的函数。以下是如何执行此操作的示例。让我们从创建一个 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 }

在您的应用程序代码中,现在可以编写一个简单的处理程序来接收 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)
}

然后,在您的测试套件中,您可以使用您的 TestDriver 类和您选择的测试自动化框架。以下示例使用 ava,但其他流行的选择(如 Jest 或 Mocha)也可以工作

test.js
const electronPath = require('electron')

const test = require('ava')

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