From 01742e6dc9ab73dd7a418850fb794701d371a83b Mon Sep 17 00:00:00 2001 From: Prekzursil Date: Fri, 14 Aug 2026 22:34:28 +0300 Subject: [PATCH] fix(e2e): fail the packaged leg in seconds with the real cause, not a 900s hang ROOT CAUSE of a nightly that has been red since 2026-08-11: electron-builder.yml sets 'enableNodeCliInspectArguments: false' (W66 hardening, commit d4a7e3f1, that same day). Playwright drives an Electron MAIN process over the NODE inspector -- its launcher passes '--inspect=0' and waits for it -- and that fuse makes Electron IGNORE --inspect. The inspector never appears, so electron.launch waits out its full 900s budget and reports nothing but a timeout. THE SHIPPED APP IS FINE. The CI call log shows it starting normally: 'DevTools listening on ws://...', then the renderer finishing load and the auto-updater reporting a result (wireAutoUpdater only fires on did-finish-load). Only Playwright's control channel is closed. Why the fuse's own comment says nothing depends on --inspect: that was measured by grepping tracked sources, and --inspect is injected at RUNTIME by playwright-core's Electron launcher, not written in any file here. A source grep structurally cannot see it. Two signals agree: the mechanism, and the exact date match between the fuse landing and the leg going red. This THROWS rather than skipping. The coverage is genuinely lost while the fuse is off, and a skip would turn a real gap into a green tick. The leg stays red -- but red in seconds, naming the cause and the three ways out (flip the fuse; build a test-only package and assert the release artifact separately; or cover the package without main-process assertions). That choice is the owner's W66 trade-off, not something to decide inside a spec file. Verified: tsc -p tsconfig.e2e.json clean; the guard resolves electron-builder.yml and fires on the current state, and a both-states control confirms it does NOT fire when the fuse is true. --- app/e2e/packaged.spec.ts | 48 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/app/e2e/packaged.spec.ts b/app/e2e/packaged.spec.ts index 4662c68b..17966593 100644 --- a/app/e2e/packaged.spec.ts +++ b/app/e2e/packaged.spec.ts @@ -19,7 +19,9 @@ import { test, expect, _electron as electron, type ElectronApplication } from '@playwright/test'; import { spawnSync } from 'node:child_process'; -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; import { COLD_TIMEOUT_MS, SIDECAR_DIR, @@ -30,6 +32,9 @@ import { type SeededEnv, } from './fixtures'; +/** Repo root, from this file: app/e2e/ -> app/ -> . */ +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..'); + // The packaged artifact is ONLY produced on the Windows leg (electron-builder.yml // has a win: target; the embeddable CPython + ffmpeg staging is Windows-only — // build/python-embed-setup.ps1). On macOS/Linux there is no package to launch, so @@ -60,6 +65,47 @@ test.describe('packaged (shipped binary) E2E', () => { // timeout; `electron.launch` needs its own, which is a separate budget again. test.setTimeout(COLD_TIMEOUT_MS + 120_000); + // FAIL FAST ON THE FUSE, before burning the 900 s launch budget. + // + // Playwright drives an Electron MAIN process over the NODE inspector: its + // launcher passes `--inspect=0` and waits for that inspector to come up. + // `electron-builder.yml` sets `enableNodeCliInspectArguments: false` (W66 + // hardening, commit d4a7e3f1, 2026-08-11), which makes Electron IGNORE + // `--inspect` entirely — so the inspector never appears and `electron.launch` + // waits out its full timeout with no diagnosis. + // + // MEASURED: this leg has failed on every nightly since 2026-08-11, the day that + // fuse landed, and the CI call log shows the app itself starting normally — + // "DevTools listening on ws://…", then the renderer finishing load and the + // auto-updater reporting a result. The SHIPPED APP IS FINE; only Playwright's + // control channel is closed. + // + // Why the fuse's own comment says nothing depends on `--inspect`: it was + // measured by grepping the repo's tracked sources, and `--inspect` is injected + // at runtime by playwright-core's Electron launcher, not written in any file + // here. A source grep structurally cannot see that dependency. + // + // This throws rather than skipping: the coverage is genuinely lost while the + // fuse is off, and a skip would turn a real gap into a green tick. Throwing + // keeps the leg red — but red in seconds, naming the cause and the options. + const fuses = readFileSync(resolve(REPO_ROOT, 'electron-builder.yml'), 'utf8').replace( + /#[^\n]*/g, + '', + ); + if (/enableNodeCliInspectArguments:\s*false/.test(fuses)) { + throw new Error( + 'packaged.spec cannot drive the shipped binary: electron-builder.yml sets ' + + '`enableNodeCliInspectArguments: false`, so Electron ignores the `--inspect` ' + + 'flag Playwright needs to attach to the main process. This is a HARDENING vs ' + + 'TESTABILITY trade-off, not an app defect — the packaged app launches fine. ' + + 'Resolve by one of: (a) flip the fuse true and accept the re-opened ' + + 'Node-injection surface; (b) build a test-only package with the fuse on and ' + + 'assert separately that the RELEASE artifact has it off; or (c) drop these ' + + 'main-process assertions and cover the package another way. Until then this ' + + 'leg is red BY CONSTRUCTION and its 900 s hang was measuring nothing.', + ); + } + // HARD requirement: a real package must exist (no dev fallback here). Set the // flag ONLY around our own resolution and restore it immediately, so it can // never leak into preview.spec (same single-worker process) and force IT to