desktop-testing-electron

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Electron Testing Patterns

Electron测试模式

Quick Guide: Use Playwright's
_electron.launch()
for 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
module. Test preload scripts by mocking
contextBridge
and
ipcRenderer
. Spectron is dead since Electron 24 -- Playwright and WebDriverIO are the replacements. Run Electron tests on headless Linux CI with
xvfb-run
or the
xvfb-maybe
wrapper.

<critical_requirements>
快速指南: 使用Playwright的
_electron.launch()
进行E2E测试——它通过CDP控制整个应用。通过模拟
electron
模块,使用测试运行器对主进程代码(IPC处理器、业务逻辑)进行单元测试。通过模拟
contextBridge
ipcRenderer
测试预加载脚本。自Electron 24起Spectron已停止维护——Playwright和WebDriverIO是其替代方案。使用
xvfb-run
xvfb-maybe
包装器在无头Linux CI上运行Electron测试。

<critical_requirements>

CRITICAL: Before Using This Skill

关键要求:使用本技能之前

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST
await electronApp.close()
in test teardown -- leaked Electron processes break CI and consume resources)
(You MUST mock the
electron
module in unit tests -- Electron APIs are only available inside the Electron runtime)
(You MUST use
xvfb-run
or
xvfb-maybe
for headless Linux CI -- Electron requires a display server)
(You MUST stub native dialogs in E2E tests --
showOpenDialog
/
showSaveDialog
block the process and cannot be interacted with by Playwright)
</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
    contextBridge
    APIs
  • 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()
    ,
    evaluate()
    , assertions
  • 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
、命名常量)
(必须在测试清理阶段
await electronApp.close()
——泄漏的Electron进程会破坏CI并消耗资源)
(必须在单元测试中模拟
electron
模块——Electron API仅在Electron运行时内可用)
(必须为无头Linux CI使用
xvfb-run
xvfb-maybe
——Electron需要显示服务器)
(必须在E2E测试中存根原生对话框——
showOpenDialog
/
showSaveDialog
会阻塞进程,且无法被Playwright交互)
</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
  • 测试预加载脚本和
    contextBridge
    API
  • 为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:
  1. 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.
  2. Main process unit tests mock the
    electron
    module and test IPC handlers, lifecycle logic, and business logic in isolation. These are fast and catch logic bugs early.
  3. 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
</philosophy>
<patterns>
Electron测试与Electron进程模型遵循相同的边界划分:
  1. E2E测试使用Playwright启动完整应用,测试完整流程——主进程、预加载、渲染器和IPC协同工作。这类测试速度较慢,但可信度高。
  2. 主进程单元测试模拟
    electron
    模块,单独测试IPC处理器、生命周期逻辑和业务逻辑。这类测试速度快,能尽早发现逻辑错误。
  3. 渲染器测试是标准Web测试——渲染器基于Chromium。使用你现有的Web测试方法即可。
指导原则: 使用单元测试测试主进程逻辑,使用E2E测试测试IPC集成,使用标准Web工具测试渲染器UI。不要尝试单元测试IPC通信本身——框架会处理消息传递。测试你的处理器在给定输入下能否产生正确结果。
何时使用E2E(Playwright):
  • 完整用户工作流(打开文件、编辑、保存)
  • 跨越主进程和渲染器的IPC往返
  • 窗口管理(多窗口、模态框、无边框窗口)
  • 视觉回归/截图对比
  • 自动更新UI流程
何时使用单元测试:
  • IPC处理器逻辑(验证输入、生成输出)
  • 主进程业务逻辑(文件操作、数据处理)
  • 预加载API形态(正确暴露通道)
  • 配置和启动逻辑
</philosophy>
<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:
afterEach
guarantees cleanup,
firstWindow()
waits for the window to load, standard Playwright assertions work on the Page object
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");
});
优势:
afterEach
保证清理,
firstWindow()
等待窗口加载,标准Playwright断言可用于Page对象
查看examples/core.md获取evaluate()、多窗口和环境变量模式的示例。

