跳转至

自动化测试

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

使用 WebDriver 接口

来自 ChromeDriver - WebDriver for Chrome:

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

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

使用 WebdriverIO

WebdriverIO (WDIO) 是一个测试自动化框架,提供了用于 WebDriver 测试的 Node.js 包。其生态系统还包括各种插件(例如 reporter 和 services),可帮助你搭建测试环境。

如果你已有 WebdriverIO 设置,建议更新依赖项,并按照文档中的说明验证现有配置。

安装测试运行器

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

```sh npm2yarn npm init wdio@latest ./

这将启动一个配置向导,帮助你完成正确的设置、安装所有必要的包,并生成 `wdio.conf.js` 配置文件。请务必在最初询问 _"What type of testing would you like to do?"_ 的某个问题中选择 _"Desktop Testing - of Electron Applications"_。

#### 将 WDIO 连接到你的 Electron 应用 {#connect-wdio-to-your-electron-app}

运行配置向导后,你的 `wdio.conf.js` 应大致包含以下内容:

```js title='wdio.conf.js' @ts-nocheck
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 与屏幕上的元素交互。该框架提供了自定义“匹配器”,让你可以轻松断言应用的状态,例如:

```js @ts-nocheck 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,以获取有关应用的静态信息:

```js @ts-nocheck
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 二进制文件并运行它:

```sh npm2yarn 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 {#connect-selenium-to-chromedriver}

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

```sh npm2yarn
npm install --save-dev selenium-webdriver

selenium-webdriver 在 Electron 中的使用方式与普通网站相同,不同之处在于你必须手动指定如何连接 ChromeDriver 以及在哪里找到 Electron 应用的二进制文件:

```js title='test.js' @ts-expect-error=[1] 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('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 {#using-playwright}

[Microsoft Playwright](https://playwright.dev) 是一个端到端测试框架,基于浏览器特定的远程调试协议构建,类似于 [Puppeteer][] 的无头 Node.js API,但更专注于端到端测试。Playwright 通过 Electron 对 [Chrome DevTools Protocol][] (CDP) 的支持,提供了实验性的 Electron 支持。



### 安装依赖 {#install-dependencies}

你可以通过首选的 Node.js 包管理器安装 Playwright。它自带一个[测试运行器][playwright-intro],专为端到端测试而构建:

```sh npm2yarn
npm install --save-dev @playwright/test

:::caution 依赖 本教程基于 @playwright/test@1.52.0 编写。请查看 Playwright 的发布 页面,了解可能影响以下代码的变更。 :::

编写测试

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

```js {5} @ts-nocheck 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` 类的一个实例。这是一个功能强大的类,可以访问主进程模块,例如:

```js {5-10} @ts-nocheck
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 并保存截图:

```js {6-7} @ts-nocheck 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` 测试文件,其中包含一个测试和断言:

```js title='example.spec.js' @ts-nocheck
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)

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

:::tip 延伸阅读 查看 Playwright 文档,了解完整的 Electron 和 ElectronApplication 类 API。 :::

使用自定义测试驱动程序

你也可以使用 Node.js 内置的 IPC-over-STDIO 编写自己的自定义驱动程序。自定义测试驱动程序需要你编写额外的应用代码,但开销更低,并且可以向测试套件暴露自定义方法。

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

```js title='testDriver.js' @ts-nocheck 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`](https://nodejs.org/api/process.html) API 监听消息并发送回复:

```js title='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 类:

```js title='testDriver.js' @ts-nocheck 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 调用:

```js title='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,也同样适用:

```js title='test.js' @ts-nocheck 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() }) ```

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 el/electron