測(cè)試自動(dòng)化是一種驗(yàn)證您的應(yīng)用是否如你預(yù)期那樣正常工作的有效方法。 Electron已然不再維護(hù)自己的測(cè)試解決方案, 本指南將會(huì)介紹幾種方法,讓您可以在 Electron 應(yīng)用上進(jìn)行端到端自動(dòng)測(cè)試。
引自 ChromeDriver - WebDriver for Chrome:
WebDriver 是一款開源的支持多瀏覽器的自動(dòng)化測(cè)試工具。 它提供了操作網(wǎng)頁(yè)、用戶輸入、JavaScript 執(zhí)行等能力。 ChromeDriver 是一個(gè)實(shí)現(xiàn)了 WebDriver 與 Chromium 聯(lián)接協(xié)議的獨(dú)立服務(wù)。 它也是由開發(fā)了 Chromium 和 WebDriver 的團(tuán)隊(duì)開發(fā)的。
有幾種方法可以使用 WebDriver 設(shè)置測(cè)試。
WebdriverIO (WDIO) 是一個(gè)自動(dòng)化測(cè)試框架,它提供了一個(gè) Node.js 軟件包用于測(cè)試 Web 驅(qū)動(dòng)程序。 它的生態(tài)系統(tǒng)還包括各種插件(例如報(bào)告器和服務(wù)) ,可以幫助你把測(cè)試設(shè)置放在一起。
安裝測(cè)試運(yùn)行器
首先,你需要在項(xiàng)目根目錄中運(yùn)行 WebdriverIO 啟動(dòng)工具包:
npm | Yarn |
|
|
這將安裝所有必要的軟件包,并生成一個(gè) wdio.conf.js
配置文件。
更新配置文件中的選項(xiàng)以指向 Electron 應(yīng)用二進(jìn)制文件:
export.config = {
// ...
capabilities: [{
browserName: 'chrome',
'goog:chromeOptions': {
binary: '/path/to/your/electron/binary', // Electron 二進(jìn)制文件的路徑
args: [/* 命令行參數(shù) */] // 可選, 比如 'app=' + /path/to/your/app/
}
}]
// ...
}
執(zhí)行命令:
$ npx wdio run wdio.conf.js
Selenium 是一個(gè)Web自動(dòng)化框架,以多種語(yǔ)言公開與 WebDriver API 的綁定方式。 Node.js 環(huán)境下, 可以通過 NPM 安裝 selenium-webdriver
包來(lái)使用此框架。
為了與 Electron 一起使用 Selenium ,你需要下載 electron-chromedriver
二進(jìn)制文件并運(yùn)行它:
npm | Yarn |
|
|
記住 9515
這個(gè)端口號(hào),我們后面會(huì)用到.
接下來(lái),把 Selenium 安裝到你的項(xiàng)目中:
npm | Yarn |
|
|
在 Electron 下使用 selenium-webdriver
和其平時(shí)的用法并沒有大的差異,只是你需要手動(dòng)設(shè)置如何連接 ChromeDriver,以及 Electron 應(yīng)用的查找路徑:
const webdriver = require('selenium-webdriver')
const driver = new webdriver.Builder()
// 端口號(hào) "9515" 是被 ChromeDriver 開啟的.
.usingServer('http://localhost:9515')
.withCapabilities({
'goog:chromeOptions': {
// 這里填您的Electron二進(jìn)制文件路徑。
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()
Microsoft Playwright 是一個(gè)端到端的測(cè)試框架,使用瀏覽器特定的遠(yuǎn)程調(diào)試協(xié)議架構(gòu),類似于 Puppeteer 的無(wú)頭 Node.js API,但面向端到端測(cè)試。 Playwright 通過 Electron 支持 [Chrome DevTools 協(xié)議][] (CDP) 獲得實(shí)驗(yàn)性的 Electron 支持。
您可以通過 Node.js 包管理器安裝 Playwright。 Playwright團(tuán)隊(duì)推薦使用 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD
環(huán)境變量來(lái)避免在測(cè)試 Electron 軟件時(shí)進(jìn)行不必要的瀏覽器下載。
npm | Yarn |
|
|
Playwright 同時(shí)也有自己的測(cè)試運(yùn)行器( Playwright Test ),可用作端到端(E2E)測(cè)試。 你也可以在項(xiàng)目中作為開發(fā)以來(lái)來(lái)安裝它:
npm | Yarn |
|
|
::: 依賴注意事項(xiàng)
本教程的編寫基于 playwright@1.16.3
和 @playwright/test@1.16.3
查看Playwright 的版本更新 頁(yè)面以知曉可能會(huì)影響到的代碼更改。
:::
:::使用第三方測(cè)試運(yùn)行程序的信息
如果您有興趣使用其他測(cè)試運(yùn)行其(例如 Jest 或 Mocha),請(qǐng)查看 Playwright 的 第三方測(cè)試運(yùn)行器 指南。
:::
Playwright通過 _electron.launch
API在開發(fā)模式下啟動(dòng)您的應(yīng)用程序。 要將此 API 指向 Electron 應(yīng)用,可以將路徑傳遞到主進(jìn)程入口點(diǎn)(此處為 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
類的一個(gè)實(shí)例。 這是一個(gè)功能強(qiáng)大的類,可以訪問主進(jìn)程模塊,例如:
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 的主進(jìn)程運(yùn)行,這里的參數(shù)總是
// 主程序代碼中 require('electron') 的返回結(jié)果。
return app.isPackaged
})
console.log(isPackaged) // false(因?yàn)槲覀兲幵陂_發(fā)環(huán)境)
// 關(guān)閉應(yīng)用程序
await electronApp.close()
})
它還可以從 Electron BrowserWindow 實(shí)例創(chuàng)建單獨(dú)的 Page 對(duì)象。 例如,獲取第一個(gè) BrowserWindow 并保存一個(gè)屏幕截圖:
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 測(cè)試運(yùn)行器將所有這些組合到一起,讓我們創(chuàng)建一個(gè)有單個(gè)測(cè)試和斷言的 example.spec.js
測(cè)試文件:
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 的主進(jìn)程運(yùn)行,這里的參數(shù)總是
// 主程序代碼中 require('electron') 的返回結(jié)果。
return app.isPackaged;
});
expect(isPackaged).toBe(false);
// 等待第一個(gè) BrowserWindow 打開
// 然后返回它的 Page 對(duì)象
const window = await electronApp.firstWindow()
await window.screenshot({ path: 'intro.png' })
// 關(guān)閉應(yīng)用程序
await electronApp.close()
});
然后,使用 npx playwright test
運(yùn)行 Playwright 測(cè)試。 您應(yīng)該在您的控制臺(tái)中看到測(cè)試通過,并在您的文件系統(tǒng)上看到一個(gè)屏幕截圖 intro.png
。
? $ npx playwright test
Running 1 test using 1 worker
? example.spec.js:4:1 ? example test (1s)
INFO
PlayWright Test 將自動(dòng)運(yùn)行與正則表達(dá)式
.*(test|spec)\.(js|ts|mjs)
匹配的所有文件。 您可以在 Playwright Test 配置選項(xiàng) 中自定義這個(gè)正則表達(dá)式。
:::延伸閱讀
查看 Playwright完整的 Electron 和 ElectronApplication class API。
:::
當(dāng)然,也可以使用node的內(nèi)建IPC STDIO來(lái)編寫自己的自定義驅(qū)動(dòng)。 自定義測(cè)試驅(qū)動(dòng)程序需要您寫額外的應(yīng)用代碼,但是有較低的開銷,讓您 在您的測(cè)試套裝上顯示自定義方法。
我們將用 Node.js 的 child_process
API 來(lái)創(chuàng)建一個(gè)自定義驅(qū)動(dòng)。 測(cè)試套件將生成 Electron 子進(jìn)程,然后建立一個(gè)簡(jiǎn)單的消息傳遞協(xié)議。
const childProcess = require('child_process')
const electronPath = require('electron')
// 啟動(dòng)子進(jìn)程
const env = { /* ... */ }
const stdio = ['inherit', 'inherit', 'inherit', 'ipc']
const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
// 偵聽?wèi)?yīng)用傳來(lái)的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)聽測(cè)試套件發(fā)送過來(lái)的消息
process.on('message', (msg) => {
// ...
})
// 發(fā)送一條消息到測(cè)試套件
process.send({ my: 'message' })
現(xiàn)在,我們可以使用appProcess
對(duì)象從測(cè)試套件到Electron應(yīng)用進(jìn)行通訊。
為方便起見,您可能希望將 appProcess
包裝在一個(gè)提供更高級(jí)功能的驅(qū)動(dòng)程序?qū)ο笾小?下面是一個(gè)示例。 讓我們從創(chuàng)建一個(gè) TestDriver
類開始:
class TestDriver {
constructor ({ path, args, env }) {
this.rpcCalls = []
// 啟動(dòng)子進(jìn)程
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)
})
}
// 簡(jiǎn)單 RPC 回調(diào)
// 可以使用:driver.rpc('method', 1, 2, 3).then(...)
async rpc (cmd, ...args) {
// 發(fā)送 RPC 請(qǐng)求
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)用代碼中,可以編寫一個(gè)簡(jiǎn)單的處理程序來(lái)接收 RPC 調(diào)用:
const METHODS = {
isReady () {
// 進(jìn)行任何需要的初始化
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)
}
然后,在您的測(cè)試套件中,您可以使用TestDriver
類用你選擇的自動(dòng)化測(cè)試框架。 下面的示例使用 ava
,但其他流行的選擇,如Jest 或者M(jìn)ocha 也可以:
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/
更多建議: