Skip to content

Add Vitest Electron BrowserProvider foundation#9276

Draft
hl662 wants to merge 8 commits into
vitest-certa-bridgefrom
nam/vcb-01-electron-redesign
Draft

Add Vitest Electron BrowserProvider foundation#9276
hl662 wants to merge 8 commits into
vitest-certa-bridgefrom
nam/vcb-01-electron-redesign

Conversation

@hl662
Copy link
Copy Markdown
Contributor

@hl662 hl662 commented May 8, 2026

Why

This is the first PR in the Vitest/Certa bridge stack. The goal is to establish the Electron renderer foundation before migrating larger full-stack packages.

The key requirement is that Electron renderer tests run inside a real Vitest browser runtime — not a custom Mocha-style renderer harness, not a fake vitest shim, and not a nodeIntegration-dependent test window.

This PR proves that path with core/electron as the first consumer.

What

  • Adds a new internal package: @itwin/vitest-certa-bridge

    • exposes a minimal package-root backend callback registration API
    • exposes @itwin/vitest-certa-bridge/electron-provider as a custom Vitest BrowserProvider
    • starts Electron, creates a preload-backed BrowserWindow, and navigates it to Vitest's browser-mode session URL
    • keeps nodeIntegration: false and contextIsolation: true
    • supports optional package-specific backendInitModule and preloadModule
    • clears registered callbacks during provider-session teardown
  • Migrates core/electron frontend integration coverage to Vitest browser mode

    • ElectronApp.test.ts now runs through the Electron BrowserProvider
    • backend/main ElectronHost tests remain on the existing spawned Electron-main model, with Vitest as the outer orchestrator/reporter
    • adds Vitest JUnit reporter config for backend and frontend integration results
  • Keeps the bridge package intentionally small

    • no full-stack migration helpers yet
    • no full browser automation APIs yet
    • no Electron-main-as-Vitest-runtime support yet
    • downstream packages still own their package-specific Vitest configs, globs, reporters, backend init modules, and preload modules
  • Cleans up obsolete core/electron test wiring

    • removes old Mocha/Certa/Webpack frontend test assets
    • removes obsolete test-framework/dev dependencies from core/electron
    • keeps backward-compatible deep CJS package exports for existing internal consumers while adding public Electron frontend/backend subpaths

Scope notes

This PR is intentionally the foundation layer only.

It proves:

  • real Vitest APIs work in an Electron renderer
  • a consumer preload can coexist with the bridge preload
  • renderer tests can call registered backend callbacks through the provider path
  • core/electron can consume the provider for renderer integration coverage

It does not attempt to migrate full-stack-tests/core yet. That migration belongs in a follow-up stack PR, where the package-specific Chrome + Electron test orchestration can be designed separately.

Validation

Targeted validation run locally:

cd tools/vitest-certa-bridge
rushx build
rushx test
rushx test:electron-provider
rushx lint
cd core/electron
rushx build
rushx lint
rushx test:integration

Also verified:

rush change --verify --no-fetch
git diff --check

Repo-wide lint was also run before opening the draft PR:

rush lint

It is not fully green due to an existing unrelated error outside this PR's touched packages:

@itwin/ecschema-metadata
core/ecschema-metadata/src/UnitConversion/UnitTree.ts:181:36
@typescript-eslint/no-redundant-type-constituents
'UnitConversion' is an 'error' type that acts as 'any' and overrides all other types in this union type

In that repo-wide lint run, @itwin/vitest-certa-bridge and @itwin/core-electron both completed successfully.

Expected/known non-blocking noise:

  • Electron prints an insecure Content-Security-Policy warning for the Vitest/Vite dev-session page. This is intentionally left visible rather than suppressed.
  • Full repo-wide rush test was previously run during this work and surfaced unrelated baseline failures outside this PR's touched packages. The branch-relevant deep @itwin/core-electron export-map breakage found during that run was fixed by adding backward-compatible deep CJS exports.

Follow-ups

  • Migrate a tiny full-stack-tests/core subset in the next stack PR.
  • Audit the full-stack-tests/core runtime split before adding more bridge API:
    • Chrome browser tests
    • Electron renderer tests
    • Chrome backend/server setup
    • Electron main/backend init modules
    • any authored tests that truly execute inside Electron main
  • Add Chrome/browser callback transport to @itwin/vitest-certa-bridge only if the full-stack migration proves a reusable transport is needed.

Nambot 🤖 (powered by OpenAI GPT-5)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new internal @itwin/vitest-certa-bridge package to provide an Electron-backed Vitest BrowserProvider (with a preload-based backend-callback bridge), and migrates @itwin/core-electron renderer integration testing from Certa/Mocha-style wiring to Vitest browser mode.

