What the agent does
The agent clones the extension's repository into its own cloud sandbox and maps what each part does with the chrome.* APIs. In a Manifest V3 starter like chibat/chrome-extension-typescript-starter, public/manifest.json declares a popup, an options page, a content script on <all_urls>, a service worker at js/background.js, and only the storage permission. src/popup.tsx reads the active tab with chrome.tabs.query, sets the badge with chrome.action.setBadgeText every time its counter changes, and sends { color: "#555555" } to the tab with chrome.tabs.sendMessage. src/options.tsx loads favoriteColor (default red) and likesColor (default true) with chrome.storage.sync.get and saves them with chrome.storage.sync.set. src/content_script.tsx listens on chrome.runtime.onMessage and changes the page background, and src/background.ts re-arms a 30-second setTimeout.
Then it finds what stops these files from being tested today. jest.config.js only transforms \.ts$ files with ts-jest, so popup.tsx, options.tsx and content_script.tsx cannot be loaded, and there is no jsdom environment for React. The only test, src/__tests__/sum.ts, checks sum(1, 1). Both pages also call createRoot(document.getElementById("root")!) when the module is imported, so a test that imports them crashes on a missing element. The agent extends the Jest transform to .tsx, adds jest-environment-jsdom and @testing-library/react, and either exports the Popup and Options components so tests can render them, or creates the #root element before importing; it lists that change in the pull request.
It mocks the Chrome APIs with jest-chrome, which provides typed jest.fn() stand-ins for the chrome global, and checks the installed Jest version against its peer range first, since the project is on Jest 29. The popup tests render the component, resolve tabs.query with a fake tab and check the URL appears, click count up and check setBadgeText was called with "1", and click change background and check sendMessage got the tab id and the colour. The options tests check the defaults load, a change is saved with both keys, and the Options saved. status appears and clears after a second, using fake timers. The content script tests dispatch messages with and without a color and check the background and the sendResponse text, and the service worker test advances fake timers by 30 seconds and checks the poll re-arms.
Last, it runs npm test until the suite passes and npm run build to confirm the webpack bundle in webpack/webpack.prod.js still builds with the changes, the same two commands the repository's build.yml workflow runs on Node 20 and 22.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Write Jest tests for this Chrome extension's popup, options page, content script and background service worker. Read the manifest and each source file first, and list every chrome.* call and what the UI does with its result. Use jest-chrome for the chrome APIs; check its peer dependency range against the installed Jest first and report any mismatch. Make the Jest config handle .tsx files and add a jsdom environment and React Testing Library if they are missing. If a page renders itself at import time, make the smallest change that lets a test render the component, and list it. Cover storage defaults and saves, tab queries, messages with and without the expected fields, badge updates and timers, using fake timers instead of waiting. Run npm test and npm run build until both pass, and open a pull request.",
"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 its own branch. For an extension shaped like this starter, the diff adds test files under src/__tests__/ for the popup, options page, content script and service worker, a Jest setup file that installs jest-chrome, the jest.config.js change for .tsx and jsdom, the new dev dependencies in package.json and package-lock.json, and the small export change that lets Popup and Options render in a test. The description lists each chrome.* call and the test that covers it.
The session transcript keeps every command with its output: npm ci, each npm test run with its failures and the final pass, and npm run build. The pull request's checks run the repository's build.yml on its Node matrix, which installs, builds and runs npm test.
Before merging, a reviewer checks the findings the tests surfaced, such as a popup that reads tab.url while the manifest grants only storage and relies on host_permissions for URL access, and checks that the export change did not alter what the built popup.js and options.js render.
Run it on every Chrome release
Chrome changes extension APIs on its own schedule, so a run per Chrome release is a cheap check: a scheduled job on your side sends POST /o/{org}/sessions with the repository in resources and a prompt to update the tests and the @types/chrome version, run them, and open a pull request only if something changed.
Set a webhook_url on the API key and Rush posts run.completed or run.failed to it when the turn ends, with a session_url that reads the result and any pull request.
The use case this job follows, with its automation and trigger: Write the missing tests.
Why run it on Rush?
Getting an extension testable means changing the Jest config, adding a DOM environment and resolving peer dependency ranges, which is exactly the kind of install that leaves a laptop's node_modules in a state nobody can reproduce. In a Rush sandbox the agent runs npm ci on a clean machine, adds what is needed, and the lockfile in the pull request is the one that produced the passing run.
Every command is kept with its output, so the reviewer can see each Jest failure that led to a config change and the final passing run, and the pull request's checks repeat it on the repository's own Node matrix.
Frequently asked questions
Do the tests run in a real Chrome?
No. These are unit tests in Jest with jsdom and a mocked chrome global, which run in seconds and catch logic errors in how the extension calls the APIs. For loading the unpacked extension in a real browser, ask for Playwright tests in input as a separate job.
Why jest-chrome instead of hand-written mocks?
It mirrors the chrome namespace with typed jest.fn() functions and event objects that support callListeners, so a test can fire runtime.onMessage the way Chrome would. If its peer range does not accept your Jest version, the agent reports it and asks before downgrading anything.
Will it change my extension's source code?
Only where a file cannot be imported in a test, such as a page that renders itself at import time. The agent makes the smallest change, usually exporting the component, and lists every non-test change in the pull request.
Does it work with Vite or Plasmo extensions?
Yes. The agent reads whichever bundler and test runner the repository uses. In a Vite project it would use Vitest with the same chrome mocks instead of adding Jest.