desktop-testing-electron
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseElectron Testing Patterns
Electron测试模式
Quick Guide: Use Playwright'sfor E2E tests -- it controls the full app via CDP. Unit test main process code (IPC handlers, business logic) with your test runner by mocking the_electron.launch()module. Test preload scripts by mockingelectronandcontextBridge. Spectron is dead since Electron 24 -- Playwright and WebDriverIO are the replacements. Run Electron tests on headless Linux CI withipcRendereror thexvfb-runwrapper.xvfb-maybe
<critical_requirements>
快速指南: 使用Playwright的进行E2E测试——它通过CDP控制整个应用。通过模拟_electron.launch()模块,使用测试运行器对主进程代码(IPC处理器、业务逻辑)进行单元测试。通过模拟electron和contextBridge测试预加载脚本。自Electron 24起Spectron已停止维护——Playwright和WebDriverIO是其替代方案。使用ipcRenderer或xvfb-run包装器在无头Linux CI上运行Electron测试。xvfb-maybe
<critical_requirements>
CRITICAL: Before Using This Skill
关键要求:使用本技能之前
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST in test teardown -- leaked Electron processes break CI and consume resources)
await electronApp.close()(You MUST mock the module in unit tests -- Electron APIs are only available inside the Electron runtime)
electron(You MUST use or for headless Linux CI -- Electron requires a display server)
xvfb-runxvfb-maybe(You MUST stub native dialogs in E2E tests -- / block the process and cannot be interacted with by Playwright)
showOpenDialogshowSaveDialog</critical_requirements>
Auto-detection: Electron testing, _electron.launch, electronApp, electronApplication, firstWindow, Playwright Electron, electron-mock-ipc, electron-playwright-helpers, stubDialog, xvfb, xvfb-run, xvfb-maybe, Spectron migration, ipcMain.handle test, ipcRenderer mock, contextBridge mock, BrowserWindow mock, Electron E2E, Electron unit test
When to use:
- Writing E2E tests for an Electron application with Playwright
- Unit testing main process code (IPC handlers, lifecycle logic)
- Mocking Electron modules (,
dialog,BrowserWindow,ipcMain)ipcRenderer - Testing preload scripts and APIs
contextBridge - Setting up headless CI for Electron tests (Linux xvfb)
- Migrating from Spectron to Playwright
- Screenshot/visual regression testing of Electron windows
- Testing auto-update flows
When NOT to use:
- Testing renderer UI in isolation (use your web testing skill -- renderer is standard web)
- Writing tests unrelated to Electron-specific APIs
- Performance profiling or benchmarking Electron apps
- Packaging or distributing Electron apps (use the Electron framework skill)
Key patterns covered:
- Playwright E2E: ,
_electron.launch(),firstWindow(), assertionsevaluate() - Main process unit testing with mocked Electron modules
- IPC handler testing (/
ipcMain.handle)ipcRenderer.invoke - Preload script testing (mock )
contextBridge.exposeInMainWorld - Dialog and menu stubbing in E2E tests
- Auto-updater test strategies
- Headless CI configuration (xvfb, GitHub Actions)
- Screenshot and visual regression testing
- Spectron migration path
<philosophy>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名法、命名导出、导入顺序、、命名常量)import type
(必须在测试清理阶段——泄漏的Electron进程会破坏CI并消耗资源)
await electronApp.close()(必须在单元测试中模拟模块——Electron API仅在Electron运行时内可用)
electron(必须为无头Linux CI使用或——Electron需要显示服务器)
xvfb-runxvfb-maybe(必须在E2E测试中存根原生对话框——/会阻塞进程,且无法被Playwright交互)
showOpenDialogshowSaveDialog</critical_requirements>
自动检测: Electron测试、_electron.launch、electronApp、electronApplication、firstWindow、Playwright Electron、electron-mock-ipc、electron-playwright-helpers、stubDialog、xvfb、xvfb-run、xvfb-maybe、Spectron迁移、ipcMain.handle测试、ipcRenderer模拟、contextBridge模拟、BrowserWindow模拟、Electron E2E、Electron单元测试
适用场景:
- 使用Playwright为Electron应用编写E2E测试
- 对主进程代码(IPC处理器、生命周期逻辑)进行单元测试
- 模拟Electron模块(、
dialog、BrowserWindow、ipcMain)ipcRenderer - 测试预加载脚本和API
contextBridge - 为Electron测试配置无头CI(Linux xvfb)
- 从Spectron迁移到Playwright
- Electron窗口的截图/视觉回归测试
- 测试自动更新流程
不适用场景:
- 单独测试渲染器UI(使用你的Web测试技能——渲染器是标准Web环境)
- 编写与Electron特定API无关的测试
- Electron应用的性能分析或基准测试
- 打包或分发Electron应用(使用Electron框架技能)
涵盖的核心模式:
- Playwright E2E:、
_electron.launch()、firstWindow()、断言evaluate() - 使用模拟Electron模块进行主进程单元测试
- IPC处理器测试(/
ipcMain.handle)ipcRenderer.invoke - 预加载脚本测试(模拟)
contextBridge.exposeInMainWorld - E2E测试中的对话框和菜单存根
- 自动更新测试策略
- 无头CI配置(xvfb、GitHub Actions)
- 截图和视觉回归测试
- Spectron迁移路径
<philosophy>
Philosophy
理念
Electron testing splits along the same boundaries as the Electron process model:
- E2E tests launch the full application with Playwright and exercise the complete flow -- main process, preload, renderer, and IPC together. These are slow but high-confidence.
- Main process unit tests mock the module and test IPC handlers, lifecycle logic, and business logic in isolation. These are fast and catch logic bugs early.
electron - Renderer tests are standard web tests -- the renderer is Chromium. Use your existing web testing approach.
Guiding principle: Test main process logic with unit tests, test integration through IPC with E2E, and test renderer UI with standard web tools. Don't try to unit test IPC communication itself -- the framework handles message passing. Test that your handlers produce the correct results given inputs.
When to use E2E (Playwright):
- Full user workflows (open file, edit, save)
- IPC round-trips that span main and renderer
- Window management (multi-window, modals, frameless)
- Visual regression / screenshot comparison
- Auto-update UI flow
When to use unit tests:
- IPC handler logic (validate input, produce output)
- Main process business logic (file operations, data processing)
- Preload API shape (correct channels exposed)
- Configuration and startup logic
<patterns>
Electron测试与Electron进程模型遵循相同的边界划分:
- E2E测试使用Playwright启动完整应用,测试完整流程——主进程、预加载、渲染器和IPC协同工作。这类测试速度较慢,但可信度高。
- 主进程单元测试模拟模块,单独测试IPC处理器、生命周期逻辑和业务逻辑。这类测试速度快,能尽早发现逻辑错误。
electron - 渲染器测试是标准Web测试——渲染器基于Chromium。使用你现有的Web测试方法即可。
指导原则: 使用单元测试测试主进程逻辑,使用E2E测试测试IPC集成,使用标准Web工具测试渲染器UI。不要尝试单元测试IPC通信本身——框架会处理消息传递。测试你的处理器在给定输入下能否产生正确结果。
何时使用E2E(Playwright):
- 完整用户工作流(打开文件、编辑、保存)
- 跨越主进程和渲染器的IPC往返
- 窗口管理(多窗口、模态框、无边框窗口)
- 视觉回归/截图对比
- 自动更新UI流程
何时使用单元测试:
- IPC处理器逻辑(验证输入、生成输出)
- 主进程业务逻辑(文件操作、数据处理)
- 预加载API形态(正确暴露通道)
- 配置和启动逻辑
<patterns>
Core Patterns
核心模式
Pattern 1: Playwright E2E -- Launch and Basic Assertions
模式1:Playwright E2E——启动与基础断言
Launch the Electron app, get the first window, and run assertions. Always close in teardown.
typescript
import { test, expect, _electron as electron } from "@playwright/test";
import type { ElectronApplication, Page } from "@playwright/test";
let electronApp: ElectronApplication;
let window: Page;
test.beforeEach(async () => {
electronApp = await electron.launch({ args: ["dist/main.js"] });
window = await electronApp.firstWindow();
});
test.afterEach(async () => {
await electronApp.close();
});
test("shows main window with title", async () => {
const title = await window.title();
expect(title).toBe("My App");
await expect(window.locator("h1")).toHaveText("Welcome");
});Why good: guarantees cleanup, waits for the window to load, standard Playwright assertions work on the Page object
afterEachfirstWindow()See examples/core.md for evaluate(), multi-window, and environment variable patterns.
启动Electron应用,获取第一个窗口并运行断言。务必在清理阶段关闭应用。
typescript
import { test, expect, _electron as electron } from "@playwright/test";
import type { ElectronApplication, Page } from "@playwright/test";
let electronApp: ElectronApplication;
let window: Page;
test.beforeEach(async () => {
electronApp = await electron.launch({ args: ["dist/main.js"] });
window = await electronApp.firstWindow();
});
test.afterEach(async () => {
await electronApp.close();
});
test("shows main window with title", async () => {
const title = await window.title();
expect(title).toBe("My App");
await expect(window.locator("h1")).toHaveText("Welcome");
});优势: 保证清理,等待窗口加载,标准Playwright断言可用于Page对象
afterEachfirstWindow()查看examples/core.md获取evaluate()、多窗口和环境变量模式的示例。
Pattern 2: Main Process Evaluation
模式2:主进程代码执行
Use to execute code in the main process context and access Electron APIs.
electronApp.evaluate()typescript
test("returns correct app version", async () => {
const version = await electronApp.evaluate(async ({ app }) => {
return app.getVersion();
});
expect(version).toMatch(/^\d+\.\d+\.\d+$/);
});
test("app path is set correctly", async () => {
const appPath = await electronApp.evaluate(async ({ app }) => {
return app.getAppPath();
});
expect(appPath).toContain("dist");
});Why good: receives the Electron object (containing , , etc.) as its first argument, runs in the real main process, returns serializable values
evaluate()moduleappBrowserWindowSee examples/core.md for browserWindow handle access and process-level assertions.
使用在主进程上下文中执行代码并访问Electron API。
electronApp.evaluate()typescript
test("returns correct app version", async () => {
const version = await electronApp.evaluate(async ({ app }) => {
return app.getVersion();
});
expect(version).toMatch(/^\d+\.\d+\.\d+$/);
});
test("app path is set correctly", async () => {
const appPath = await electronApp.evaluate(async ({ app }) => {
return app.getAppPath();
});
expect(appPath).toContain("dist");
});优势: 接收Electron 对象(包含、等)作为第一个参数,在真实主进程中运行,返回可序列化的值
evaluate()moduleappBrowserWindow查看examples/core.md获取browserWindow句柄访问和进程级断言的示例。
Pattern 3: Unit Testing IPC Handlers
模式3:单元测试IPC处理器
Extract handler logic into pure functions, then unit test those functions. Mock the module so it doesn't fail outside the Electron runtime.
electrontypescript
// main/handlers/file-handler.ts -- extracted pure logic
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
const ALLOWED_EXTENSIONS = [".txt", ".md", ".json"];
export async function handleReadFile(
filePath: string,
): Promise<{ success: boolean; content?: string; error?: string }> {
const ext = path.extname(filePath);
if (!ALLOWED_EXTENSIONS.includes(ext)) {
return { success: false, error: `Unsupported extension: ${ext}` };
}
const content = await readFile(filePath, "utf-8");
return { success: true, content };
}typescript
// main/handlers/file-handler.test.ts
import { describe, it, expect } from "vitest";
import { handleReadFile } from "./file-handler.js";
describe("handleReadFile", () => {
it("rejects unsupported extensions", async () => {
const result = await handleReadFile("/tmp/file.exe");
expect(result).toStrictEqual({
success: false,
error: "Unsupported extension: .exe",
});
});
});Why good: Handler logic is a pure function with no Electron dependency, testable with any test runner, no mocking required
See examples/core.md for the full IPC registration pattern and wiring handlers to .
ipcMain.handle将处理器逻辑提取为纯函数,然后对这些函数进行单元测试。模拟模块,使其在Electron运行时外不会报错。
electrontypescript
// main/handlers/file-handler.ts -- 提取的纯逻辑
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
const ALLOWED_EXTENSIONS = [".txt", ".md", ".json"];
export async function handleReadFile(
filePath: string,
): Promise<{ success: boolean; content?: string; error?: string }> {
const ext = path.extname(filePath);
if (!ALLOWED_EXTENSIONS.includes(ext)) {
return { success: false, error: `Unsupported extension: ${ext}` };
}
const content = await readFile(filePath, "utf-8");
return { success: true, content };
}typescript
// main/handlers/file-handler.test.ts
import { describe, it, expect } from "vitest";
import { handleReadFile } from "./file-handler.js";
describe("handleReadFile", () => {
it("rejects unsupported extensions", async () => {
const result = await handleReadFile("/tmp/file.exe");
expect(result).toStrictEqual({
success: false,
error: "Unsupported extension: .exe",
});
});
});优势: 处理器逻辑是无Electron依赖的纯函数,可使用任何测试运行器进行测试,无需模拟
查看examples/core.md获取完整的IPC注册模式以及将处理器连接到的示例。
ipcMain.handlePattern 4: Mocking the Electron Module
模式4:模拟Electron模块
When main process code imports directly from , mock the module in your test runner so tests don't fail outside the Electron runtime.
electrontypescript
// test setup file -- mock the electron module globally
vi.mock("electron", () => ({
app: {
getPath: vi.fn().mockReturnValue("/tmp/mock-app-data"),
getVersion: vi.fn().mockReturnValue("1.0.0"),
whenReady: vi.fn().mockResolvedValue(undefined),
},
BrowserWindow: vi.fn().mockImplementation(() => ({
loadFile: vi.fn(),
webContents: { send: vi.fn() },
on: vi.fn(),
})),
ipcMain: {
handle: vi.fn(),
on: vi.fn(),
removeHandler: vi.fn(),
},
dialog: {
showOpenDialog: vi.fn(),
showSaveDialog: vi.fn(),
showMessageBox: vi.fn(),
},
}));Why good: Provides minimal stubs for common Electron APIs, tests run in Node.js without Electron runtime, each mock returns sensible defaults
See examples/mocking.md for per-test overrides and more granular mock patterns.
当主进程代码直接从导入时,在测试运行器中模拟该模块,使测试在Electron运行时外不会失败。
electrontypescript
// 测试设置文件——全局模拟electron模块
vi.mock("electron", () => ({
app: {
getPath: vi.fn().mockReturnValue("/tmp/mock-app-data"),
getVersion: vi.fn().mockReturnValue("1.0.0"),
whenReady: vi.fn().mockResolvedValue(undefined),
},
BrowserWindow: vi.fn().mockImplementation(() => ({
loadFile: vi.fn(),
webContents: { send: vi.fn() },
on: vi.fn(),
})),
ipcMain: {
handle: vi.fn(),
on: vi.fn(),
removeHandler: vi.fn(),
},
dialog: {
showOpenDialog: vi.fn(),
showSaveDialog: vi.fn(),
showMessageBox: vi.fn(),
},
}));优势: 为常见Electron API提供最小存根,测试可在Node.js中运行而无需Electron运行时,每个模拟返回合理的默认值
查看examples/mocking.md获取单测试覆盖和更细粒度的模拟模式。
Pattern 5: Dialog Stubbing in E2E Tests
模式5:E2E测试中的对话框存根
Native dialogs cannot be interacted with by Playwright. Stub them via before triggering the dialog.
evaluate()typescript
test("opens a file via dialog", async () => {
// Stub the dialog before the UI triggers it
await electronApp.evaluate(async ({ dialog }) => {
dialog.showOpenDialog = async () => ({
canceled: false,
filePaths: ["/tmp/test-file.txt"],
});
});
// Click the button that triggers showOpenDialog
await window.click('button[data-testid="open-file"]');
await expect(window.locator('[data-testid="file-name"]')).toHaveText(
"test-file.txt",
);
});Why good: Stubs the dialog module in the running main process, returns controlled data, test can verify the downstream UI effect
See examples/e2e-patterns.md for save dialog, message box, and library patterns.
electron-playwright-helpersPlaywright无法与原生对话框交互。在触发对话框前,通过对其进行存根。
evaluate()typescript
test("opens a file via dialog", async () => {
// 在UI触发对话框前存根
await electronApp.evaluate(async ({ dialog }) => {
dialog.showOpenDialog = async () => ({
canceled: false,
filePaths: ["/tmp/test-file.txt"],
});
});
// 点击触发showOpenDialog的按钮
await window.click('button[data-testid="open-file"]');
await expect(window.locator('[data-testid="file-name"]')).toHaveText(
"test-file.txt",
);
});优势: 在运行中的主进程中存根对话框模块,返回受控数据,测试可验证下游UI效果
查看examples/e2e-patterns.md获取保存对话框、消息框和库模式的示例。
electron-playwright-helpersPattern 6: Headless CI Configuration
模式6:无头CI配置
Electron requires a display server. On Linux CI, use or the cross-platform wrapper.
xvfb-runxvfb-maybeyaml
undefinedElectron需要显示服务器。在Linux CI上,使用或跨平台的包装器。
xvfb-runxvfb-maybeyaml
undefinedGitHub Actions example
GitHub Actions示例
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: xvfb-run --auto-servernum -- npx playwright test
**Key point:** `xvfb-run --auto-servernum` creates a virtual display and sets `$DISPLAY` automatically. On macOS/Windows runners, `xvfb-run` is not needed -- Electron has native display access. `xvfb-maybe` wraps this cross-platform: it applies xvfb on Linux and does nothing elsewhere.
See [examples/e2e-patterns.md](examples/e2e-patterns.md) for the full CI matrix and `xvfb-maybe` npm script pattern.
---jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: xvfb-run --auto-servernum -- npx playwright test
**关键点:** `xvfb-run --auto-servernum`创建虚拟显示并自动设置`$DISPLAY`。在macOS/Windows运行器上,无需`xvfb-run`——Electron具有原生显示访问权限。`xvfb-maybe`实现跨平台支持:在Linux上应用xvfb,在其他平台上不执行任何操作。
查看[examples/e2e-patterns.md](examples/e2e-patterns.md)获取完整CI矩阵和`xvfb-maybe` npm脚本模式。
---Pattern 7: Screenshot and Visual Regression Testing
模式7:截图与视觉回归测试
Playwright's works with Electron windows for visual regression testing.
toHaveScreenshot()typescript
test("main window matches screenshot", async () => {
// Wait for the UI to stabilize
await window.waitForLoadState("domcontentloaded");
await expect(window).toHaveScreenshot("main-window.png", {
maxDiffPixelRatio: 0.01,
});
});
test("dialog state matches screenshot", async () => {
await window.click('button[data-testid="open-settings"]');
await window.waitForSelector('[data-testid="settings-panel"]');
await expect(
window.locator('[data-testid="settings-panel"]'),
).toHaveScreenshot("settings-panel.png");
});Key point: Run screenshot tests on a single OS in CI (Linux with xvfb) for consistent baselines. Cross-OS font rendering differences cause false positives. Use or for tolerance.
</patterns>
maxDiffPixelRatiomaxDiffPixels<decision_framework>
Playwright的可用于Electron窗口的视觉回归测试。
toHaveScreenshot()typescript
test("main window matches screenshot", async () => {
// 等待UI稳定
await window.waitForLoadState("domcontentloaded");
await expect(window).toHaveScreenshot("main-window.png", {
maxDiffPixelRatio: 0.01,
});
});
test("dialog state matches screenshot", async () => {
await window.click('button[data-testid="open-settings"]');
await window.waitForSelector('[data-testid="settings-panel"]');
await expect(
window.locator('[data-testid="settings-panel"]'),
).toHaveScreenshot("settings-panel.png");
});关键点: 在CI中仅在单个操作系统(带xvfb的Linux)上运行截图测试以获得一致的基准。跨操作系统的字体渲染差异会导致误报。使用或设置容差。
</patterns>
maxDiffPixelRatiomaxDiffPixels<decision_framework>
Decision Framework
决策框架
What to Test Where
测试场景选择
What are you testing?
+-- Full user workflow (open, edit, save, multi-window)?
| +-- Playwright E2E (launch real app)
+-- Main process handler logic (validate input, transform data)?
| +-- Unit test with mocked electron module
+-- Preload script API shape?
| +-- Unit test with mocked contextBridge/ipcRenderer
+-- Renderer UI components?
| +-- Standard web testing tools (not Electron-specific)
+-- IPC round-trip (main <-> renderer)?
| +-- Playwright E2E (tests the real channel)
+-- Dialog/menu interactions?
| +-- Playwright E2E with stubbed dialogs
+-- Visual appearance?
| +-- Playwright screenshot comparison
+-- Auto-update flow?
+-- Mock event emission in unit tests + real staging server for integration你要测试什么?
+-- 完整用户工作流(打开、编辑、保存、多窗口)?
| +-- Playwright E2E(启动真实应用)
+-- 主进程处理器逻辑(验证输入、转换数据)?
| +-- 使用模拟electron模块进行单元测试
+-- 预加载脚本API形态?
| +-- 使用模拟contextBridge/ipcRenderer进行单元测试
+-- 渲染器UI组件?
| +-- 标准Web测试工具(非Electron特定)
+-- IPC往返(主进程 <-> 渲染器)?
| +-- Playwright E2E(测试真实通道)
+-- 对话框/菜单交互?
| +-- 带存根对话框的Playwright E2E
+-- 视觉外观?
| +-- Playwright截图对比
+-- 自动更新流程?
+-- 单元测试中模拟事件触发 + 真实 staging 服务器进行集成测试Mocking Decision
模拟决策
Does your code import from "electron"?
+-- YES: Is the logic separable from Electron APIs?
| +-- YES --> Extract pure function, test without mocking
| +-- NO --> Mock the electron module (use your test runner's module mocking)
+-- NO: Standard Node.js code
+-- Test normally, no special setup needed</decision_framework>
Detailed resources:
- examples/core.md - Playwright launch, evaluate, firstWindow, IPC handler unit testing, preload testing
- examples/e2e-patterns.md - Dialog stubbing, CI setup, screenshot testing, auto-update testing, multi-window
- examples/mocking.md - Mocking electron module, ipcMain/ipcRenderer, BrowserWindow, dialog, contextBridge
- reference.md - Playwright Electron API quick reference, Spectron migration, test runner comparison
<red_flags>
你的代码是否从"electron"导入?
+-- 是:逻辑是否可与Electron API分离?
| +-- 是 --> 提取纯函数,无需模拟即可测试
| +-- 否 --> 模拟electron模块(使用测试运行器的模块模拟功能)
+-- 否:标准Node.js代码
+-- 正常测试,无需特殊设置</decision_framework>
详细资源:
- examples/core.md - Playwright启动、evaluate、firstWindow、IPC处理器单元测试、预加载测试
- examples/e2e-patterns.md - 对话框存根、CI设置、截图测试、自动更新测试、多窗口
- examples/mocking.md - 模拟electron模块、ipcMain/ipcRenderer、BrowserWindow、dialog、contextBridge
- reference.md - Playwright Electron API快速参考、Spectron迁移、测试运行器对比
<red_flags>
RED FLAGS
警告信号
Critical Issues:
- Not closing in test teardown -- leaked processes accumulate, break CI, and cause port conflicts
electronApp - Running Electron E2E tests on Linux CI without xvfb -- tests fail immediately with "no display" errors
- Testing IPC communication logic itself rather than handler outcomes -- the framework handles message passing, test your business logic
- Using Spectron for Electron 24+ -- Spectron is unmaintained and incompatible with modern Electron
Architecture Issues:
- Putting all test logic in E2E tests when unit tests would suffice -- E2E is slow, unit test handler logic separately
- Mocking in E2E tests -- E2E tests use the real IPC channel; mock only native OS APIs (dialogs, menus)
ipcRenderer - Testing renderer components through Electron launch -- renderer is standard Chromium, test with web tools for speed
- Coupling handler logic directly to registration -- extract handlers to pure functions for testability
ipcMain.handle
Common Mistakes:
- Forgetting returns a
await electronApp.firstWindow(), not aPage-- use Playwright page API, not Electron window APIBrowserWindow - Assuming can return non-serializable values (functions, DOM nodes) -- it serializes via JSON
evaluate() - Hardcoding file paths in E2E dialog stubs -- use or test fixtures
path.join(os.tmpdir(), ...) - Not waiting for window load before assertions -- use or
waitForLoadState()before checking contentwaitForSelector()
Gotchas & Edge Cases:
- uses the
_electron.launch()binary fromelectronby default -- setnode_modules/.bin/if your app bundles a different Electron versionexecutablePath - Playwright Electron support is marked "experimental" -- API may change between major Playwright versions
- receives the Electron module object (not
electronApp.evaluate()) as its first argument -- destructurerequire("electron"),{ app }, etc.{ dialog } - Screenshot baselines differ across OSes due to font rendering -- pin to one OS in CI or use per-OS baselines
- handle from
BrowserWindowreturns aelectronApp.browserWindow(page), not a direct object -- call methods viaJSHandleon the handleevaluate - can only have one handler per channel -- calling
ipcMain.handletwice on the same channel throws; usehandlefirst in testsremoveHandler
</red_flags>
<critical_reminders>
关键问题:
- 测试清理阶段未关闭——泄漏的进程会累积,破坏CI并导致端口冲突
electronApp - 在Linux CI上运行Electron E2E测试时未使用xvfb——测试会立即因“无显示”错误失败
- 测试IPC通信逻辑本身而非处理器结果——框架会处理消息传递,测试你的业务逻辑即可
- 为Electron 24+使用Spectron——Spectron已停止维护,与现代Electron不兼容
架构问题:
- 当单元测试足够时,将所有测试逻辑放在E2E测试中——E2E速度慢,应单独测试处理器逻辑
- 在E2E测试中模拟——E2E测试使用真实IPC通道;仅模拟原生OS API(对话框、菜单)
ipcRenderer - 通过Electron启动测试渲染器组件——渲染器是标准Chromium,使用Web工具测试以提高速度
- 将处理器逻辑直接耦合到注册——将处理器提取为纯函数以提高可测试性
ipcMain.handle
常见错误:
- 忘记返回
await electronApp.firstWindow()而非Page——使用Playwright页面API,而非Electron窗口APIBrowserWindow - 假设可以返回非可序列化值(函数、DOM节点)——它通过JSON序列化
evaluate() - 在E2E对话框存根中硬编码文件路径——使用或测试夹具
path.join(os.tmpdir(), ...) - 断言前未等待窗口加载——在检查内容前使用或
waitForLoadState()waitForSelector()
注意事项与边缘情况:
- 默认使用
_electron.launch()中的node_modules/.bin/二进制文件——如果你的应用捆绑了不同的Electron版本,请设置electronexecutablePath - Playwright Electron支持标记为“实验性”——API可能在Playwright主要版本之间变化
- 接收Electron模块对象(而非
electronApp.evaluate())作为第一个参数——解构require("electron")、{ app }等{ dialog } - 由于字体渲染差异,截图基准在不同操作系统上不同——在CI中固定为一个操作系统或使用每个操作系统的基准
- 从获取的
electronApp.browserWindow(page)句柄返回BrowserWindow,而非直接对象——通过句柄上的JSHandle调用方法evaluate - 每个通道只能有一个处理器——在同一通道上两次调用
ipcMain.handle会抛出错误;测试中先使用handleremoveHandler
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
关键提醒
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST in test teardown -- leaked Electron processes break CI and consume resources)
await electronApp.close()(You MUST mock the module in unit tests -- Electron APIs are only available inside the Electron runtime)
electron(You MUST use or for headless Linux CI -- Electron requires a display server)
xvfb-runxvfb-maybe(You MUST stub native dialogs in E2E tests -- / block the process and cannot be interacted with by Playwright)
showOpenDialogshowSaveDialogFailure to follow these rules will cause leaked processes, CI failures, and untestable dialog interactions.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名法、命名导出、导入顺序、、命名常量)import type
(必须在测试清理阶段——泄漏的Electron进程会破坏CI并消耗资源)
await electronApp.close()(必须在单元测试中模拟模块——Electron API仅在Electron运行时内可用)
electron(必须为无头Linux CI使用或——Electron需要显示服务器)
xvfb-runxvfb-maybe(必须在E2E测试中存根原生对话框——/会阻塞进程,且无法被Playwright交互)
showOpenDialogshowSaveDialog不遵循这些规则会导致进程泄漏、CI失败和无法测试的对话框交互。
</critical_reminders>