fix-ci-e2e-flake
诊断并修复 CI 偶发、本地稳过的 Playwright e2e 失败(flake)。当用户提到 CI e2e 偶发失败 / flaky / 本地跑没问题但 CI 挂了 / e2e 不稳定,或贴出 toHaveCount / toBeVisible / timeout 报错、expect(locator) call log 时使用。
SKILL.md.claude/skills/fix-ci-e2e-flake/SKILL.md
GitHub修复 CI 偶发 e2e 失败(flake)
本仓库 e2e 用 Playwright + _electron 启动真实 Electron,通过 window.__E2E__ 探针调服务。CI 偶发、本地稳过的失败绝大多数不是产品 bug,而是断言写法不够鲁棒,或 CI 环境噪音(extension host 崩溃、进程启动慢、定时器竞态)。核心套路:判定真回归 vs flake → 读 call log 的“失败形态” → 把断言收敛到“被测对象本身” → 本地验证 happy path 不破 → 经验追加到案例库。
⚠️ 第一原则:不要为了让 CI 变绿而削弱对被测行为的覆盖。鲁棒化 = 排除背景噪音干扰,被测断言强度不变。只能靠放宽真正的被测断言才能过 → 它可能是真回归,别盖住。
判定流程
- 真回归 vs flake:本地
--repeat-each=5能否复现(本地稳过+CI 偶发→flake;本地也挂→回归);同 commit 重跑能过=flake,每次必挂=回归/结构性缺口;retry 救得回=瞬时竞态(flake),救不回=结构性(test 超时击穿/真回归/产物缺口)——分类第一信号;git log -p看失败 spec 是否刚改过;怀疑回归时git stash纯净基线复跑(对比前两边都先pnpm build);核对 CI 堆栈绝对路径(D:\a\...是 runner 路径)语义对得上你改的目录。 - 读 call log 失败形态:count 波动=背景元素间歇出现(噪音污染全局 count);count/received 稳定停错值=被测对象自身没就位(真回归/定时器没触发/fire-once 空转);
waiting for locator恒 0=渲染没发生/探针没触发/选择器错;timeout 且无元素=往前看前置步骤;元素 visible 但 click 超时 +... intercepts pointer events=被兄弟元素遮挡(hit-target 检查走 elementFromPoint,水平布局溢出时常见,见案例 42)。 - 已知噪音源:extension host 偶发崩溃(
ExtensionHostClientService._handleCrash发背景 toast + error 日志);renderer 定时器竞态(auto-hide/auto-read 在 CI 晚几百 ms)。 - 最小且鲁棒的修复(优先对齐同文件已鲁棒化的兄弟断言——同文件内有的步骤已加固、有的还裸,后者是遗留薄弱点):噪音污染列表→
.filter({hasText:'<被测唯一文案>'})收敛;噪音污染全局单值/一次性状态→从源头禁用无关子系统(先 grep 确认无 spec 依赖);定时器/异步竞态→expect.poll/toHaveCount({timeout}),少用固定waitForTimeout+硬断言;纯环境型→别强改产品,记录案例库。 - 验证:诊断前先确认产物链是新的(
pnpm build;手跑单 LSP spec 还需pnpm ext:build+extension-host/dist+vendor/typescript-language-server,缺→符号空/探针缺=产物问题非回归)。UNIVERSE_E2E_NO_TAG_FILTER=1 pnpm --filter @universe-editor/editor exec playwright test e2e/specs/<spec>.ts [--repeat-each=5](cwd 落在 apps/editor,会命中该目录的转发 config 从而套用默认 tag 过滤——不带NO_TAG_FILTER时全@regression的 spec 会直接报 “No tests found”,看着像路径写错);全量pnpm e2e(输出多,只截错误)。本地无法复现 CI 噪音是常态——目标是“鲁棒化没破坏 happy path”。 - 沉淀:把“失败形态→根因→修法”追加到
references/cases.md(信号行必填),并在下方「案例索引」补一行。这是本 skill 长期价值所在。
案例索引(按失败信号速查,命中后去 references/cases.md 读详情)
call log count 波动=背景噪音 → 案例 1;全局单值/一次性状态同轮多挂=同一噪音源打一片 → 案例 2
Test timeout of 30000ms exceeded(非 poll message)=test 天花板击穿 → 案例 3/10/32CI 每次必挂+本地稳过+received 恒空+retry 救不回=伪 flake(CI 缺产物)→ 案例 4
expect.poll里keyboard.press后 received 稳定错值=盲按污染被测对象 → 案例 5Execution context was destroyed ... navigation(fire 后页面 reload/导航再 evaluate)→ 案例 6;冷启动首个裸 evaluate、无任何导航/崩溃(utility world 销毁,挂 CDP 后不复现)→ 案例 58;Resulting promise was garbage collected栈在 evaluateWhenRestored 内(同竞态第二种消息变体击穿只认 destroyed 的守卫,retry 救不回≠非瞬时竞态)→ 案例 78冷启动首 poll 恒
"":局部硬编码 timeout 盖 CI 分档 / 首 poll 漏带大 timeout → 案例 7/25/31EBUSY/EPERM rmdir栈在 teardown=Windows 文件锁 → 案例 8;ENOTEMPTY+.json.tmp=storage 原子写竞态 → 案例 17;EPERM/EBUSY 在 test body finally 删「app 打开/watch 的目录」+retry 救不回=app/daemon 还活着(fixture teardown 在 finally 之后),用 harnessscratchDir排到 closeApp 后 → 案例 77(本地 smoke.* 冷启那批已迁,判据与 shared fixture 例外见 77b)runCommand+expect.poll分离、received 卡死初值=fire-once 在就位前空转 → 案例 9click 把状态切离默认后 received 恒卡默认值=mount 后 fire-and-forget reconcile 迟到覆盖 → 案例 27
上轮修完形态变了仍挂=只治了表象,复合根因继续剥 → 案例 11/30/40
Target page ... has been closed+退出码 0xC0000005:单实例过、多实例崩=parcel watcher → 案例 12/16/44;sharedApp 下崩点飘忽受害者无辜 → 案例 26Worker teardown timeout+测试全过:有 fixture 名=quit 链 veto → 案例 13;无 fixture 名+事后有孤儿=孤儿子进程握 CDP pipe → 案例 45;无 fixture 名+事后无孤儿=多 shared app 串行 closeApp 超预算 → 案例 63滚动采样 received 单调爬升、
--workers=1过--workers=4挂=测量瞬时值 → 案例 14滚动恢复断言 frac 异常(RAF 过冲/虚拟双峰/像素中点落末条)→ 案例 15/34/41
仅 CI Windows 确定性挂+涉及 tmp 路径/git=8.3 短路径 → 案例 18;URI 字符串稳定差
%7E/~→ 案例 29新 spec
--repeat-each批量挂、received 恒他人写入值=多层叠加 → 案例 19--workers=1确定性全挂+stash 回 HEAD 仍挂=既有脆弱点被激活,别当环境噪音 → 案例 20/30sharedApp 多 ghost tab / 跨 spec 状态泄漏=reload 不拆 main 态 → 案例 21/40
关闭编辑器后紧接 model 操作、修改静默丢失=dispose 竞态 → 案例 22
某提交后多 spec 同红形态各异=
git show看时序变更波及面 → 案例 23;补了.catch仍挂=永久 pending → 案例 24Contributed command "<ext.cmd>" does not exist.=激活竞态(贡献命令的 handler 尚未注册,宿主 reject;2026-10 已根治,残留 host-only 命令见案例 35)→ 案例 35;同一句也被「该扩展在本窗口整体不适用」触发,先看 spec 是否在非本家工作区里裸 fire 扩展命令拖拽 spec received 恒 0(稳定卡值)=drop 在 Monaco 就位前 fire → 案例 36
workspace 切换后紧接的命令 received 恒初值=host 异步 re-pin 窗口 → 案例 37;boot 后
openWorkspace的 LSP spec provider poll 恒 0 / teardown 报 Disposable 泄漏(栈在_createProvider)=双重启竞态,用workspaceSeederlaunch pin workspace → 案例 54等满 timeout 恒 not found 且该时长==产品放弃点=根本没就位(产品 bug)→ 案例 38
click 侧栏后 received 恒 explorer / 快照
Explorer [pressed]=后台逻辑抢容器 → 案例 39/40元素 visible 但 click 超时+
intercepts pointer events、本地全绿=en-US+1280 窗口溢出遮挡 → 案例 42;点击超高 Monaco.view-lines容器中心、CI relayout 后中心滑出可见区被 Welcome/标签栏挡 → 案例 54仅 CI Windows 确定性挂+断言基于行/列宽度(getBoundingClientRect/scrollWidth)+失败截图视口恒等于 runner 屏宽(1024x718)=runner 屏 1024x768 夹窄默认 1280 窗口,布局断言在 Windows 上测的是另一个场景 → 案例 92
toBeFocused恒 inactive+无 workspace 冷启动=bootstrap 一次性焦点恢复抢焦 → 案例 43;测试中途 openWorkspace 后编辑器区内焦点被抢=workspace restore 窗口 editor 分支裸抢 → 案例 53纯黑页+probe 恒无+业务无关 spec 同轮随机挂=bootstrap RPC 被 gate 丢弃 → 案例 33
本机裸
electron.launch报Process failed to launch!(exitCode=9)、CI 正常=本机环境 → 案例 28;CI Windows 同报错+ICU 加载失败/文件被占用+同窗口多 worker 齐挂=runner 文件锁窗口,harnesslaunchElectron已内建重试 → 案例 72;CI Linux 报spawn ETXTBSY栈在 launchElectron 内=瞬时守卫正则不匹配新变体 / Windows 重试耗尽仍挂=锁窗口超预算(Defender 排除根治)/ Windows 报Electron failed to install correctly=同族新变体(已并入守卫)→ 案例 72b;报错无electron.launch:前缀+trace error 条目早于重试留痕=playwright 内部游离 promise unhandledRejection 击穿守卫(已 pnpm patch playwright-core)→ 案例 72c失败仅集中 DnD 类且重跑能过=headless 手势时序 → 案例 46;锁屏时剪贴板用例必败 → 案例 47
终端 spec 在 WSL 下 initial+retry 确定性挂、续行文本比预期多一个
"(CI 全绿)=把 pty 对输入的回显当成了 shell 输出(缓冲区含某段文本 ≠ shell 执行过;快 shell 赢 race、慢$SHELL必输),定位判据锚「行首」→ 案例 87新写「重启后状态仍在」用例 initial 挂/retry 过、failed 在自建 poll 等持久化值、received 里混入别的类型条目(view)=renderer 的 debounce 写还没落盘就
closeApp+ 对整份持久化容器严格相等 → 案例 88chord 用例卡
defocusEditor等 focus 变 false+retry 秒过=defocus 时序噪声(观察中)→ 案例 48Alt+<n>偶发等于没按(element(s) not found恒 0 / received 恒初值)+ 失败现场 DOMactiveElement已在目标区内而when里的*Focus键读到反向值 = 焦点键陈旧(逐 Part 键由 tracker 记账;同一次按的上下文不同会走不同代码路径——浮层开着走 React handler、关着走全局 binding)→ 案例 90(已修:键改 DOM 派生 + 原子读不变量断言)列表相等断言 received 是 expected 前缀子集+采样点为固定 sleep=增量渲染截半,poll 到收敛 → 案例 49
sash 拖拽/尺寸持久化 spec,reload 后目标 pane 高度稳定卡等分值=异步 reconcile 落后于 Allotment 首次布局、preferredSize 挂载后是 no-op → 案例 50;修完同断言再挂=等分值经 onChange→debounce 落盘污染磁盘,恢复路径修得再好读的也是脏值,须收窄落盘权到用户动作 → 案例 50b;再挂且诊断现场 mem==DOM==贪心值(磁盘干净)=mem 记账被 onChange 覆盖、storedSizesKey/target 读脏 mem 锁死,恢复目标必须读独立的 persisted 权威源(save 序列化也走它)+settle 窗口内持续校验 → 案例 50c
ACP 配置写入后立即建 session、echo agent received 恒
"[]"=异步镜像池 stale 滤空 wire 列表 → 案例 51[MonacoLoader] not initialized栈过同步探针=poll 回调抛异常击穿等待 → 案例 52(已修又再发时先核对 main 上修复真实存在:git log -S)openWorkspace 后立即外部写文件、等 watcher surface 的 treeitem 等满 timeout 恒不出现=watcher 跨进程 arm(spawn utility process+subscribe)窗口吞事件,seed 可见≠订阅生效 → 案例 55;同一条事实的非树消费者(会话改动兜底)变体:等行恒 0 且失败取证里连该通道的
.log文件都不存在=工作区 watcher 还是 idle 相位才 arm(WorkspaceWatchContribution),树的 catch-up 重读只治树,别的 consumer 得自己等就位信号 → 案例 89teardown 泄漏栈
MainThreadLanguages._createProvider但断言全过、bundle 变更提交后 CI 恒定挂、泄漏数=activate 注册批大小=host activate 赢了 Monaco dynamic import,注册抛 not-initialized 半建 store 成孤儿+provider 批静默丢失 → 案例 56(区分 54:那是 dying-host 帧打 disposed 对象)「忙等+可信输入」spec 本地
--repeat-each必现 flaky、双形态交替(poll 恒 0 / byType 实收单元素但非期望类型如["keyup"])=setTimeout 提前量赛跑 CDP 输入派发 + dedupe 只留最慢样本;修=console token 确认主线程已阻塞再按键 + 断言放宽到事件族 → 案例 57焦点门控断言(如聚焦时应出现应用内 toast)恒不出现+host.log 是
notify shown而非skipped=并行 workerwin.show()偷前台,断言前page.bringToFront()钉焦点 → 案例 59EPERM rename '<x>.json.<pid>.<ts>.tmp'经 reviveWireError 从 main 传回、retry 救得回=产品原子写 rename 撞 Windows 瞬时锁(并发读/AV),修产品加重试 → 案例 60beforeAll 起本地 server 报
did not start on <url>、retry 救得回=固定随机端口撞占用(上轮孤儿 server)+stdio ignore 无诊断 → 案例 61tokenColor(<word>)poll 恒 undefined 到Test timeout=Monarch 合并 span 使 exact-text 在 TextMate takeover 前结构性匹配不到,补显式 takeover 门控+test.slow → 案例 62图表 reveal 断言 received 恒
_row_…(行在、选中类稳定缺失)+retry 变 "Loading…" 行不出现=reveal 与初始 load 双发请求、晚到 load 清选中(产品竞态)→ 案例 64自启动 spec
closeApp: graceful close still pending→force-kill 后CDP pipe still open→teardown 30s 超时、事后无孤儿=某进程握子端 pipe 句柄(wsl 探测/agent 孙进程/并发 spawn 继承泄漏),终解=closeApp 父端 stdio destroy 兜底 + seed 收敛 + 死父指纹清扫 → 案例 65takeover 门控过后
tokenColor仍恒 undefined(整行单 mtk1 span)/ token 有色但错色(mtkN 呈 +1 位移查了残表)=TextMate 双产品竞态(grammar 注册竞速 Monarch 丢 resolve 不 fire + 主题快照乱序落盘残表);探针门控用 getOrCreate 会替产品治病,须改只读 → 案例 66探针抛
[E2E] no durable active ACP session/ echo agentsession/load拒session not found、失败点在「发送 prompt 后立刻依赖 durable id/agent 落账」=user 消息本地乐观上屏不等握手;就位信号=agent 回复进 timeline,pollstatus==='idle'有假窗口不充分 → 案例 67大 fixture 多次 print 往返+裸 toBeVisible 等满不出现、initial+retry 同形态=「纯慢 vs 静默 '' 误路由」双假设同修(失败结构化传播+断言先 poll 任一终态);artifact
if-no-files-found: ignore把上传路径错位静默吞掉致无现场 → 案例 68(真根因后来定案在案例 69)案例 68 修后同形态再挂、仅 CI Linux 偶发本地 Windows 全绿+失败截图恒黑+aria 快照只剩 alert 空壳=fake CLI 大 stdout 后
process.exit()在 POSIX 截断 → 尺寸路由翻转 → providerless webview 静默超时;修=process.exitCode自然退出+poll 终态补 custom-editor+失败自动收集 userData/logs → 案例 69toBeFocused恒data-focused="false"(稳定卡值)但 aria 快照树内容已正确、断言前一步刚触发异步数据推送=焦点交付后 payload 落地触发 keyed remount 换掉被聚焦节点;修=ChangesTree 同 commit 换树保焦(模块级 viewId 意图集+microtask 过期)→ 案例 70(与案例 64 同族互参)poll context key 恒
""+截图纯黑+aria 只剩 alert 空壳+bootstrap 日志完整走完后静默(探针活着)=workspace-swap restore 竞态擦掉 swap 窗口内新开的编辑器;修=restore 读期间新进 editor 先 detach 再 re-admit 永不擦;React 19 逃逸边界错误静默 unmount root 且被 isBenignError 吞掉零留痕,createRoot 须显式 onUncaughtError 落盘 → 案例 71(与案例 33/69 的黑屏形态区分见详情)toContainEqual 的 received 里消息 text 是期望的前缀(echo 回声同截断、retry 截断点漂移)+失败点前是
keyboard.type直接 Enter 打 Monaco 输入框=EditContext 异步落字赛跑提交,type 后先 pollgetAcpPromptText()全文再 Enter → 案例 73palette
type→Enter后命令效果 poll 恒卡初值、焦点断言正常+双平台同 run initial+retry 全灭+插桩(type 与 Enter 间加 evaluate)后不复现=QuickInputPanel useDeferredValue 旧列表被 accept(产品竞态,慢机高概率),修产品 accept 路径不改 spec → 案例 74复制图片后剪贴板 poll 恒无图、
toPngBase64快路径剥前缀未校验 payload=echo fixture 发伪 PNG 字节,main nativeImage 解出空图静默跳过写(多 worker 各自独立 app 时不复现,serial lane 共享 app 才现)→ 案例 75;Ctrl+V 粘贴后 chip 恒不出现、仅 CI Linux=xvfb 剪贴板 ownership 翻转不同步,seed 后先 poll 剪贴板可读回图再粘 → 案例 75belectronApplication.firstWindow: Timeout 30000ms栈在 harness fixtures(非 spec 体)+ 报错无electron.launch:前缀(launch 已成功)+ initial+retry 双挂 + 伴随无 fixture 名Worker teardown timeout=post-launch→pre-window 相位被 runner 环境窗口拖死(72 家族守卫不覆盖此相位),harnesslaunchAppReady已内建整链重试+log dump+closeApp 防孤儿 → 案例 76webview/iframe 计数钩子 poll 恒 0+initial/retry 同形态+失败 commit 与被测链路无关=钩子以静态 HTML 标记为门控
if(!obj) return静默空转(案例 9 iframe 变体)叠加单次写赛跑 createFileSystemWatcher arm 窗口(案例 55 exthost 变体);修=poll 对象本身就位+钩子守卫改 throw+poll 内幂等重写 → 案例 79teardown 泄漏栈在 Monaco
bindDocumentChangeListeners+断言全过+全 ts spec 恒定挂=provider 挂onDidChange后 model 级ModelSemanticColoring(活到 teardown)把订阅存普通数组成孤儿 root(泄漏门误报);修=proxy 侧包装 event 给订阅setParentOfDisposable(store)锚定 root 链(区分 54 dying-host/56 半建 store)→ 案例 80自启动 spec 中途 openWorkspace 后
getActiveEditorUri().toBeFalsy()恒停旧值(a.json)或release-notes:whatsNew、--repeat-each可复现=手写 state.json 漏了 harnessINITIAL_STATE的app.releaseNotes.lastVersionpin,升级 "What's New" 标签异步打开既污染空 workspace 又触发 persist 跨通道赛跑把 A 写进 B;修=seed 合并INITIAL_STATE→ 案例 81output 过滤后写真实 logger 的高等级行 10s 不可见、仅 CI Windows 某 shard 双挂、本地大量复现失败=先把「写链路」与「过滤可见」断言分层并加失败 dump,再逐项证伪(文件落盘/mtime、等级解析、Monaco 可见性、teardown 顺序红鲱鱼);「aria 只剩 alert 空壳」先查 teardown
expectNoLeaks先 unmount 再拍 snapshot → 案例 82sideBarVisible恒 false 但同一时刻 treeitem 的 toBeVisible 已过(矛盾指纹)+ 侧栏宽度 0 而树行仍在 DOM=裸 fire 了 toggle 命令workbench.view.explorer,一半概率把 Explorer 关掉;改走幂等WorkbenchPO.showExplorer()(读状态再决定是否 fire)→ 案例 83键盘 ContextMenu 键断言仅 CI Linux 确定性挂+
detail===0恒空+retry 同形态+Windows 全绿+新 spec 首跑=keyup 补充 contextmenu 是 Windows 专属 Chromium 契约,断言改平台感知(win32 精确/非 win32 0..1 且形状不变,非弱化)→ 案例 84虚拟化行出窗断言
not.toBeInViewport但 ratio=1 且 rect 真在窗外(矛盾指纹)=IO 采样到 transform 前瞬态帧;改 rect 判窗后又稳定停中间值=Tree reveal useLayoutEffect 被 watcher 初始 refresh 的 structureVersion 变化重跑、scrollIntoView 撤销 scrollTop=0;修=poll 内每次先重设 scrollTop=0 再读 getBoundingClientRect 判窗外 → 案例 85键盘 ContextMenu 键开出的菜单 count 恰 1 但缺行级项(Rename…/Delete not found)+ 本地确定性复现=守卫判据失效致空白区菜单 last-wins 顶掉行级菜单;根因=keyup 补充事件坐标是焦点元素中心不是 (0,0)(上一轮按未实测的 (0,0) 收紧守卫),判据须换
button === -1(CDP 右键恒button: 2);改共享守卫判据后必须回查所有模拟该事件的单测否则假绿 → 案例 86预填/选区类断言 received 是光标默认位的旧值(
#TestValue变#const)+ 同文件同族 setup 早已expect.poll只有这一处裸 fire + 本机高概率挂/git stash回基线同挂=探针 setup 丢掉了返回值,openWorkspace后活动编辑器还没就位/选区被迟到 adopt 覆盖 → 案例 91连续键盘 grow → shrink 恰差一个步长、启动窗口内真实 Allotment 单测稳定复现=onChange 启动恢复撤销键盘操作(产品竞态),键盘与拖动共用用户操作标记 → 案例 93
focusedView 已到位但立即重开 Ctrl+Tab 首行仍是 Files、retry 恢复=FocusTracker 延迟 settle 而 picker 只取一次快照,打开前只读 poll MRU head(同查 Ctrl+P)→ 案例 94
子代理全文已到但
live=false, sawLive=false、展开 click 比末块观察窗更慢=采样错过流式阶段;仅延后封口又出现mdparse.calls=1=展开前已发完,首块挂载握手 + 末块采样握手保留真实增量渲染 → 案例 95非快捷键用例在切组前等
editorFocus=false恒 true、还没按 chord 就超时=场景搭建耦合无关的移焦步骤,改走同一切组命令,保留活动组与业务断言 → 案例 96反向断言「某日志为空」CI 偶发收到一条逐文件窄查=断言挂在一场赛跑上(该通道按设计在特定相位就是会响),改判形态(目录 spec
/...//*);改完用 fake 故障档验信号仍在 → 案例 97type+Enter盲打后waitForHidden超时、现场只有「对话框没关」=诊断黑洞;打字前input.focus()+ Enter 前断言落字,失败点前移(重跑即绿=环境噪音,只加门控不改产品)→ 案例 98等
isMaximized()/ 绝对宽度阈值(innerWidth > 1500)恒不满足、仅 CI 挂=runner 虚拟显示器小(win ≈1024 / xvfb ≈1280)且 xvfb 无窗口管理器,maximize()no-op → 窗口尺寸改setBounds+ 相对阈值 → 案例 99发完 prompt / 图片后紧跟断言消息或 chip、received 恒空/旧值且加宽窗口无效=
sendAcpPrompt的 await 只覆盖派发不覆盖渲染,断言抢跑 → 改expect.poll等落地 → 案例 100读已打开文档 ✓ / 读未打开文件 ✗ 的不对称(received 恒
[])=fs 网关(DocumentStore overlay 不过网关),不是路径计算;先pnpm build排除陈旧产物 → 案例 101waitForHidden超时但现场「什么都对」(焦点/值/composing/按键 target 全正常、列表非空且已选中)=光标比列表晚一个 commit 归位(deferred 值 vs effect 修正的索引),Enter 落在中间读越界索引静默 no-op;窗口每次运行必现(渲染路径计数render:1),命中靠时序;修产品 accept 路径(越界按实时文本重算),别当环境噪音 retry → 案例 102新建窗口后紧跟的固定预算断言首跑超时 /
expect.poll空等(并行冷启、机器忙)+ 失败现场「目录已设好、窗口列表对、下一探测立刻应答」=建窗后 adopt 目录 / 起扩展宿主期间的启动竞争,加一次只读 ping 门控(只是 ping,不是就绪信号、也不是性能修复;抛错原样上抛,别用宽泛 catch 吞成重试)→ 案例 103等 fake CLI 盘上日志的断言 60s 恒空
[]、而 SCM provider/refresh/探针全程健康(纯黑截图+aria 空壳是已知产物)=产品端「先await读配置、后 push 订阅」的毫秒窗口吞掉启动期异步载入的工作区设置层(该会话永久为空,retry 救得回但改断言无用);修=抽watchConfig先订阅后读(seq 守卫),并给该 fixture 接失败取证(输出通道+fake 日志)。压测(--repeat-each=10 --workers=2)再暴露更深一层:取证里reconcile exclusions: 0 dir(s)+--exclude-dir全程 0 = 设置层根本没载入(跟消费方订阅顺序无关)——UserSettingsSync把onDidChangeFile注册在initialize()末尾,而 main 在工作区变更时 fire 的那次「装槽 + 广播」正好落在「首读已发出、监听器未注册」的窗口里被丢(瞬态广播无 replay,之后无人 re-read);修=订阅移到构造函数 + 可控读闸单测(变异验证)→ 案例 104reload 后 pane 尺寸回到等分、差值恰等于产品步长常量(如 50 =
RESIZE_STEP)、同 suite 其它 reload 用例与 Linux 恒绿(Windows 慢机偶发、retry 同形态)=恢复校正的两条路径都「一次性且不重试」(key effect 早退后永不重跑 + 600ms 窗口从容器 mount 起算、而 reconcile 超时从 app mount 起算,对晚挂载容器实际预算为 0),等分被proportionalLayout锁死;先看截图两 pane 是否相等,再用探针getPersistedViewSize分开「没落盘」与「没恢复」;修=「未应用即保持武装」,别放宽断言或加 timeout → 案例 105δ
--sync -a日志恒 0、磁盘已更新且原生日志出现 sync,删掉命令注册等待后暴露=SCM 注册不等于首轮 δ scan 分派;健康 δ 操作前只读等待目录扫描 argv,不改首轮前 native 契约 → 案例 106折叠键按下后 received 稳定等于按前可见行数、同测试相邻按键全过=monaco fold action 解析异步折叠模型,模型尚无 region 时静默 no-op;按键前 poll 探针
regionAtCursor(fold-all 用regionCount > 0),保持单次按键与断言强度 → 案例 107
关键参考路径
apps/editor/e2e/specs/—— 所有 e2e spec;@p0阻塞 CI,@p1次级apps/editor/e2e/fixtures/electronApp.ts——workbenchfixture、runCommand/waitForRestored/statusBar封装、closeAppapps/editor/src/renderer/e2e/probe.ts——window.__E2E__探针apps/editor/src/renderer/services/extensions/ExtensionHostClientService.ts—— ext host 崩溃→通知(CI 噪音主源)
其它
- 后续用本 skill,发现新经验,按判定流程第 6 步同步更新案例库与索引
- 修复了某个e2e本身测试稳固性的问题,那么需要整体评估一下所有e2e是否也存在同样的问题,并一并修复