What the agent does
In an Express project like gothinkster/node-express-realworld-example-app, the test setup is Jest 29 running through Nx. project.json wires the api test target to @nx/jest:jest, and jest.config.ts matches files with testMatch src/**/(.)@(spec|test).[jt]s?(x) and transforms TypeScript through ts-jest with tsconfig.spec.json. The suite itself is small and concrete: service tests at src/tests/services/{tag,auth,profile,article}.service.test.ts plus src/tests/utils/profile.utils.test.ts, and src/tests/prisma-mock.ts builds the mocked Prisma client with jest-mock-extended. package.json scripts run through Nx: npm test is nx test, npm run build is nx build, npm run start is nx serve, all on npm with a 367 KB package-lock.json.
The migration starts by recording the baseline: npm test with the test count and timing, so the after numbers mean something. Config moves first: a vitest config replaces jest.config.ts with the same include pattern and the same tsconfig path alias behavior, and globals are enabled or imports are rewritten so describe and it resolve. The Nx target in project.json moves from @nx/jest:jest to the Vitest executor, which keeps npm test working as the entry point. The e2e project declares its own target with passWithNoTests, and the agent decides whether it converts or retires it and says which.
Then the test files. jest.mock calls become vi.mock, jest.spyOn becomes vi.spyOn, and the fake-timer and date APIs map onto vi.useFakeTimers with their modern behavior as the default. The prisma mock is the fiddly part: jest-mock-extended has a Vitest sibling, and the agent either adopts it or builds the mock with vi.fn so the service tests keep their current shape. Snapshots, if any, are regenerated rather than hand-edited, and the regenerated files are flagged in the pull request. Jest itself and @types/jest leave package.json, and package-lock.json is regenerated with the same npm the project already uses.
The proof is numerical. The agent runs npm test before and after and puts both counts in the pull request description, and the same count passing is the acceptance line. It also runs npm run build, because the esbuild target in project.json emits the server bundle and a stray jest global in production code would fail there. The diff should touch no controller, service or model logic; any hunk that does is called out for review.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "codex",
"input": "Migrate this project's tests from Jest to Vitest. Convert config, jest.* calls, module mocks, fake timers and snapshots. Make the same tests pass with the same count, remove Jest and its types, and open a pull request with the before and after test counts.",
"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
The pull request contains the Vitest config, the converted test files, the updated Nx target, and package.json minus jest, ts-jest, @types/jest and jest-mock-extended, with package-lock.json regenerated. Its description states the before and after test counts and the commands that produced them. npm test still works as the entry point, now running Vitest under the same Nx target name, and npm run build passes.
A reviewer checks three things: the counts match, no test lost its assertions in conversion, and no source file changed behavior. The prisma mock conversion is worth a close look, since that is where a lazily-typed mock can silently stop asserting. The session read returns the repository and pull request information, and the transcript holds both test runs, before and after, with their output.
Run it on every migration issue opened
When an issue asks for a framework migration like this one, your automation sends POST /o/{org}/sessions with the issue text and this prompt, and the migration starts without you bootstrapping anything locally. Install the Rush GitHub App on the repository first and authenticate with an API key; an Idempotency-Key header keeps a retried submission from starting two runs.
Keep the returned execution_id and read the session when the run settles, or receive the key's completion webhook. The before and after counts in the pull request description tell you at a glance whether it worked; if one test was deliberately dropped or split, the follow-up question goes to the finished turn and the messages endpoint wakes the parked sandbox to answer it.
The use case this job follows, with its automation and trigger: Hand off from your terminal.
Why run it on Rush?
A test-framework migration is repetitive, detail-heavy and easy to half-finish on a laptop that keeps getting used for other things. On Rush the agent has a dedicated sandbox, the before and after runs happen in the same environment, and the migration finishes while your machine does something else. The transcript holds both runs' output, so the equal-count claim is checkable evidence rather than a number typed into a description.
For several services on the same Jest version, submit one repository per run within your key's concurrency allowance and the whole fleet moves together. You review each pull request for the same three properties: equal counts, preserved assertions, no source changes. The useful proof is the before and after test output side by side.
Frequently asked questions
Does npm test still work after the migration?
Yes. The Nx target in project.json keeps the same name and moves to the Vitest executor, so the package.json scripts are unchanged. The agent runs npm test after the conversion as proof, not just the direct Vitest invocation.
What happens to the Prisma mock?
src/tests/prisma-mock.ts uses jest-mock-extended today. The agent either moves it to the Vitest equivalent package or rebuilds it with vi.fn, keeping the same mock shape the service tests rely on. That file deserves the closest review, since a weakly-typed mock can keep tests green while asserting nothing.
Are fake timers and snapshots handled?
Yes. jest.useFakeTimers and the date APIs map onto vi.useFakeTimers with modern behavior as the default, and any snapshots are regenerated and flagged in the pull request rather than edited by hand.
What if one test cannot be converted cleanly?
The prompt's rule is the same count passing. If a test must be split, renamed or dropped to hold that, the agent writes the reason in the pull request description, and you decide whether to accept it. A silent change in count means the run is not done.
Can I check on it after closing my laptop?
Yes. Save the execution_id the API returns and read the session when you are back, or receive the key's completion webhook. The parked sandbox is kept for 7 idle days, so a follow-up the next morning still wakes it.