Pattern 2: Main Process Evaluation

模式2:主进程代码执行

Use
electronApp.evaluate()
to execute code in the main process context and access Electron APIs.
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:
evaluate()
receives the Electron
module
object (containing
app
,
BrowserWindow
, etc.) as its first argument, runs in the real main process, returns serializable values
See examples/core.md for browserWindow handle access and process-level assertions.

使用
electronApp.evaluate()
在主进程上下文中执行代码并访问Electron API。
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");
});
优势:
evaluate()
接收Electron
module
对象(包含
app
BrowserWindow
等)作为第一个参数,在真实主进程中运行,返回可序列化的值
查看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
electron
module so it doesn't fail outside the Electron runtime.
typescript
// 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
模块,使其在Electron运行时外不会报错。
typescript
// 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.handle
的示例。

Pattern 4: Mocking the Electron Module

模式4:模拟Electron模块

When main process code imports directly from
electron
, mock the module in your test runner so tests don't fail outside the Electron runtime.
typescript
// 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
导入时,在测试运行器中模拟该模块,使测试在Electron运行时外不会失败。
typescript
// 测试设置文件——全局模拟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
evaluate()
before triggering the dialog.
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
electron-playwright-helpers
library patterns.

Playwright无法与原生对话框交互。在触发对话框前,通过
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-helpers
库模式的示例。

Pattern 6: Headless CI Configuration

模式6:无头CI配置

Electron requires a display server. On Linux CI, use
xvfb-run
or the cross-platform
xvfb-maybe
wrapper.
yaml
undefined
Electron需要显示服务器。在Linux CI上,使用
xvfb-run
或跨平台的
xvfb-maybe
包装器。
yaml
undefined

GitHub 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
toHaveScreenshot()
works with Electron windows for visual regression testing.
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
maxDiffPixelRatio
or
maxDiffPixels
for tolerance.
</patterns>
<decision_framework>
Playwright的
toHaveScreenshot()
可用于Electron窗口的视觉回归测试。
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)上运行截图测试以获得一致的基准。跨操作系统的字体渲染差异会导致误报。使用
maxDiffPixelRatio
maxDiffPixels
设置容差。
</patterns>
<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
    electronApp
    in test teardown -- leaked processes accumulate, break CI, and cause port conflicts
  • 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
    ipcRenderer
    in E2E tests -- E2E tests use the real IPC channel; mock only native OS APIs (dialogs, menus)
  • Testing renderer components through Electron launch -- renderer is standard Chromium, test with web tools for speed
  • Coupling handler logic directly to
    ipcMain.handle
    registration -- extract handlers to pure functions for testability
Common Mistakes:
  • Forgetting
    await electronApp.firstWindow()
    returns a
    Page
    , not a
    BrowserWindow
    -- use Playwright page API, not Electron window API
  • Assuming
    evaluate()
    can return non-serializable values (functions, DOM nodes) -- it serializes via JSON
  • Hardcoding file paths in E2E dialog stubs -- use
    path.join(os.tmpdir(), ...)
    or test fixtures
  • Not waiting for window load before assertions -- use
    waitForLoadState()
    or
    waitForSelector()
    before checking content
Gotchas & Edge Cases:
  • _electron.launch()
    uses the
    electron
    binary from
    node_modules/.bin/
    by default -- set
    executablePath
    if your app bundles a different Electron version
  • Playwright Electron support is marked "experimental" -- API may change between major Playwright versions
  • electronApp.evaluate()
    receives the Electron module object (not
    require("electron")
    ) as its first argument -- destructure
    { app }
    ,
    { dialog }
    , etc.
  • Screenshot baselines differ across OSes due to font rendering -- pin to one OS in CI or use per-OS baselines
  • BrowserWindow
    handle from
    electronApp.browserWindow(page)
    returns a
    JSHandle
    , not a direct object -- call methods via
    evaluate
    on the handle
  • ipcMain.handle
    can only have one handler per channel -- calling
    handle
    twice on the same channel throws; use
    removeHandler
    first in tests
</red_flags>

<critical_reminders>
关键问题:
  • 测试清理阶段未关闭
    electronApp
    ——泄漏的进程会累积,破坏CI并导致端口冲突
  • 在Linux CI上运行Electron E2E测试时未使用xvfb——测试会立即因“无显示”错误失败
  • 测试IPC通信逻辑本身而非处理器结果——框架会处理消息传递,测试你的业务逻辑即可
  • 为Electron 24+使用Spectron——Spectron已停止维护,与现代Electron不兼容
架构问题:
  • 当单元测试足够时,将所有测试逻辑放在E2E测试中——E2E速度慢,应单独测试处理器逻辑
  • 在E2E测试中模拟
    ipcRenderer
    ——E2E测试使用真实IPC通道;仅模拟原生OS API(对话框、菜单)
  • 通过Electron启动测试渲染器组件——渲染器是标准Chromium,使用Web工具测试以提高速度
  • 将处理器逻辑直接耦合到
    ipcMain.handle
    注册——将处理器提取为纯函数以提高可测试性
常见错误:
  • 忘记
    await electronApp.firstWindow()
    返回
    Page
    而非
    BrowserWindow
    ——使用Playwright页面API,而非Electron窗口API
  • 假设
    evaluate()
    可以返回非可序列化值(函数、DOM节点)——它通过JSON序列化
  • 在E2E对话框存根中硬编码文件路径——使用
    path.join(os.tmpdir(), ...)
    或测试夹具
  • 断言前未等待窗口加载——在检查内容前使用
    waitForLoadState()
    waitForSelector()
注意事项与边缘情况:
  • _electron.launch()
    默认使用
    node_modules/.bin/
    中的
    electron
    二进制文件——如果你的应用捆绑了不同的Electron版本,请设置
    executablePath
  • Playwright Electron支持标记为“实验性”——API可能在Playwright主要版本之间变化
  • electronApp.evaluate()
    接收Electron模块对象(而非
    require("electron")
    )作为第一个参数——解构
    { app }
    { dialog }
  • 由于字体渲染差异,截图基准在不同操作系统上不同——在CI中固定为一个操作系统或使用每个操作系统的基准
  • electronApp.browserWindow(page)
    获取的
    BrowserWindow
    句柄返回
    JSHandle
    ,而非直接对象——通过句柄上的
    evaluate
    调用方法
  • ipcMain.handle
    每个通道只能有一个处理器——在同一通道上两次调用
    handle
    会抛出错误;测试中先使用
    removeHandler
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

关键提醒

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST
await electronApp.close()
in test teardown -- leaked Electron processes break CI and consume resources)
(You MUST mock the
electron
module in unit tests -- Electron APIs are only available inside the Electron runtime)
(You MUST use
xvfb-run
or
xvfb-maybe
for headless Linux CI -- Electron requires a display server)
(You MUST stub native dialogs in E2E tests --
showOpenDialog
/
showSaveDialog
block the process and cannot be interacted with by Playwright)
Failure to follow these rules will cause leaked processes, CI failures, and untestable dialog interactions.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名法、命名导出、导入顺序、
import type
、命名常量)
(必须在测试清理阶段
await electronApp.close()
——泄漏的Electron进程会破坏CI并消耗资源)
(必须在单元测试中模拟
electron
模块——Electron API仅在Electron运行时内可用)
(必须为无头Linux CI使用
xvfb-run
xvfb-maybe
——Electron需要显示服务器)
(必须在E2E测试中存根原生对话框——
showOpenDialog
/
showSaveDialog
会阻塞进程,且无法被Playwright交互)
不遵循这些规则会导致进程泄漏、CI失败和无法测试的对话框交互。
</critical_reminders>