lpm test
Auto-detect and run the project's test runner — vitest, jest, or mocha.
lpm test [-- args...]Detects which test runner is installed in the project (vitest → jest → mocha) and runs it, forwarding any trailing arguments verbatim. No config; no per-runner subcommand.
Examples
lpm test # run the whole suite (single-pass)
lpm test src/utils.test.ts # run one file
lpm test --watch # vitest: enter watch mode (see below)
lpm test -- -w # short form, same effect
lpm test -- --reporter=verboseDetection order
vitest(independenciesordevDependencies)jestmochascripts.testinpackage.json(only consulted when no runner above is installed)
Whichever is found first wins. If none of the runners is installed and no test script exists, lpm test reports it.
Argument forwarding
Anything after the command (or after --) is forwarded to the runner:
lpm test -- --coverage --reporter=html
# vitest: vitest run --coverage --reporter=html
# jest: jest --coverage --reporter=htmlThe vitest base command is vitest run (single-pass), except in Watch mode below.
Watch mode
lpm test --watch (or -w) enters watch mode. For vitest specifically, LPM rewrites the base command from vitest run to vitest so the --watch flag is honored — vitest run would silently force single-pass and drop the flag. jest and mocha accept --watch natively against their bare command, so the rewrite is vitest-only.
lpm test --watch # vitest: → vitest --watch
# jest: → jest --watch
# mocha: → mocha --watchSee Test & bench runners for the full rewrite rules.
Workspaces
lpm test --all # every member
lpm test --filter web # exact name
lpm test --filter '@scope/*' # glob
lpm test --filter './apps/*' # path glob
lpm test --filter web --filter api # union
lpm test --filter-prod ...shared # prod graph closure
lpm test --affected # only members touched since main
lpm test --affected --base develop
lpm test --filter web --fail-if-no-match # exit non-zero on typo'd filter
lpm test --filter '@scope/*' --workspace-concurrency 2Detection runs per member, so a workspace can mix runners (one member uses vitest, another uses jest) and each gets the right command. A member with no installed runner and no scripts.test becomes a per-member detection failure in the JSON envelope rather than aborting the whole run.
--all is mutually exclusive with filters and --affected. --filter and --filter-prod compose with --affected (the affected set is unioned with the filter result). --filter-prod uses the same grammar as --filter, but closure operators ignore devDependencies.
--workspace-concurrency <N> caps how many selected workspace members run at once within each topological level.
Forwarding runner flags with the same names
The workspace flags (--all, --filter, --filter-prod, --affected, --base, --fail-if-no-match, --workspace-concurrency) are claimed by LPM. To pass any of them through to the underlying runner (e.g. bun's --filter, jest's --all), put them after --:
lpm test -- --filter pattern # forwards --filter to bun/vitest/etc
lpm test -- --all # forwards --all to the runner
lpm test --filter web -- --reporter=verbose # workspace + forwarded argsAnything after -- is forwarded verbatim regardless of name.
Watch with a workspace selector
lpm test --all --watch would start one watcher per workspace member — almost always a mistake. So watch mode is gated by selection size:
lpm test --filter web --watch # ✓ one member, one watcher (hands off
# to single-package mode in web/)
lpm test --all --watch # ✗ rejected: would start N watchers
lpm test --filter '@scope/*' --watch # ✓ allowed only if exactly one member matches
# ✗ rejected with the actual count if more
lpm test --filter typo --watch # ✗ rejected: nothing to watchWhen the filter resolves to exactly one member, lpm test runs against that member's directory as if you'd cd-ed in — the existing vitest run → bare vitest rewrite kicks in, so --watch is honored.
Flags
| Flag | Effect |
|---|---|
--all | Run in every workspace member |
--filter <expr> | Select workspace members by the filter grammar (repeatable) |
--filter-prod <expr> | Select workspace members with production-only dependency closures |
--affected | Run only in members affected by changes vs --base |
--base <REF> | Git base ref for --affected (default: main) |
--changed-files-ignore-pattern <glob> | Ignore matching git-diff paths for --affected / [git-ref] filters |
--test-pattern <glob> | Treat matching git-diff paths as test-only for --affected / [git-ref] fan-out decisions |
--fail-if-no-match | Exit non-zero if no member matches the filter set |
--workspace-concurrency <N> | Limit concurrent workspace members for --all, --filter, or --affected runs |
Anything after -- (or trailing) is forwarded to the runner.
Plus the global flags.
--json in workspace mode
Single-package mode (lpm test) preserves the runner's stdout — LPM does not wrap it. Workspace mode emits a single LPM envelope on stdout; per-member stdout/stderr is captured and surfaced inside the envelope only on failure. See lpm lint for the exact envelope shape. Per-member detection failures (no runner installed) appear as exit_code: null paired with an error string.
See also
lpm bench— same auto-detection, for benchmarkslpm run test— run thetestscript frompackage.jsoninstead- Test & bench runners — how detection works