# Add Missing Tests to Every Pull Request in a React App

Yes. On each pull request an agent reads the React diff, writes the Vitest or Cypress tests it lacks, runs them in a sandbox browser and pushes them to the branch.

[Run this on Rush](https://cloud.getrush.ai/run/test-every-pull-request/react/), in a Project that has your repository.

## What the agent does

The agent starts from the diff against the base branch and the three test layers the app already has, because the right layer depends on what changed. In an app like cypress-io/cypress-realworld-app, the README's table is the map: unit tests in `src/__tests__/` and `src/utils/__tests__/` under Vitest, component tests next to components as `src/**/*.cy.tsx`, API specs in `cypress/tests/api/` and UI specs in `cypress/tests/ui/`. `vite.config.ts` gives Vitest a `jsdom` environment, `src/setup-tests.js` and `fileParallelism: false`, with a comment that the files share `data/database.json`. `cypress.config.ts` sets the component `specPattern` to `src/**/*.cy.{js,jsx,ts,tsx}` with `cypress/support/component.ts`, and the e2e `specPattern` to `cypress/tests/**/*.spec.{js,jsx,ts,tsx}` with `baseUrl` on port 3000 and the API on 3001.

For a changed function in `backend/database.ts` or `src/utils/transactionUtils.ts`, the test is a Vitest case in the existing file. `src/__tests__/transactions.test.ts` imports the database functions directly, calls `seedDatabase()` in `beforeEach` and asserts against `totalTransactions` and `transactionsPerUser` from `scripts/seedDataUtils.ts`; `src/utils/__tests__/transactionUtils.test.ts` builds a `fakeTransaction` with `shortid` and faker and checks `isRequestTransaction` and the query helpers. A new branch in `createTransaction`, which stores `amount * 100`, marks a payment `complete` and creates a `received` or `requested` notification, gets a case for each path it adds.

For a changed component, the test is a Cypress component spec beside it, in the style of `src/components/SignInForm.cy.tsx` and `TransactionDateRangeFilter.cy.tsx`, mounting the component and driving it through its `data-test` attributes. For a changed flow, it is a case in the matching UI spec: `cypress/tests/ui/new-transaction.spec.ts` begins with `cy.task('db:seed')`, registers `cy.intercept` aliases for `/users*`, `/transactions` and `/notifications`, reads users with `cy.database('filter', 'users')` and signs in with `cy.loginByXstate`, then drives the form with `cy.getBySelLike('amount-input')` and waits on `@createTransaction`. A new case follows that shape, waits on an alias rather than a number of milliseconds, and branches on `isMobile()` from `cypress/support/utils.ts` where the existing cases do. For a backend route change, a case in `cypress/tests/api/` calls the route and checks the status and body.

The agent runs each new test in the sandbox before pushing: `yarn test:unit:ci` for Vitest, `yarn cypress:run:component` for a component spec, and for an e2e case `yarn build:ci`, `yarn start:ci`, a wait on both ports and `yarn cypress:run --spec <file>` in Chrome. It uses Node 22.20.0 from `.node-version` and `yarn install --frozen-lockfile`, and runs `yarn types` and `yarn lint` so Prettier's 100 column rule and ESLint's Cypress plugin accept the new files. It does not change application code: a test that fails because the code is wrong stays out of the commit and is described in the comment. Then it pushes the test files to the pull request's branch and comments with the list.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "This pull request changes a React 18 app with an Express API in backend/ and three test layers: Vitest in src/__tests__ and src/utils/__tests__, Cypress component specs beside components as src/**/*.cy.tsx, and Cypress e2e specs in cypress/tests/ui and cypress/tests/api. Read the diff against the base branch and list the new or changed behavior no test covers. For each item write the test in the layer that can see it, following the existing file's style: seedDatabase() in beforeEach for database functions, data-test selectors through cy.getBySel for components, and cy.task('db:seed'), cy.intercept aliases and cy.loginByXstate for flows. Never wait on a number of milliseconds. Use Node 22.20.0 and yarn install --frozen-lockfile, then run yarn types, yarn lint and yarn test:unit:ci; run a component spec with yarn cypress:run:component and an e2e spec by building with yarn build:ci, starting yarn start:ci and running yarn cypress:run --spec <file>. Do not change application code: if a test fails because the code is wrong, leave it out and describe the failure. Push the tests to this pull request's branch and post a comment listing each test and the behavior it covers.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app/pull/123"
    }
  ]
}
```

1. **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.
2. **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.
3. **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 commit on the pull request's own branch that adds test files only: a new case in an existing file under `src/__tests__/` or `src/utils/__tests__/`, a new `*.cy.tsx` beside a component, or a new `it` block in a spec under `cypress/tests/`. No dependency is added, because Vitest, Cypress, Testing Library and faker are already in `devDependencies`. The comment on the pull request lists each test with the behaviour it covers and the command that ran it, with the browser for a Cypress spec.

The push re-runs `.github/workflows/main.yml`, so the `install` job runs the new Vitest cases and the five-container Chrome, Firefox and mobile matrix runs the new e2e case on GitHub. A reviewer checks that an e2e case waits on an intercept alias and not a fixed delay, that a Vitest case reseeds rather than relying on another file's state, and that a case the agent left out is explained in the comment as a possible bug in the change rather than a missing test.

The session keeps the record: the diff it read, each spec run with its summary, and the push. `GET /o/{org}/sessions/{id}` names the branch and pull request once the turn has finished, and a follow-up can ask the parked sandbox to add a mobile-viewport variant with `yarn cypress:run:mobile`.

## Run it on every pull request opened

Add an automation to the Project for this repository with this prompt and a `github` trigger on `pull_request.opened`, with `develop` in `branch_patterns` since that is the repository's default branch. The trigger appends the pull request after the prompt. Only pull requests by an owner, member or collaborator fire it, and ones from forks never do, which also keeps the run's push on branches your team owns.

Leave `read_only` off, because the result is a commit on the pull request's branch. Add `pull_request.synchronize` only if you want tests regenerated on every push; a run that finds nothing uncovered pushes nothing and says so. `max_runs_per_hour` on the automation bounds a burst of pull requests from `renovate.json`.

## Why run it on Rush?

Writing an end-to-end test for this app means a built frontend, the Express API, a reseeded JSON database and a real browser driving a multi-step form, plus a type check and a lint pass before the file is accepted. Doing that for every pull request on a laptop is why coverage slips. A Rush run has its own sandbox with a browser, builds from a clean clone of the pull request's branch, and keeps the output of every spec it ran.

The tests are in the repository's own idiom because the agent reads `new-transaction.spec.ts`, `SignInForm.cy.tsx` and `transactions.test.ts` before writing, so a reviewer recognises them. Several pull requests get tests at once, each in its own sandbox, and each gets a commit that the GitHub matrix then runs across browsers and viewports.

## Frequently asked questions

### Will it push to my branch?

Yes. The commit holds test files only and lands on the pull request's branch, through the Rush GitHub App installed on the repository. The comment lists every file. If you prefer a separate pull request, say so in the prompt and it opens one against your branch instead.

### Which layer does it pick for a changed component?

A Cypress component spec beside the component, as `SignInForm.cy.tsx` and `TransactionTitle.cy.tsx` are, mounted through `cypress/support/component.ts` with Vite as the bundler. A Vitest case is used instead only when the change is a function with no rendering, such as something in `src/utils/`.

### Why does it avoid a fixed wait?

Because the existing specs wait on `cy.intercept` aliases such as `@createTransaction` and `@notifications`, and `cypress.config.ts` already masks timing with `retries.runMode: 2`. A numeric `cy.wait` would pass locally and fail in the five-container matrix on GitHub. The prompt forbids it.

### What if the change touches Auth0 or Okta sign-in?

Specs for those providers live in `cypress/tests/ui-auth-providers/` and are gated on credentials in `cypress.config.ts`. The agent writes the test in that style but cannot run it in the sandbox, says so in the comment, and runs the local-session equivalent in `cypress/tests/ui/auth.spec.ts` where that covers the same code.

### Does it run the mobile viewport?

Not by default. The e2e case runs once in Chrome at the 1280 by 1000 viewport from `cypress.config.ts`. The agent writes `isMobile()` branches where the surrounding cases have them, and the GitHub matrix runs the 375 by 667 configuration on push.
