Electron 自動化測試

2023-02-16 17:16 更新

測試自動化是一種驗證您的應(yīng)用是否如你預(yù)期那樣正常工作的有效方法。 Electron已然不再維護自己的測試解決方案, 本指南將會介紹幾種方法,讓您可以在 Electron 應(yīng)用上進行端到端自動測試。

使用 WebDriver 接口

引自 ChromeDriver - WebDriver for Chrome:

WebDriver 是一款開源的支持多瀏覽器的自動化測試工具。 它提供了操作網(wǎng)頁、用戶輸入、JavaScript 執(zhí)行等能力。 ChromeDriver 是一個實現(xiàn)了 WebDriver 與 Chromium 聯(lián)接協(xié)議的獨立服務(wù)。 它也是由開發(fā)了 Chromium 和 WebDriver 的團隊開發(fā)的。

有幾種方法可以使用 WebDriver 設(shè)置測試。

使用 WebdriverIO

WebdriverIO (WDIO) 是一個自動化測試框架,它提供了一個 Node.js 軟件包用于測試 Web 驅(qū)動程序。 它的生態(tài)系統(tǒng)還包括各種插件(例如報告器和服務(wù)) ,可以幫助你把測試設(shè)置放在一起。

安裝測試運行器

首先,你需要在項目根目錄中運行 WebdriverIO 啟動工具包:

 npm Yarn 
npx wdio . --yes
npx wdio . --yes

這將安裝所有必要的軟件包,并生成一個 wdio.conf.js 配置文件。

將 WDIO 連接到 Electron 應(yīng)用程序?

更新配置文件中的選項以指向 Electron 應(yīng)用二進制文件:

export.config = {
  // ...
  capabilities: [{
    browserName: 'chrome',
    'goog:chromeOptions': {
      binary: '/path/to/your/electron/binary', // Electron 二進制文件的路徑
      args: [/* 命令行參數(shù) */] // 可選, 比如 'app=' + /path/to/your/app/
    }
  }]
  // ...
}

運行測試?

執(zhí)行命令:

$ npx wdio run wdio.conf.js

使用 Selenium

Selenium 是一個Web自動化框架,以多種語言公開與 WebDriver API 的綁定方式。 Node.js 環(huán)境下, 可以通過 NPM 安裝 selenium-webdriver 包來使用此框架。

運行 ChromeDriver 服務(wù)

為了與 Electron 一起使用 Selenium ,你需要下載 electron-chromedriver 二進制文件并運行它:

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

在 Electron 下使用 selenium-webdriver 和其平時的用法并沒有大的差異,只是你需要手動設(shè)置如何連接 ChromeDriver,以及 Electron 應(yīng)用的查找路徑:

const webdriver = require('selenium-webdriver')
const driver = new webdriver.Builder()
  // 端口號 "9515" 是被 ChromeDriver 開啟的.
  .usingServer('http://localhost:9515')
  .withCapabilities({
    'goog:chromeOptions': {
      // 這里填您的Electron二進制文件路徑。
      binary: '/Path-to-Your-App.app/Contents/MacOS/Electron'
    }
  })
  .forBrowser('chrome') // 注意: 使用 .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()

使用 Playwright?

Microsoft Playwright 是一個端到端的測試框架,使用瀏覽器特定的遠程調(diào)試協(xié)議架構(gòu),類似于 Puppeteer 的無頭 Node.js API,但面向端到端測試。 Playwright 通過 Electron 支持 [Chrome DevTools 協(xié)議][] (CDP) 獲得實驗性的 Electron 支持。

安裝依賴項

您可以通過 Node.js 包管理器安裝 Playwright。 Playwright團隊推薦使用 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD 環(huán)境變量來避免在測試 Electron 軟件時進行不必要的瀏覽器下載。

 npm Yarn 
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --save-dev playwright
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 yarn add --dev playwright

Playwright 同時也有自己的測試運行器( Playwright Test ),可用作端到端(E2E)測試。 你也可以在項目中作為開發(fā)以來來安裝它:

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

::: 依賴注意事項

本教程的編寫基于 playwright@1.16.3 和 @playwright/test@1.16.3 查看Playwright 的版本更新 頁面以知曉可能會影響到的代碼更改。

:::

:::使用第三方測試運行程序的信息

如果您有興趣使用其他測試運行其(例如 Jest 或 Mocha),請查看 Playwright 的 第三方測試運行器 指南。

:::

編寫測試

