「我们有崩溃恢复」这句话,说出来太容易了。真正想知道的是:如果进程现在就没掉,接下来会发生什么。
这个问题不只属于Electron。任何在本地写文件的工具都躲不开:媒体导出器、下载器、迁移脚本、AI工作流。重启一个应用很简单,把状态恢复到足以完成用户手头那件事,才是真正值得测的部分。
![]()
我拿一个Electron录制工具当例子。它的机制不复杂:持久化一些东西,杀掉进程,重启,找到残留物,重建,然后检查结果。
对一个把工作过程变成短视觉故事的本地优先桌面应用来说,「现在」有两种让人不太舒服的含义。两种情况下,原始帧可能已经存在,但输出要么是残缺的,要么干脆没有。
我的做法是:启动独立的Electron进程,创建真实的会话文件夹,发送SIGKILL,然后启动全新的Electron进程,让它去找并重建剩下的东西。用SIGKILL,是因为它会立刻终止进程,关闭处理器没有机会悄悄把状态清理干净。
这套测试还顺带揪出了一个渲染bug:一个预算3.00秒的故事,编码出来是3.46秒。
如果已经捕获了帧,那么后续启动的应用进程必须能在不重新录制的前提下,提供一次重建。
实现方式是按会话存储的清单文件,放在用户本地的应用支持目录下。清单里包含输出文件夹、原始帧文件夹、目标时长、输出格式和时间戳。清单里不包含截图数据本身。
记录必须按会话隔离。一个全局的active-session.json在只有一个未完成会话时能用,一旦出现第二个,它就会把第一个覆盖掉。
会话A对应recovery/.json,会话B对应recovery/.json。
每个清单先写成临时文件,再重命名到位。一次成功的恢复,只清除被恢复那个会话的清单。
冒烟测试用合成的PNG,而不是真的去录屏。除此之外全是生产代码:Electron启动、会话编排、清单存储、故事选择,还有FFmpeg。
测试框架跑两个场景。父进程把每个Electron应用放进自己的进程组,然后向整个进程组发送SIGKILL。这一点很关键:只杀掉Electron父进程、把FFmpeg留在后面,并不能干净地模拟一次中断。
每次杀掉之后,一个新的Electron进程会读取本地的恢复清单。测试框架重建每一个被发现的会话,对输出运行ffprobe(FFmpeg的检查工具),然后验证结果。这条命令就在仓库里:pnpm test:crash-recovery。
如果你要测的是别的应用,把「帧」换成你的应用会留下的东西就行:上传的分块、编辑过的文档、下载的文件、生成的报告,或者排队的任务。伪代码大致是这样:
await runUntil("checkpoint:persisted");
killProcessGroup();
const restartedApp = await launchWithSameDataDirectory();
const pending = await restartedApp.discoverRecoverableWork();
const result = await restartedApp.resume(pending[0]);
expect(await inspect(result)).toMatchObject(expectedArtefact);
expect(await sourceBytes()).toEqual(bytesBeforeCrash);
expect(await pendingRecords()).toEqual([]);
清理要单独检查,因为很容易做错:残留记录会导致反复弹出恢复提示,而清理范围过大又会抹掉另一个还没做完的任务。
第一次恢复运行,栽在了一个比「文件存在」更严格的检查上。
故事目标是三秒。ffprobe量出来是3.458333秒。
这很烦人,因为视频看起来没问题。能播,编码正确,看不出哪里坏了。一个只检查文件是否存在的测试会放它过去,然后我就会把这个bug发出去。
渲染器用的是FFmpeg的concat demuxer,因为故事片段时长不一。常见的变通做法是重复最后一个文件,让FFmpeg保留最后一帧静止画面。副作用就是编码后的视频多出一段尾巴。
我把编码输出限制在故事计算出的时长上,并保留了一个回归测试:生成带权重的片段,再用ffprobe测量结果文件。
await runFfmpeg([
// concat input and video filter omitted
"-movflags", "+faststart",
"-t", (story.totalDurationMs / 1000).toString(),
"-y", outputPath,
]);
这就是为什么要检查输出,而不是断言文件存在。报告可能存在但缺行;视频能播,时长却可能是错的。
2026-10-05,冒烟测试跑在macOS 26.3、arm64 Mac17,2和Node.js 22.14.0上:
capture interruption → 3 frames discovered → recovered
encoding interruption → 3 frames discovered → recovered
source PNGs → unchanged
output → H.264 / yuv420p / 3.00 s
我也测了未签名的打包arm64 .app,而不只是开发进程。应用带着临时配置启动,从app.asar内部加载Sharp,接受一次生产环境的IPC重渲染请求,生成了一个三秒的MP4。
CSC_IDENTITY_AUTO_DISCOVERY=false pnpm run pack
pnpm test:packaged
这能抓住另一类失败:代码在工作区里能跑,只是因为它碰巧从开发机上解析到了原生依赖。
这些覆盖的是进程崩溃,仅此而已。
对一个本地优先的工具来说,UI没了的时候,有用的数据可能已经在磁盘上了。在这个应用里,因为FFmpeg死了就丢掉一个没做完的会话,会是个糟糕的默认行为。
每次跑测试框架,走的都是同一个循环。
接下来我需要一次真正的两到五小时会话,带上资源测量、睡眠/唤醒,以及一次有意的中断。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.