What the agent does
The agent reads the test setup before it changes it, because in an Electron project the test runner is wired into the build. In an app shaped like electron-react-boilerplate/electron-react-boilerplate, package.json runs jest as test, builds with electron-vite build and starts with electron-vite dev --watch; jest.config.js sets testEnvironment to jsdom, maps .svg?react imports to .erb/mocks/svgComponentMock.js, image and font extensions to .erb/mocks/fileMock.js and stylesheets to identity-obj-proxy, adds release/app/node_modules and src to moduleDirectories, and transforms TypeScript with ts-jest resolved from .erb/tooling, a separate locked package that holds TypeScript 6 because the root project compiles with TypeScript 7, whose compiler API ts-jest cannot use. setupFiles points at .erb/scripts/check-build-exists.ts, which throws unless release/app/dist/main/main.js and renderer.js exist, and polyfills TextEncoder for JSDOM.
There are two tests, and they use Jest in different ways. src/__tests__/App.test.tsx renders src/renderer/App.tsx with @testing-library/react and @testing-library/jest-dom matchers; src/__tests__/updates.test.ts carries a /** @jest-environment node */ docblock, calls jest.mock on electron, electron-updater and electron-log, uses jest.resetModules in beforeEach and require inside each test to get fresh module state, and asserts that startAutoUpdates in src/main/updates.ts contacts the updater once in a packaged app and never in development. The agent maps each of those onto Vitest: vi.mock with factories, vi.resetModules, a // @vitest-environment node comment in place of the docblock, and @testing-library/jest-dom/vitest for the matchers.
Then it writes vitest.config.ts from electron.vite.config.ts, not from scratch: the renderer section already has the react() and svgr() plugins, which make the .svg?react mock unnecessary, and the release/app/package.json dependencies it externalizes. The Vitest config sets environment to jsdom, setupFiles to a ported check-build-exists and the matcher import, css handling in place of identity-obj-proxy, an alias for asset files in place of fileMock.js, and resolve.modules equivalents for release/app/node_modules. It removes jest, jest-environment-jsdom, @types/jest and identity-obj-proxy from the root devDependencies, removes ts-jest and eslint-plugin-jest from .erb/tooling/package.json and runs npm --prefix .erb/tooling install there as its README asks, swaps "types": ["node", "jest"] in tsconfig.json for vitest/globals or explicit imports, and replaces the eslint-plugin-jest block in eslint.config.js with eslint-plugin-vitest rules for src/**/__tests__/**.
It runs the sequence .github/workflows/test.yml runs: npm ci, npm run package (which builds and so creates the files the setup script checks for), npm run lint, npm exec tsc -- --noEmit and npm test, now vitest run. On Linux it also runs xvfb-run --auto-servernum npm run test:smoke, the Electron startup check, to prove the main process bundle still starts. The pull request changes the configs, the two tests, both package.json files and their lockfiles, and nothing in src/main or src/renderer beyond test files.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Migrate this Electron project from Jest to Vitest. Read package.json, jest.config.js, electron.vite.config.ts, tsconfig.json, eslint.config.js, .erb/tooling/package.json and its README, .erb/scripts/check-build-exists.ts and both files under src/__tests__ first. Write vitest.config.ts that reuses the renderer plugins from electron.vite.config.ts, keeps the jsdom environment, the build-exists setup check, the module directories and the stylesheet and asset handling the Jest config had. Port App.test.tsx to @testing-library/jest-dom/vitest matchers and updates.test.ts to vi.mock, vi.resetModules and a vitest-environment node comment, keeping every assertion. Replace the test script with vitest run, remove jest, jest-environment-jsdom, @types/jest and identity-obj-proxy from the root, remove ts-jest and eslint-plugin-jest from .erb/tooling and reinstall it, update the tsconfig types and the ESLint test block. Run npm ci, npm run package, npm run lint, npm exec tsc -- --noEmit, npm test and, under xvfb-run, npm run test:smoke, and fix what fails. Open a pull request that lists each Jest feature and its Vitest replacement and quotes the vitest summary. Do not change application code under src/main or src/renderer.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a pull request on a new branch. It adds vitest.config.ts, deletes jest.config.js, rewrites the two test files, edits package.json, package-lock.json, .erb/tooling/package.json and its lockfile, tsconfig.json and eslint.config.js, and leaves src/main/ and src/renderer/ untouched. The description is a table of Jest feature to Vitest replacement (jest.mock to vi.mock, the environment docblock to the environment comment, moduleNameMapper to aliases and plugins, ts-jest to Vite's own TypeScript handling) and quotes the vitest run summary.
The session read names the repository and the pull request URL, and the transcript keeps each command with its output: the package build, lint, type check, the Vitest run and the smoke test. A reviewer can see the first Vitest run if a mock behaved differently from its Jest version and what changed to fix it.
Before merging, a reviewer checks that the updates.test.ts assertions still cover the packaged and development branches of startAutoUpdates, that the build-exists check still runs before the suite, and that .erb/tooling no longer carries test tooling it does not need.
Run it on every repository still on Jest
Start one run per repository from a terminal or a script with this body and the repository in resources, with a request_id, one UUID kept per repository, so a retry returns the same run. The pull request URL arrives on the session, or in the run.completed webhook when the key has a webhook_url.
The sandbox parks after the turn with its node_modules and its built release/app/dist, so a follow-up such as "also move the smoke test under Vitest" continues in the same clone without a second npm ci.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
This migration touches two package manifests, a tooling package pinned to an older TypeScript, the build config and the ESLint config, and the only way to know it worked is to run the full test.yml sequence including npm run package and an Electron startup under a virtual display. On Rush that runs in the sandbox with every command's output kept, so the pull request can quote the Vitest summary and the smoke test from a run a reviewer can read.
Several Electron apps built from the same boilerplate can be migrated at the same time, each in its own sandbox, and each pull request carries the proof for that repository rather than for a template.
Frequently asked questions
Does it keep the .erb/tooling package?
Yes. The README there explains it exists because ts-node, ts-jest and typescript-eslint need TypeScript 6 while the root compiles with TypeScript 7. The agent removes only ts-jest and eslint-plugin-jest from it and leaves ts-node and the ESLint parser, which .erb/scripts/register.cjs and eslint.config.js still load.
What happens to the /** @jest-environment node */ docblock?
It becomes a // @vitest-environment node comment at the top of updates.test.ts, so that test keeps running in Node while App.test.tsx runs in jsdom from the config default.
Will it change how the renderer is built?
No. electron.vite.config.ts stays as it is; vitest.config.ts imports the renderer plugins from it so the test build and the real build agree on React and SVG handling.
Does it run the Electron smoke test?
Yes, under xvfb-run as the Linux job in test.yml does, because the migration edits package.json and the tooling package that .erb/scripts/register.cjs depends on, and a broken main bundle would show up there and nowhere else.