Playwright通過 _electron.launch API在開發(fā)模式下啟動您的應(yīng)用程序。 要將此 API 指向 Electron 應(yīng)用,可以將路徑傳遞到主進程入口點(此處為 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()
})

在此之后,您將可以訪問到 Playwright 的 ElectronApp 類的一個實例。 這是一個功能強大的類,可以訪問主進程模塊,例如:

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 }) => {
    // 在 Electron 的主進程運行,這里的參數(shù)總是
    // 主程序代碼中 require('electron') 的返回結(jié)果。
    return app.isPackaged
  })
  console.log(isPackaged) // false(因為我們處在開發(fā)環(huán)境)
  // 關(guān)閉應(yīng)用程序
  await electronApp.close()
})

它還可以從 Electron BrowserWindow 實例創(chuàng)建單獨的 Page 對象。 例如,獲取第一個 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' })
  // 關(guān)閉應(yīng)用程序
  await electronApp.close()
})

使用 PlayWright 測試運行器將所有這些組合到一起,讓我們創(chuàng)建一個有單個測試和斷言的 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 }) => {
    // 在 Electron 的主進程運行,這里的參數(shù)總是
    // 主程序代碼中 require('electron') 的返回結(jié)果。
    return app.isPackaged;
  });

  expect(isPackaged).toBe(false);

  // 等待第一個 BrowserWindow 打開
  // 然后返回它的 Page 對象
  const window = await electronApp.firstWindow()
  await window.screenshot({ path: 'intro.png' })

  // 關(guān)閉應(yīng)用程序
  await electronApp.close()
});

然后,使用 npx playwright test 運行 Playwright 測試。 您應(yīng)該在您的控制臺中看到測試通過,并在您的文件系統(tǒng)上看到一個屏幕截圖 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 配置選項 中自定義這個正則表達式。

:::延伸閱讀

查看 Playwright完整的 Electron 和 ElectronApplication class API。

:::

使用自定義測試驅(qū)動?

當(dāng)然,也可以使用node的內(nèi)建IPC STDIO來編寫自己的自定義驅(qū)動。 自定義測試驅(qū)動程序需要您寫額外的應(yīng)用代碼,但是有較低的開銷,讓您 在您的測試套裝上顯示自定義方法。

我們將用 Node.js 的 child_process API 來創(chuàng)建一個自定義驅(qū)動。 測試套件將生成 Electron 子進程,然后建立一個簡單的消息傳遞協(xié)議。

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

// 偵聽?wèi)?yīng)用傳來的IPC信息
appProcess.on('message', (msg) => {
  // ...
})

// 向應(yīng)用發(fā)送IPC消息
appProcess.send({ my: 'message' })

在 Electron 應(yīng)用程序中,您可以使用 Node.js 的 process API 監(jiān)聽消息并發(fā)送回復(fù):

// 監(jiān)聽測試套件發(fā)送過來的消息
process.on('message', (msg) => {
  // ...
})

// 發(fā)送一條消息到測試套件
process.send({ my: 'message' })

現(xiàn)在,我們可以使用appProcess 對象從測試套件到Electron應(yīng)用進行通訊。

為方便起見,您可能希望將 appProcess 包裝在一個提供更高級功能的驅(qū)動程序?qū)ο笾小?下面是一個示例。 讓我們從創(chuàng)建一個 TestDriver 類開始:

class TestDriver {
  constructor ({ path, args, env }) {
    this.rpcCalls = []

    // 啟動子進程
    env.APP_TEST_DRIVER = 1 // 讓應(yīng)用知道它應(yīng)當(dāng)偵聽信息
    this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })

    // 處理RPC回復(fù)
    this.process.on('message', (message) => {
      // 彈出處理器
      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)
    })

    // 等待準(zhǔn)備完畢
    this.isReady = this.rpc('isReady').catch((err) => {
      console.error('Application failed to start', err)
      this.stop()
      process.exit(1)
    })
  }

  // 簡單 RPC 回調(diào)
  // 可以使用:driver.rpc('method', 1, 2, 3).then(...)
  async rpc (cmd, ...args) {
    // 發(fā)送 RPC 請求
    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 };

然后,在您的應(yīng)用代碼中,可以編寫一個簡單的處理程序來接收 RPC 調(diào)用:

const METHODS = {
  isReady () {
    // 進行任何需要的初始化
    return true
  }
  // 在這里定義可做 RPC 調(diào)用的方法
}

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 也可以:

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

[Chrome DevTools 協(xié)議]: https://chromedevtools.github.io/devtools-protocol/


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號