D
DevTools HQ
Open-Source Test Selection for Jest & Vitest

testpick: Run Only the Tests Your Diff Can Actually Break

testpick is a test-selection CLI for JavaScript and TypeScript. It looks at what you changed in git diff and runs just the tests affected by those changes — turning multi-minute CI runs into seconds.

$ npx testpick map     # one-time: learn which tests touch which code
$ npx testpick run     # from now on: run only what your changes affect

Free & open source · MIT licensed · Zero config · Jest, Vitest & monorepos

What Is testpick?

testpick is an open-source, coverage-based test-selection tool for JavaScript and TypeScript projects. Instead of re-running your entire test suite on every change, it figures out which tests can actually be affected by your git diff and runs only those — so a CI job that used to take six minutes can finish in seconds.

The difference from static approaches is how testpick builds its map: from runtime coverage — what each test actually executed — rather than a static import graph. That lets it capture couplings a static analyzer can't see (like a module loaded through a runtime-computed path) while staying safe: when a change touches something it hasn't mapped, testpick runs more tests, never fewer.

How It Works

Three commands, zero configuration. testpick auto-detects your runner (Jest or Vitest) and any workspaces.

1

Map

Run testpick map once. It measures runtime coverage and records which tests exercise which source files.

2

Change

Edit your code as usual. testpick reads your git diff to see exactly which files moved.

3

Run

Run testpick run and only the affected test files execute. Use explain to see why.

$ testpick map
Mapping 23 test file(s) with vitest in 8 single-pass shard(s).
 Map saved to .testpick/map.json — 24 source files tracked.

$ vim src/util.ts        # change one file...

$ testpick run
testpick: 1/23 test file(s) affected by 1 change(s).
  src/greet.test.ts (1 test) 4ms

$ testpick explain       # ...and see *why*
Changed files (1):
  • src/util.ts
Decisions:
   src/util.ts  [coverage map → 1 test(s)]
      → src/greet.test.ts
Result: run 1 of 23 test file(s).

Key Features

Runtime Coverage Map

Selection is built from what each test actually executed — so it catches runtime-computed couplings (plugin registries, DI containers, dynamic imports of a path that's data) that a static import graph can't see.

Safety First

The rule is simple: when in doubt, run more — never less. A change to an unmapped file (new file, config) runs the whole suite by default, so testpick never silently skips a test that matters.

Fast, Single-Pass Maps

Instead of starting the runner once per file, testpick shards files across your cores and runs each shard as one process — far fewer startups, measurably faster wall-clock and less total CPU.

Incremental & Parallel

Each test file is hashed, so only changed or new files are re-measured — a no-op refresh is instant. Mapping runs up to one lane per CPU, tunable with -j.

Monorepo Aware

Detects npm, yarn, and pnpm workspaces and treats each package as its own unit — its own runner, its own map. Vitest in one package and Jest in another both work, selected per package.

Explainable Selection

testpick explain is a dry run that prints exactly which tests were selected or skipped and the reason for each decision — so you can trust the selection before you rely on it in CI.

Why Not Just vitest --changed or jest --onlyChanged?

Those flags are great — until your code has couplings their static import graph can't see. A module loaded via a runtime-computed path (a plugin registry, a DI container, or import(pathFromConfig)) is invisible to static analysis: Vite can glob a literal dynamic import, but it can't analyze a path that is data.

// Vite's static graph can't see this — the path is data, not a literal
const REGISTRY = { feat: "../features/feat.ts" };
export const load = (n) => import(/* @vite-ignore */ REGISTRY[n]);

testpick builds its map from what each test actually ran, so it captures that edge. Change features/feat.ts and static selection finds nothing to run — testpick still runs the test that loads it:

Change features/feat.ts Result
vitest related features/feat.ts No test files found ✗
testpick run Runs loader.test.ts ✓

testpick and a static module graph are complementary, not strictly better: a coverage map is more precise for runtime-computed couplings and for trimming imported-but-unexecuted modules, while a static graph can flag a yet-to-run branch a coverage map hasn't seen. That's exactly why testpick errs toward running more — and why it works the same for Jest, where there is no Vite graph.

Commands & Options

CommandWhat it does
testpick mapBuild or refresh the coverage map (incremental by default).
testpick runRun only the tests affected by your changes.
testpick explainDry run — print the selection and the reasoning behind it.
OptionMeaning
--base <ref>Diff against a ref (in CI: --base origin/main). Default: working tree vs HEAD.
--aiUse an LLM (needs ANTHROPIC_API_KEY) to narrow unmapped changes — but it can never cause a skip.
--allEscape hatch: run the whole suite.
--fullmap only: rebuild from scratch instead of incrementally.
-j, --jobs <n>map only: max concurrent coverage passes (default: CPU count).

In CI (GitHub Actions)

Point testpick at the pull request's base branch and it runs only the tests the PR can affect. Commit .testpick/map.json to share the map across runs, or rebuild it on a schedule.

- run: npm ci
- run: npx testpick run --base origin/${{ github.base_ref }}

Who Uses testpick?

testpick pays off anywhere the test suite has outgrown the change being tested.

Frequently Asked Questions

What is testpick?
testpick is an open-source test-selection CLI for JavaScript and TypeScript. It looks at what you changed in git and runs only the tests affected by those changes instead of your whole suite. Because it builds its map from runtime coverage — what each test actually executed — it works for Jest, Vitest, and monorepos, and turns multi-minute CI runs into seconds.
Is testpick free?
Yes. testpick is completely free and open source under the MIT license. There is nothing to sign up for — run it directly with npx testpick or add it to your project's dev dependencies.
How is testpick different from vitest --changed or jest --onlyChanged?
Those tools walk a static import graph. testpick builds its map from runtime coverage — what each test actually executed — so it captures couplings the static graph can't see, such as a module loaded via a runtime-computed path (a plugin registry, DI container, or dynamic import of a path that is data). When a change touches something testpick hasn't mapped, it safely runs everything: when in doubt, it runs more, never less.
Which test runners does testpick support?
testpick supports Vitest and Jest, in both single-package repos and monorepos. In a monorepo it detects npm, yarn, and pnpm workspaces and treats each package as its own unit — a package using Vitest and another using Jest in the same repo both work.
Can testpick skip a test it shouldn't?
testpick's core rule is: when in doubt, run more — never less. A change to a file the map doesn't know about (a new file, a config) runs all tests by default. The optional --ai flag can narrow unmapped changes, but if the model is unsure it still falls back to running everything, so the AI can never cause a skip. testpick explain shows exactly why each test was selected or skipped.
How do I use testpick in CI with GitHub Actions?
Run npx testpick run --base origin/<base_ref> after installing dependencies. Commit the generated .testpick/map.json to share the coverage map across CI runs, or rebuild it on a schedule. testpick then runs only the tests affected by the pull request's diff.
Does testpick need configuration or extra dependencies?
No. testpick works with zero configuration. It auto-detects your test runner (Jest or Vitest) and any npm, yarn, or pnpm workspaces, and uses the V8 precise coverage that Node already provides. There is nothing to configure to get started — npx testpick map then npx testpick run.

Stop Running Tests Your Change Can't Break

testpick is free and open source. Install it with a single command and cut your test runs down to what actually matters.

Star testpick on GitHub