Changes:

  • Added @itwin/vitest-certa-bridge, including an Electron BrowserProvider, provider-session main entry, and callback registry + smoke tests.
  • Migrated core/electron renderer integration coverage to Vitest browser mode; backend tests remain “spawned Electron main” but orchestrated by Vitest with JUnit output.
  • Removed obsolete core/electron Mocha/Certa/Webpack frontend test wiring and updated repo configs (Rush project list, browser-approved packages, lockfile, CODEOWNERS, change files).

Reviewed changes

Copilot reviewed 32 out of 33 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tools/vitest-certa-bridge/tsconfig.test-fixtures.json Adds TS build config for compiling provider test fixtures.
tools/vitest-certa-bridge/tsconfig.json Base TS config for the new bridge package (CJS output).
tools/vitest-certa-bridge/tsconfig.eslint.json ESLint project TS config for the new bridge package.
tools/vitest-certa-bridge/src/test/vitest.electron-provider.config.mts Vitest config to run the provider smoke test in Electron browser mode.
tools/vitest-certa-bridge/src/test/vitest.config.mts Default Vitest config for bridge package unit tests.
tools/vitest-certa-bridge/src/test/fixtures/electron-provider-user-preload.ts Fixture preload used to validate preload composition.
tools/vitest-certa-bridge/src/test/fixtures/electron-provider-backend-init.ts Fixture backend init that registers callbacks for the smoke test.
tools/vitest-certa-bridge/src/test/electron-provider-smoke.test.ts Smoke test verifying real Vitest APIs in Electron renderer + backend callback bridge.
tools/vitest-certa-bridge/src/test/bridge.test.ts Unit tests for the callback registry/dispatch behavior.
tools/vitest-certa-bridge/src/index.ts Public package-root exports for callback registration APIs.
tools/vitest-certa-bridge/src/electron/types.ts Defines provider options surface for Electron BrowserProvider.
tools/vitest-certa-bridge/src/electron/provider.ts Implements the custom Vitest BrowserProvider that spawns Electron sessions.
tools/vitest-certa-bridge/src/electron/provider-session.ts Electron main-process entry that creates the BrowserWindow and preload bridge.
tools/vitest-certa-bridge/src/callbackRegistry.ts Implements global callback registry + dispatch/token validation.
tools/vitest-certa-bridge/README.md Documents provider usage, callback bridge, and security model.
tools/vitest-certa-bridge/package.json New package metadata, build/test scripts, and export map.
tools/vitest-certa-bridge/eslint.config.js ESLint configuration for the new bridge package.
rush.json Registers @itwin/vitest-certa-bridge as a Rush project.
core/electron/vitest.frontend.config.mts Adds Vitest browser-mode config using the new Electron provider for renderer tests.
core/electron/vitest.config.mts Adds Vitest config for backend integration orchestration + JUnit output.
core/electron/src/test/frontend/utils/webpack.config.js Removes legacy Webpack bundling for Certa-based renderer tests.
core/electron/src/test/frontend/utils/certa.json Removes legacy Certa config for renderer integration tests.
core/electron/src/test/frontend/utils/backend.ts Updates renderer-backend init for ElectronHost startup under the new flow.
core/electron/src/test/frontend/ElectronApp.test.ts Migrates renderer integration tests from Chai/Mocha to Vitest APIs.
core/electron/src/test/backend/RunElectronBackendTests.ts Uses Vitest as the orchestrator and improves spawned Electron process timeout/cleanup.
core/electron/src/test/backend/ElectronHost.test.ts Adjusts ElectronHost tests for updated startup options and more stable maximize/unmaximize assertions.
core/electron/src/test/backend/.mocharc.json Removes Mocha config (backend integration now run via Vitest config).
core/electron/package.json Switches integration test scripts to Vitest, adds ESM build, and adds export map/deep exports.
common/config/rush/pnpm-lock.yaml Locks new dependencies (@vitest/browser, vitest, bridge package) and removes Certa/Mocha deps from core/electron.
common/config/rush/browser-approved-packages.json Approves the new bridge package and broadens allowed categories for some testing/tooling deps.
common/changes/@itwin/vitest-certa-bridge/nam-vcb-electron-provider_2026-05-07-15-48.json Change entry for introducing the bridge package.
common/changes/@itwin/core-electron/nam-vcb-01-electron-redesign_2026-05-07-19-48.json Change entry for migrating Electron renderer integration coverage to Vitest.
.github/CODEOWNERS Adds code ownership for the new bridge package paths.
Files not reviewed (1)
  • common/config/rush/pnpm-lock.yaml: Language not supported

