* test: assert with vitest's matchers instead of chai
Every assertion moves from chai's chainable language to vitest's own
matchers, so an assertion is always a call and never a bare property read:
- `to.equal` → `toBe`, `to.deep.equal`/`eql` → `toEqual`, `to.be.true` →
`toBe(true)`, `to.have.property` → `toHaveProperty`, `to.include` →
`toContain`/`toMatchObject`/`toContainEqual`, `to.be.a('string')` →
`toBeTypeOf('string')`, `instanceOf` → `toBeInstanceOf`, `lengthOf` →
`toHaveLength`, `above`/`least`/`below`/`most` → `toBeGreaterThan` and
friends, `match` → `toMatch`, `throw` → `toThrow`, `oneOf` → `toContain`
with the operands swapped, `closeTo(e, d)` → `Math.abs(x - e) <= d`,
`members`/`keys` → `arrayContaining`/`Object.keys`. A chain that kept
going after `.property()` or `.a()` becomes one matcher where vitest has
one (`toHaveProperty(key, expect.any(String))`) and separate assertions
otherwise. chai-as-promised becomes `.resolves`/`.rejects`, a failed
manual check is `assert.fail()` rather than `new chai.AssertionError`.
- vitest reports promise assertions that are not awaited; the app badge,
webview zoom and `import('electron')` specs had some that never ran and
now do (the service worker badge ones waited for a count the worker had
already cleared, so those just check the worker's answer; the zoom fixture
issued loadURL() from dom-ready, which aborts, and now waits for the load).
- Code that runs outside the spec module graph (itremote bodies in a
renderer, the utility-process net harness, two crash-case apps) is
CommonJS and vitest refuses `require('vitest')`, so it loads the same
module through spec/lib/vitest-cjs.cjs.
- A lint rule (script/lint-plugins/no-chai-assertions.mjs) rejects chai
chains on `expect()`, and no-unused-expressions is on for the specs, so
neither form of property-style assertion can come back.
- chai, chai-as-promised, dirty-chai and their @types leave
spec/package.json; the refresh-page fixture served chai.js only as "a
large script" and serves the jquery fixture instead.
* test: return promises instead of taking mocha's `done` callback
vitest has no `done` parameter. The tests and hooks that used one now
return a promise instead; where the callback was only ever called as
`done()` it is the promise's `resolve`, and where it was also handed to an
error-first API or called as `done(error)` it keeps mocha's meaning
(reject on a truthy argument, resolve otherwise) through a local `done`.
* test: move the spec runner's own semantics out of the mocha shim
Three things the shim did are not mocha compatibility but how the Electron
specs need to run, whichever API they are written against: every hook and
test starts from a fresh macrotask so that Chromium's message loop turns
between them, `defer()`-ed cleanup runs before the enclosing suites' own
afterEach hooks, and spec/disabled-tests.json can switch a test off by its
title. Those now live in a small custom vitest runner
(spec/vitest/runner.ts) that applies to every test, and the shim only
translates mocha's API.
* test: write the specs against vitest's API instead of mocha's
Mechanical conversion of every spec file and helper:
- `describe`/`it`/`beforeEach`/... are imported from 'vitest' rather than
taken from the runner's globals; `before`/`after` become
`beforeAll`/`afterAll`.
- `this.timeout(n)` becomes the `{ timeout: n }` option of the test or
suite (a hook's second argument; hooks inside a suite that had a timeout
get it explicitly, since vitest's suite option only reaches the tests), `this.retries(n)` `{ retry: n }`, and
`this.skip()` the test context's `ctx.skip()`. The two `before` hooks that
skipped a whole suite now compute the condition up front
(`describe.runIf`) or per test. `function () {}` callbacks that only
existed for `this` are arrows again.
- `ifit`/`ifdescribe` map to `it.skip`/`describe.skip`, mocha's chained
`it(...).timeout(n)` and `this.bail()` are gone, and the two helpers that
read `this.test` use the context's task state.
* test: remove the mocha compatibility layer
Nothing imports mocha's API any more, so spec/vitest/mocha-compat.ts and the
setup file that installed its globals go, together with @types/mocha and the
Mocha type overloads in spec/ambient.d.ts. Timeouts are vitest's own again
(30 s per test and hook, ELECTRON_SPEC_TIMEOUT raises it for the sanitizer
jobs, replacing MOCHA_TIMEOUT), the junit path variable becomes
ELECTRON_SPEC_JUNIT_FILE, and `closeAllWindows` loses the parameter that a
hook's context argument would otherwise land in.
* refactor: add a native peer helper for cppgc wrappers
cppgc wrapper destructors run during sweeping, where they must not touch
other GC objects, run JavaScript, or tear down native registrations.
Debugger, Notification and WebContents each hand rolled an off-heap peer
for this.
Add NativePeer<Wrapper>, which owns native registrations, resources, and
independent V8 roots for a wrapper. The wrapper's deleter only queues the
peer. Queued peers are released and deleted from a posted task. At
shutdown they are released while V8 can still run and deleted after the
isolate is disposed, so peer teardown never runs inside a cppgc finalizer
and nothing leaks.
* chore: apply review feedback
Co-authored-by: Samuel Attard <sam@electronjs.org>
* chore: apply review feedback
Co-authored-by: Samuel Attard <sam@electronjs.org>
---------
Co-authored-by: Samuel Attard <sam@electronjs.org>
* test: run the spec suite with vitest
Replace the in-process mocha runner with vitest driving a pool of Electron
processes.
- spec/vitest/electron-pool.ts is a vitest pool whose workers are
'electron spec/ --vitest-worker' browser processes talking to the CLI
over a Node IPC channel (JSON, see ipc-serialization.ts, because the
host Node cannot read Electron's newer V8 wire format).
- spec/index.js keeps the pre-ready process setup and then hands the main
process to vitest's worker runtime (spec/vitest/worker.ts).
experimental.viteModuleRunner is off, so spec files are imported by
Electron's own Node exactly as before (native ESM, type stripping, no
Vite transform).
- spec/vitest/mocha-compat.ts provides mocha's BDD globals (describe/it/
before/after, this.timeout()/this.skip(), done callbacks, resettable
timeouts, hooks in declaration order, a macrotask between runnables) on
top of vitest, so the spec files run unchanged.
- Two projects run in sequence: everything not tagged 'serial' across
several workers, then the 'serial' suites one file at a time.
- script/spec-runner.js translates --files/-g into vitest filters, wraps
the CLI in dbus_mock.py on Linux and reruns failures from vitest's JUnit
report; leftover Electron processes are killed once per run
(global-setup.js) and per worker process group.
- spec-timings.json for shard balancing comes from timings-reporter.ts;
mocha, its reporters and ELECTRON_FORCE_TEST_SUITE_EXIT are gone.
* test: run the macOS app activation, hide and dock specs serially
app.isActive(), app.hide()/show() and the dock are machine-wide state, so
these suites cannot share the screen with other spec processes. The
isActive spec also assumed the process starts out inactive, which a
freshly launched worker on the CI hosts is not; hide first in that case.
* build: drop unused dev dependencies and the abstract-socket native module
Removes dependencies that nothing in the tree uses any more:
* sinon / @types/sinon: no spec imports them.
* check-for-leaks: only wired to `prepack`, and the root package is
private and never packed.
* null-loader: the only rule using it matched against an always-empty
`ignoredModules` list in the webpack config.
* @types/uuid: uuid ships its own types since well before the version
we depend on.
* q, coffeescript, mkdirp: each backed a single test of third-party
module behaviour. The `require` app-directory test now uses
dirty-chai (any module that only lives in spec/node_modules works,
it is exercising module.paths, not the module), and the mkdirp asar
test is replaced by a `fs.mkdirSync(..., { recursive: true })` case
next to the existing mkdirSync test.
Also drops abstract-socket. dbus-native only used it to connect to
`unix:abstract=` bus addresses, and Node.js has supported abstract
sockets natively via a leading NUL byte since 20.8, so dbus-native is
now patched to use net.createConnection instead. That lets us delete
the abstract-socket patch, its resolutions entry, the dependenciesMeta
`built` opt-in, the packageExtensions block in .yarnrc.yml and the
Linux-only `npm rebuild abstract-socket` step in the spec runner.
abstract-socket itself still appears in the lockfile as dbus-native's
optional dependency (patches cannot change resolution), but it is no
longer built or required by anything.
No-Verification-Needed: dependency, test and build-tooling changes only; no product source touched
* build: replace uuid, basic-auth and temp with Node.js built-ins
* uuid -> crypto.randomUUID() across the specs.
* basic-auth -> a small parseBasicAuth() helper in spec/lib/net-helpers,
used by the two specs that need to read Basic credentials.
* temp -> fs.mkdtempSync in release.ts and in the asar copyFile specs.
Also removes script/doc-only-change.js, whose last caller went away with
the AppVeyor configs.
No-Verification-Needed: spec, release-tooling and manifest changes only; no product source touched
* build: use webpack's BannerPlugin instead of wrapper-webpack-plugin
wrapper-webpack-plugin is a webpack 4 era plugin that still hooks the
deprecated optimizeChunkAssets hook. webpack 5's built-in BannerPlugin
can prepend a raw header and (with `footer: true`) append a raw footer,
and instances run in registration order, so the profiling wrapper and
the try/catch wrapper nest exactly as before. The only difference in the
emitted bundles is a newline between each wrapper and the bundle body.
No-Verification-Needed: webpack build config only; emitted bundle diffed against the previous plugin's output (whitespace-only), production output checked for nesting and syntax
* build: vendor the default app's octicons
The default app uses five icons and an 88 byte stylesheet out of
@primer/octicons, and pulling them out of node_modules was the only
place the GN build reached into node_modules for shipped content. Copy
the files (MIT licensed, notice included alongside) into
default_app/octicon so they are plain static sources like the rest of
the default app, and drop the dependency and the extra copy target. The
files end up at the same paths inside default_app.asar as before.
No-Verification-Needed: vendored files are byte-identical to the octicon files the existing build already ships at the same asar paths
* build: drop got and dedupe @electron/fiddle-core
The release scripts were the only direct users of got, and every call
was a plain GET/HEAD/POST that global fetch covers: HEAD requests use
`redirect: 'manual'` in place of `followRedirect: false`, and the
non-2xx handling that previously relied on `throwHttpErrors: false` now
checks `response.status` explicitly. The asset manifest in release.ts is
untouched.
got@11 was also still being kept alive by @electron/fiddle-core@1, which
the root package depended on while .github/workflows already used ^2.
Aligning the root on ^2 removes the second copy of fiddle-core and
@electron/get along with the whole got@11 dependency chain (41 packages).
fiddle-core 2 is ESM-only; the CommonJS spec runner loads it through
require(esm), which is available on the Node 22.21 CI uses and anything
newer than 22.12.
No-Verification-Needed: release tooling, spec runner and manifest changes only; no product source touched
* test: replace express in the autoUpdater specs with a small router
The two autoUpdater specs were the only users of express, and between
them they use `use()`, `get()` with one `:param` route, `res.status()`,
`res.send()`, `res.json()` and `res.download()`. spec/lib/http-server-
helpers.ts provides exactly that surface on top of node:http, with file
responses still going through `send` (what express uses internally) so
HEAD, Range and content-type behaviour towards the updaters is unchanged.
`send` is bumped to the 1.x line express 5 was already using, since 0.x
serves .msix files as application/octet-stream rather than
application/msix.
Removes express, @types/express and 43 transitive packages.
No-Verification-Needed: spec-only change; helper exercised over HTTP for GET/HEAD/Range/json/204/404 routing
* test: depend on resedit directly instead of @electron/packager
The asar integrity spec only used @electron/packager for its resedit
wrapper, and only the branch of that wrapper which embeds the integrity
resource on Windows. Do that directly with resedit (the library the
wrapper is built on), which is a dozen lines, and drop packager along
with the 24 signing/notarizing/downloading packages it brings in.
No-Verification-Needed: spec-only change; resource embedding round-tripped against a real PE locally
* test: replace the last temp usage in the protocol code cache spec
The `temp` package was dropped from the root package.json but
api-protocol-spec.ts still required it inline, so every test shard
that loads that file failed at spec-runner startup.
No-Verification-Needed: test-only change
The artifact is uploaded unarchived and named update-patches.patch, so the
documented gh run download command fails with 'not a valid zip'; fetching
the artifact's zip endpoint returns the raw patch.
No-Verification-Needed: documentation only
* ci: upload audit findings as an audit-results artifact
* docs: name the workflows that upload the audit-results artifact
* ci: upload audit results unzipped
* ci: retrigger checks
---------
Co-authored-by: Claude <noreply@anthropic.com>
Adds a new skill mirroring the Chromium upgrade skill, adapted for
Node.js rolls. Covers patch conflict resolution, build fix workflow,
commit guidelines, and documents high-churn patches and major version
upgrade patterns (V8 bridge patch deletions, BoringSSL complexity).
Consolidates the root .eslintrc.json and five nested configs (build,
script, docs, default_app, spec) into a single .oxlintrc.json at the
repo root. script/lint.js now shells out to the oxlint binary from
node_modules/.bin instead of using the ESLint Node API, and emits
GitHub Actions annotations directly via --format=github in CI
(replacing the deleted eslint-stylish problem matcher).
Oxlint has no markdown processor, so the ESLint-based lint of JS code
blocks in docs/**/*.md is replaced with an inline regex check for bare
Node.js builtin imports. This preserves the rule docs/.eslintrc.json
was originally added for in #42113; the rest of the standard ruleset on
docs code blocks was already being enforced in parallel by
lint-roller-markdown-standard.
ci: upload patch conflict fix as artifact in apply-patches
When patch-up.js cannot auto-push the 3-way-merged patch diff (e.g. on
fork PRs), the checkout action already writes patches/update-patches.patch
and tells the user to check CI artifacts — but nothing was uploading it.
This adds the missing upload-artifact step to the apply-patches job so
the resolved diff is available for download, and documents in CLAUDE.md
that pulling this artifact and applying it with `git am` is the fast
path for fixing patch conflicts on PR branches without a full local sync.
Co-authored-by: Claude <noreply@anthropic.com>
Document semver/* and target/* label conventions so Claude Code
can automatically apply the right labels when opening PRs against
electron/electron. Includes a gh one-liner to discover currently
active target branches via label color.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>