crash.spec spawns child Electrons that defaulted to the shared userData
profile; under parallel workers that contended with other test-spawned
Electrons (autoupdater etc.). Each child now gets a mkdtemp --user-data-dir
that afterEach removes (even on timeout, since the kill+rmSync runs in the
hook).
api-autoupdater-darwin: 'import * as express' under vite SSR gives a
namespace object, so express() fails with '__vite_ssr_import_N__ is not a
function'. Use 'import = require()' for express and psList which are
called directly as functions.
search: passes the path raw and os.tmpdir() may not be the right tmp inside
the CI container; query: {out} URL-encodes properly and userData is the
per-worker mkdtemp we control.
The 'does not crash when closed via window.close()' test (added in #47933)
listens for 'blur' during WebContents destruction; on macOS that Emit runs
a microtask checkpoint after weak_factory_ is already torn down, so any
re-entrant Destroy() -> GetWeakPtr() DCHECKs at base/memory/weak_ptr.h:298.
Skipped with a TODO until the native lifecycle is fixed.
Also rewrites the 'accepts existing webContents object' count assertion to
compare id sets so still-closing contents from prior tests don't skew it.
- api-in-app-purchase: wrap in ifdescribe(darwin) instead of early-return,
which left an empty suite that vitest treats as an error
- api-browser-view: custom afterEach nulls w before defer() callbacks run;
call runCleanupFunctions() first so defer(() => w.removeBrowserView(...))
sees a live window
- api-web-contents-view: afterEach used contents.close() without awaiting
destruction, so the next test's getAllWebContents() count included
still-closing leftovers; use cleanupWebContents() which awaits 'destroyed'
- modules/esm: run the ESM-vs-CJS key comparison in a child Electron (outside
vite's alias) via a .mjs fixture, sort both lists, and filter Node's
CJS-interop 'default'/'module.exports' keys before comparing
makeBindingWindow now lazily creates one window per describe and swaps
preloads via session.registerPreloadScript/unregisterPreloadScript instead
of constructing a fresh BrowserWindow per test. ~21s -> ~9s for 144 tests.
no-locals is stricter than plain @remote: closures may not reference
ANY identifier declared in the file (imports from anywhere, or file-scope
const/let/var/function/class). JS/DOM globals are fine. Applied to
callWithBindings in api-context-bridge.spec.ts, whose closures are
executeJavaScript'd in a page world with no __rt shim and no require.
makeBindingWindow stringifies its bindingCreator closure into a preload
script; under vite the captured 'contextBridge' import becomes
__vite_ssr_import_N__.contextBridge. Tag makeBindingWindow /** @remote */,
run rewriteForRemoteEval on the closure, and declare 'const __rt = renderer_1'
in the preload so __rt.contextBridge resolves.
remote-tools.ts re-exports contextBridge/ipcRenderer/webFrame from
electron/renderer — undefined at runtime in the main process, but typed
correctly for closures that only ever execute in a preload.
callWithBindings closures only reference their 'root' param, so no change
needed there. 144/144 pass.
Lazy-fork once, await a 'ready' handshake, then post each test body to the
same long-lived child. The fixture no longer exits after each message; it
posts {ok:false} on failure and keeps running, and exits cleanly on a
{type:'shutdown'} message sent from afterAll.
Functions declared with a /** @remote */ JSDoc (or const/for-of bindings
so tagged) are treated like itremote/remotely: any closure passed to them
is checked for imports that don't come from ./lib/remote-tools.
api-net.spec.ts's itUtility stringifies its closure and posts it to a
utility-process fixture, so it's the same __vite_ssr_import_ problem.
- itUtility now runs rewriteForRemoteEval(fn) before posting
- the fixture declares a local __rt spreading net-helpers + electron/main
- the for-of loop that aliases itUtility as 'test' is tagged @remote
- remote-tools.ts re-exports net, session, http, defer, and the net-helpers
- api-net.spec.ts imports those via remote-tools (closure bodies unchanged)
api-net: 252 passed / 2 todo.
Both passed without actually waiting for the condition; now that waitUntil
takes ctx.signal they at least stopped on abort, but the assertions were
still never gating the test.
Same ESM-vs-CJS stub issue as release-notes: sinon.stub on
require('node:child_process') doesn't intercept the ESM import that
version-bumper.ts uses under vite. vi.mock('node:child_process', {spy:true})
does. sinon is no longer used anywhere in spec/.
Under vite, notes.ts imports spawnSync via ESM; sinon.stub on the CJS
require() object doesn't intercept that binding. vi.mock('node:child_process',
{spy: true}) replaces the module in vite's graph so vi.mocked(cp.spawnSync)
.mockImplementation() applies to the import notes.ts sees.
close.html and unload.html previously wrote to __dirname + '/close'|'/unload'
inside spec/fixtures/api/ itself. Pass the output path via ?out= query param
and use a pid+timestamp tmp file so parallel workers and retries never race.
16 inner it() calls in api-browser-window.spec.ts were wrapped in an outer
it() or ifit() where the author meant describe()/ifdescribe(). Under mocha
these inner tests were registered as siblings at collection time; under
vitest they throw. Also removes two unused chai imports in api-safe-storage
left over from moving chai.use() to setup.ts.
Mocha silently tolerated it() inside another it()/ifit() body; vitest does
not. The rule tracks test-call nesting depth and flags any inner definition.
Swaps expect/setTimeout/BrowserWindow imports in the 5 files flagged by
remote-tools-imports/no-foreign-imports-in-remote-closure so their
stringified closures resolve via the __rt shim. Import-line changes only.
remote-tools-imports/no-foreign-imports-in-remote-closure flags any
identifier inside an itremote()/remotely() closure that is bound to an
import from somewhere other than spec/lib/remote-tools. Those bindings
get rewritten to __vite_ssr_import_N__ by vite's SSR transform and fail
when the closure is stringified and eval'd in a remote context.
Skips type-only imports and type-annotation positions. Currently flags
50 sites across 5 files (node, webview, chromium, fuses, api-native-image).
vite's SSR transform rewrites import bindings to __vite_ssr_import_N__,
which breaks closures that are stringified and eval'd in a remote context.
remote-tools.ts re-exports the common modules (path, fs, url, expect, ...)
so spec files can 'import { path, expect } from ./lib/remote-tools' and
closure bodies stay unchanged. runRemote/remotely regex-replace every
__vite_ssr_import_N__ with __rt (a renderer-side object whose keys mirror
the remote-tools export names), so __rt.path.join(...) etc. resolve with
no property-name ambiguity.
asar.spec.ts: 170 failing itremote tests -> 0, import-line changes only.
vitest's onTestFinished runs *after* afterEach (contrary to the earlier
assumption), so defer() callbacks saw windows already destroyed by
afterEach(closeAllWindows). And runCleanupFunctions didn't clear its
queue on throw, so one bad defer cascaded into every subsequent test.
- runCleanupFunctions now splices the queue before iterating and wraps
each cleanup in try/catch, aggregating errors.
- closeAllWindows runs runCleanupFunctions() first, so the 190+ existing
afterEach(closeAllWindows) call sites get mocha's ordering (defers run,
then windows close) without modification. setup.ts's onTestFinished
remains as a fallback for tests that defer() without closeAllWindows.
- cleanupWebContents guards against already-destroyed contents.
vitest passes a context object as the first arg to hook callbacks, so
afterEach(closeAllWindows) effectively called closeAllWindows(ctx) - a
truthy value that triggered the unused assert-no-windows-remain path,
producing unhandled rejections. afterAll(closeAllWindows) failed outright
with a FixtureParseError because vitest parses the param list.
No caller ever passed the argument explicitly, so the param is removed.
Await each CDP step in sequence and use Runtime.evaluate on the attached
session after Debugger.enable is acknowledged, so Debugger.scriptParsed is
guaranteed to fire regardless of page-load timing. 0/10 flakes (was ~3/5).
The closure at spec/api-app.spec.ts:2282 is stringified and eval'd inside
a utility-process fixture. Under vite's SSR transform the captured import
bindings become __vite_ssr_import_N__ refs that don't exist in the fixture.
Inline require() calls survive toString() intact.
- spec/_vitest_runner/package.json: name/productName match spec/package.json
so app.name assertions hold
- worker-entry: userData is <mkdtemp>/<app.name>/ so getPath('userData')
still includes the app name
- net-helpers: import defer from defer-helpers and own listen() directly,
so the utility-process fixture (which ts-node-requires net-helpers) no
longer pulls in spec-helpers -> vitest
vite's resolve.alias in object form matched the bare 'electron' specifier
but not 'electron/main' etc. The array-with-regex form matches all four.
Also extract defer/runCleanupFunctions into spec/lib/defer-helpers.ts so
setup.ts doesn't transitively import 'electron/main' (setupFiles go
through a slightly different SSR load path than test files).
- refresh-page fixture served mocha.js as a 'large JS file' over a custom
protocol; point it at chai.js instead since mocha is no longer a dep
- drop descriptive 'mocha' mentions from comments
This was a win-arm64 workaround for the mocha runner: process.exit() could
hang in the single Electron process, so the suite SIGTERMed itself after
printing the failure count to stdout for script/spec-runner.js to scrape.
Under vitest, the process that decides and reports the exit code is the
plain-Node vitest CLI; Electron instances are pool workers that the pool
tears down. The workaround no longer applies.
- allowOnly: !CI matches mocha's forbidOnly
- retry: 3 on CI matches mocha's CI retries
- runCleanupFunctions via ctx.onTestFinished so defer()-ed cleanups run
before test-file afterEach hooks, matching the per-suite hook ordering
in spec/index.js