Comment on lines +21 to +27
/** Resolve the Electron provider main-process entry point from this package's compiled CJS output. */
function getProviderSessionEntryPath(): string {
// __dirname is either lib/cjs/electron or lib/esm/electron. Electron main runs under CJS,
// so always target the CJS provider-session entry.
const pkgRoot = path.resolve(__dirname, "../../..");
return path.join(pkgRoot, "lib", "cjs", "electron", "provider-session.js");
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nambot (powered by OpenAI GPT-5) — Good catch. I changed the provider entry to avoid __dirname so the ESM build can be imported directly by Vitest.

The provider now uses createRequire(...) plus package export resolution to locate the compiled CJS provider-session.js, which keeps the Electron main-process entry on CJS without relying on CJS-only globals in the ESM provider module. I also validated the ESM import directly with:

node --input-type=module -e "const mod = await import('./lib/esm/electron/provider.js'); console.log(typeof mod.default)"

Comment on lines +121 to +125

const cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), `vitest-electron-${sessionId}-`));
const electronBin = require("electron/index.js");
const sessionEntry = getProviderSessionEntryPath();
const electronArgs = [...(this._options.electronArgs ?? []), sessionEntry];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nambot (powered by OpenAI GPT-5) — Fixed. The provider no longer uses the ambient CommonJS require global to resolve Electron.

It now creates a local require via Node's createRequire(...) and calls moduleRequire("electron/index.js"), which works from the ESM-loaded provider while still returning Electron's executable path.

electronProcess.on("exit", (code, signal) => {
this._sessions.delete(sessionId);
cleanupCacheDir(cacheDir);
if (!settled && code !== 0 && code !== null)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nambot (powered by OpenAI GPT-5) — Fixed. The startup wait now rejects on any pre-ready Electron process exit, including code === 0 and signal exits.

That means openPage() no longer waits until the timeout if Electron exits before printing the provider ready marker.

Comment on lines +140 to +142
// Vitest browser mode executes test modules inside a tester iframe. Load the preload
// in subframes too so renderer tests can reach the callback bridge from that iframe.
nodeIntegrationInSubFrames: true,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nambot (powered by OpenAI GPT-5) — I kept this setting, but tightened the comment/docs because the tradeoff is intentional for Vitest browser mode.

Vitest executes the test module inside a same-origin tester iframe. Without loading the preload into that subframe, core/electron's renderer test loses window.itwinjs and falls back to require("electron"), which fails with require is not defined under nodeIntegration: false.

The BrowserWindow still has nodeIntegration: false and contextIsolation: true; this option is being used so the preload/contextBridge surface is present in Vitest's tester iframe, not so renderer tests can depend on Node globals. I updated the inline comment and README to make that explicit.

Comment on lines +9 to +15
const cacheDir = process.env.ELECTRON_CACHE_DIR;
await ElectronHost.startup(cacheDir ? {
iModelHost: {
cacheDir,
profileName: `renderer-${process.pid}`,
},
} : undefined);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nambot (powered by OpenAI GPT-5) — Fixed. The core/electron backend init now reads VITEST_ELECTRON_CACHE_DIR first, with ELECTRON_CACHE_DIR as a compatibility fallback.

I also have the provider populate both env vars so package-local init modules can use either name while we settle the provider contract.

Comment on lines +22 to +32
"exports": {
"./ElectronFrontend": {
"types": "./lib/cjs/ElectronFrontend.d.ts",
"import": "./lib/esm/ElectronFrontend.js",
"require": "./lib/cjs/ElectronFrontend.js"
},
"./ElectronBackend": {
"types": "./lib/cjs/ElectronBackend.d.ts",
"import": "./lib/esm/ElectronBackend.js",
"require": "./lib/cjs/ElectronBackend.js"
},
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nambot (powered by OpenAI GPT-5) — I don't think we should add a root export here.

@itwin/core-electron did not previously define main, module, or a runtime package-root entrypoint, and src/core-electron.ts is explicitly marked docs-only with "Do not import it in real code!" because it mixes frontend and backend exports. Adding "." now would make that invalid docs barrel look like a supported runtime API.

The intended runtime contract in this PR is the narrower pair of subpaths: @itwin/core-electron/ElectronFrontend and @itwin/core-electron/ElectronBackend, plus temporary deep-CJS compatibility exports for existing internal consumers.

Comment thread .github/CODEOWNERS
/test-apps/imodel-from-reality-model @iTwin/itwinjs-core-display

/tools @aruniverse @wgoehrig
/tools/certa @aruniverse @wgoehrig
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aruniverse, should we still be adding Bill as code owner in core ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Nambot (powered by OpenAI GPT-5) — I added Bill here to preserve the existing /tools ownership, not because the new package specifically needs an extra owner.

/tools is already owned by @aruniverse @wgoehrig, and this more specific /tools/vitest-certa-bridge rule overrides that broader rule. If we only listed @hl662, we'd accidentally drop the existing tools owners for this path. Happy to narrow it if you/Arun think this package should have a different owner set.

Comment thread core/electron/src/test/backend/ElectronHost.test.ts
Comment thread core/electron/package.json
Comment thread tools/vitest-certa-bridge/src/electron/types.ts Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants