Electron 调试与测试
三类调试链路、单元测试、端到端测试与常见崩溃排查。
Key Points渲染进程用 DevTools,主进程用 --inspect,整应用用 Playwright 做端到端——三层各管一段。
三层调试
| Layer | Tool | What it shows |
|---|---|---|
| Renderer | Chrome DevTools | DOM、Console、Network、Performance |
| Main | --inspect + 调试器 | 断点、调用栈、变量 |
| Whole app | Playwright / Spectron | 真实点击、窗口、文件结果 |
单元测试
JavaScript
// 纯逻辑抽成独立模块,不依赖 electron,就能直接单测
// lib/format.js
function formatBytes(n) {
const units = ['B', 'KB', 'MB', 'GB'];
let i = 0;
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
return `${n.toFixed(1)} ${units[i]}`;
}
module.exports = { formatBytes };JavaScript
// test/format.test.js
const { test } = require('node:test');
const assert = require('node:assert');
const { formatBytes } = require('../lib/format');
test('formatBytes', () => {
assert.equal(formatBytes(512), '512.0 B');
assert.equal(formatBytes(2048), '2.0 KB');
assert.equal(formatBytes(5 * 1024 * 1024), '5.0 MB');
});Bash
node --testOutput
✔ formatBytes (1.2ms)
ℹ tests 1
ℹ pass 1
ℹ fail 0Electron code is hard to test because require('electron') yields nothing real under plain Node. Extract logic into modules that never import electron and let the main process only wire things up — then most logic is covered by ordinary unit tests.
端到端测试
JavaScript
// e2e/app.spec.js —— Playwright 支持直接启动 Electron
const { test, expect, _electron: electron } = require('@playwright/test');
test('窗口能打开并显示标题', async () => {
const app = await electron.launch({ args: ['.'] });
const win = await app.firstWindow();
await expect(win).toHaveTitle(/Hello/);
await win.click('#btn');
await expect(win.locator('#info')).toContainText('Electron');
await app.close();
});electron.launch starts the real app and can verify the whole click → IPC → main → file chain. Unit tests cover one link; only end-to-end tests cover the wiring.
常见崩溃排查
| Symptom | Cause |
|---|---|
| White window | loadFile 路径错,或渲染进程抛异常 |
| App exits silently | 主进程未捕获异常 |
| NODE_MODULE_VERSION mismatch | 原生模块没按 Electron 重编译 |
| Blank after packaging | files 漏了资源,或用了 __dirname 相对路径 |
| Crash on GPU | 显卡驱动问题,可 app.disableHardwareAcceleration() |
JavaScript
// 兜住主进程的未捕获异常,至少写进日志
process.on('uncaughtException', (err) => {
console.error('主进程未捕获异常', err);
logToFile(err);
});
process.on('unhandledRejection', (reason) => {
console.error('未处理的 Promise 拒绝', reason);
});A blank window after packaging is almost always a resource path issue:
__dirname points into app.asar after packaging, so relative bases change. Use path.join(__dirname, ...) and confirm files includes those assets.三层调试
| 层 | 工具 | 能看到什么 |
|---|---|---|
| 渲染 | Chrome DevTools | DOM、Console、Network、Performance |
| 主进程 | --inspect + 调试器 | 断点、调用栈、变量 |
| 整应用 | Playwright / Spectron | 真实点击、窗口、文件结果 |
单元测试
JavaScript
// 纯逻辑抽成独立模块,不依赖 electron,就能直接单测
// lib/format.js
function formatBytes(n) {
const units = ['B', 'KB', 'MB', 'GB'];
let i = 0;
while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; }
return `${n.toFixed(1)} ${units[i]}`;
}
module.exports = { formatBytes };JavaScript
// test/format.test.js
const { test } = require('node:test');
const assert = require('node:assert');
const { formatBytes } = require('../lib/format');
test('formatBytes', () => {
assert.equal(formatBytes(512), '512.0 B');
assert.equal(formatBytes(2048), '2.0 KB');
assert.equal(formatBytes(5 * 1024 * 1024), '5.0 MB');
});Bash
node --testOutput
✔ formatBytes (1.2ms)
ℹ tests 1
ℹ pass 1
ℹ fail 0Electron 的代码难测,是因为 require('electron') 在 Node 里拿不到真实对象。解决办法是把业务逻辑抽到不依赖 electron 的模块里,主进程只做「接线」——这样大部分逻辑都能用普通单测覆盖。
端到端测试
JavaScript
// e2e/app.spec.js —— Playwright 支持直接启动 Electron
const { test, expect, _electron: electron } = require('@playwright/test');
test('窗口能打开并显示标题', async () => {
const app = await electron.launch({ args: ['.'] });
const win = await app.firstWindow();
await expect(win).toHaveTitle(/Hello/);
await win.click('#btn');
await expect(win.locator('#info')).toContainText('Electron');
await app.close();
});electron.launch 会真的启动应用,能验证「点击 → IPC → 主进程 → 文件写入」这条完整链路。单元测试只能测其中一段,端到端才能测接线是否正确。
常见崩溃排查
| 现象 | 原因 |
|---|---|
| 白屏 | loadFile 路径错,或渲染进程抛异常 |
| 静默退出 | 主进程未捕获异常 |
| ABI 不匹配 | 原生模块没按 Electron 重编译 |
| 打包后白屏 | files 漏了资源,或用了 __dirname 相对路径 |
| GPU 相关崩溃 | 显卡驱动问题,可 app.disableHardwareAcceleration() |
JavaScript
// 兜住主进程的未捕获异常,至少写进日志
process.on('uncaughtException', (err) => {
console.error('主进程未捕获异常', err);
logToFile(err);
});
process.on('unhandledRejection', (reason) => {
console.error('未处理的 Promise 拒绝', reason);
});打包后白屏几乎总是资源路径问题。开发时
__dirname 是项目目录,打包后在 app.asar 里,相对路径的基准变了。用 path.join(__dirname, ...) 并确认 files 字段包含那些资源。