Publicado · en mejora
Guía de Electron · 5/6
Por ahora, este capítulo solo está disponible en inglés.
An Electron app has a main process, preload scripts, and renderers working together, so no single kind of test covers everything. This chapter shows how to unit test logic that does not depend on Electron, and how to write end-to-end (E2E) tests with Playwright that launch the real app and drive its windows.
| Kind | Target | Tools |
|---|---|---|
| Unit tests | Pure functions, data transforms, validation | Vitest, Jest |
| Module tests | Main process code that calls Electron APIs | Vitest with a mocked electron |
| Renderer component tests | React or Vue components | Your UI framework's testing tools |
| E2E tests | The whole running app | Playwright, WebdriverIO |
The first step toward a testable app is to separate business logic from Electron APIs. Rather than writing long logic inside IPC handlers, extract it into pure functions and have the handlers simply call them.
Take a function that maintains a recent-files list. It knows nothing about Electron, so plain Node.js tests can check it.
// src/lib/recent-files.js
export function addRecentFile(list, file, max = 10) {
return [file, ...list.filter((item) => item !== file)].slice(0, max)
}npm install --save-dev vitest
npx vitest// tests/recent-files.test.js
import { describe, it, expect } from 'vitest'
import { addRecentFile } from '../src/lib/recent-files'
describe('addRecentFile', () => {
it('puts a new file first', () => {
expect(addRecentFile(['a.txt'], 'b.txt')).toEqual(['b.txt', 'a.txt'])
})
it('moves an existing file to the front', () => {
expect(addRecentFile(['a.txt', 'b.txt'], 'b.txt')).toEqual(['b.txt', 'a.txt'])
})
it('never exceeds the maximum length', () => {
expect(addRecentFile(['a', 'b', 'c'], 'd', 3)).toEqual(['d', 'a', 'b'])
})
})In plain Node.js, require('electron') returns the path to the Electron executable as a string, not the API. To load main process code in a Node.js test runner, replace the electron module with a mock.
// src/main/settings-path.js
import { app } from 'electron'
import path from 'node:path'
export const settingsPath = () => path.join(app.getPath('userData'), 'settings.json')// tests/settings-path.test.ts
import { vi, it, expect } from 'vitest'
import path from 'node:path'
vi.mock('electron', () => ({
app: { getPath: vi.fn(() => '/tmp/test-user-data') },
}))
import { settingsPath } from '../src/main/settings-path'
it('points at settings.json under userData', () => {
expect(settingsPath()).toBe(path.join('/tmp/test-user-data', 'settings.json'))
})vi.mock is hoisted to the top of the file, so it takes effect before the imports below it. Note that Vitest mocks apply to modules that Vitest itself transforms and loads, not to CommonJS modules that Node.js loads directly through require. That is why the main process code above uses ES module syntax, which comes naturally in projects that bundle the main process, such as the Forge Vite and webpack templates.
Playwright has experimental support for launching and controlling Electron apps. _electron.launch() starts your app and returns an ElectronApplication; from it you get windows as regular Page objects and drive them like any web test. evaluate() runs code inside the main process.
npm install --save-dev @playwright/test
npx playwright test// tests/app.spec.ts
import { test, expect, _electron as electron } from '@playwright/test'
test('opens the main window', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
// Runs in the main process
const isPackaged = await electronApp.evaluate(async ({ app }) => app.isPackaged)
expect(isPackaged).toBe(false)
const window = await electronApp.firstWindow()
await expect(window).toHaveTitle('Hello Electron')
await expect(window.locator('h1')).toHaveText('Hello from Electron')
await electronApp.close()
})Native dialogs cannot be automated, so swap out the dialog functions in the main process from inside the test.
test('opens a file', async () => {
const electronApp = await electron.launch({ args: ['main.js'] })
await electronApp.evaluate(({ dialog }, filePath) => {
dialog.showOpenDialog = async () => ({ canceled: false, filePaths: [filePath] })
}, '/tmp/sample.txt')
const window = await electronApp.firstWindow()
await window.getByRole('button', { name: 'Open' }).click()
await expect(window.locator('#file')).toHaveText('/tmp/sample.txt')
await electronApp.close()
})To test a packaged build, pass the path of the built executable as electron.launch({ executablePath: '...' }).
Linux CI machines have no display, so Electron windows need a virtual one.
# Ubuntu example
sudo apt-get install -y xvfb
xvfb-run --auto-servernum npx playwright testWebdriverIO also supports Electron E2E testing through its Electron service (wdio-electron-service). Spectron, which older guides mention, is no longer maintained and should not be used for new projects.
electron module when loading main process code in a Node.js test runner._electron.launch() to start the real app, drive its windows, and inspect the main process with evaluate().xvfb-run on Linux CI.
0 comentarios
Iniciar sesión · Inicia sesión para dejar un comentario.
Sé el primero en comentar.