Files
next.js/scripts/run-jest.sh
Luke SandbergandGitHub 0b08e2f611 Collapse pnpm test scripts into a single helper invocation (#93097)
### What?

Every `pnpm test-*` / `pnpm testonly-*` script in the root `package.json` is now a single direct invocation of a new `scripts/run-jest.sh` helper instead of chaining through several `pnpm run` + `cross-env` layers.

### Why?

Each hop through `pnpm run` / `cross-env` spins up its own Node process and takes a few hundred milliseconds before anything useful happens. For example, `pnpm test-dev-turbo` used to walk through five layers:

```
test-dev-turbo
  -> pnpm run with-turbo pnpm test-dev-inner           (cross-env IS_TURBOPACK_TEST=1)
       -> pnpm test-dev-inner                          (cross-env NEXT_TEST_MODE=dev)
            -> pnpm testheadless                       (cross-env HEADLESS=true)
                 -> pnpm testonly
                      -> jest --runInBand
```

#### Measured overhead (same machine, same stubbed jest invocation — `jest --version`)

| Chain | Layers | Median (ms) | Range (ms) |
|---|---|---|---|
| `jest --version` (baseline, direct) | 1 | 80 | 75–89 |
| `pnpm test-dev-turbo --version` (**new**) | `pnpm → bash → jest` | 448 | 435–493 |
| `pnpm test-dev-turbo --version` (**old**) | `pnpm → cross-env → pnpm → cross-env → pnpm → cross-env → pnpm → jest` | 2494 | 2462–2512 |

That's **~2 seconds per test invocation** shaved off, and ~85% reduction in wrapper overhead (from ~2414 ms to ~368 ms). The remaining ~368 ms is essentially just one `pnpm run` startup, which is unavoidable while we keep the `pnpm test-*` entry points.

Real-world impact: for a ~72 s e2e test run this is ~3%, but for fast unit-test files (often <5 s) this is closer to 30-40%, and it adds up quickly in CI jobs that shell out to these scripts in a loop.

### How?

- New `scripts/run-jest.sh` parses named flags and `exec`s jest directly:
  - `--mode=<dev|start|deploy>` → sets `NEXT_TEST_MODE`
  - `--bundler=<webpack|turbo|rspack>` → sets `IS_WEBPACK_TEST=1` / `IS_TURBOPACK_TEST=1` / `NEXT_RSPACK=1 NEXT_TEST_USE_RSPACK=1`
  - `--experimental` → sets `__NEXT_CACHE_COMPONENTS=true __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true`
  - `--headless` → sets `HEADLESS=true`
  - `--` terminates flag parsing; everything after is forwarded verbatim to jest so existing workflows like `pnpm test-dev-turbo path/to/foo.test.ts -t "pattern"` keep working.
- Every `test-*` / `testonly-*` entry in `package.json` (including `testheadless`) now reads like `scripts/run-jest.sh --mode=dev --bundler=turbo --headless --`. No more `pnpm run` / `cross-env` indirection at the top level.
- Removed nine unused internal intermediates (`test-dev-inner`, `test-dev-experimental-inner`, `test-start-inner`, `test-start-experimental-inner`, `test-deploy-inner`, `testonly-dev-inner`, `testonly-start-inner`, `testonly-deploy-inner`, `test-inner`). These were only called by other package.json scripts and not referenced anywhere else in the repo.
- Kept `testonly`, `testheadless`, and all public `test-*` / `testonly-*` names so `AGENTS.md`, `contributing/core/testing.md`, `.conductor/`, and `.agents/skills/` work unchanged.
- The `with-webpack`, `with-turbo`, `with-rspack`, and `with-experimental` prefix scripts remain for ad-hoc use; they are just no longer in the hot path of the test scripts.
- `scripts/run-jest.sh` invokes a bare `jest`, which resolves via `$PATH` (pnpm prepends `node_modules/.bin/` when running a package script). The helper is documented as only supported when invoked through a package runner.

### Verification

Ran a small e2e test through the new chain in both dev+turbopack and start+webpack modes. Output confirms env vars propagate correctly (`PASS Turbopack ...` / `PASS webpack ...`, plus `pnpm next --turbopack` vs `pnpm next start` in the logs):

```
pnpm test-dev-turbo test/e2e/app-dir/_allow-underscored-root-directory/...
  -> PASS Turbopack ... 3 passed

pnpm test-start-webpack test/e2e/app-dir/_allow-underscored-root-directory/...
  -> PASS webpack ... 3 passed
```

CI is unaffected: every workflow invokes `node run-tests.js ...` directly, never through these pnpm scripts.

<!-- NEXT_JS_LLM_PR -->
2026-04-21 16:53:16 -07:00

64 lines
1.5 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Set up environment variables for a Next.js jest test run and exec jest
# in a single hop, replacing this shell process.
#
# Usage:
# scripts/run-jest.sh \
# [--mode=<dev|start|deploy>] \
# [--bundler=<webpack|turbo|rspack>] \
# [--experimental] \
# [--headless] \
# -- [jest args...]
#
# All arguments after `--` are forwarded verbatim to jest.
set -eo pipefail
while [ $# -gt 0 ]; do
case "$1" in
--mode=dev|--mode=start|--mode=deploy)
export NEXT_TEST_MODE="${1#--mode=}"
;;
--mode=*)
echo "run-jest.sh: unknown mode: ${1#--mode=}" >&2
exit 1
;;
--bundler=webpack)
export IS_WEBPACK_TEST=1
;;
--bundler=turbo)
export IS_TURBOPACK_TEST=1
;;
--bundler=rspack)
export NEXT_RSPACK=1
export NEXT_TEST_USE_RSPACK=1
;;
--bundler=*)
echo "run-jest.sh: unknown bundler: ${1#--bundler=}" >&2
exit 1
;;
--experimental)
export __NEXT_CACHE_COMPONENTS=true
export __NEXT_EXPERIMENTAL_APP_NEW_SCROLL_HANDLER=true
;;
--headless)
export HEADLESS=true
;;
--)
shift
break
;;
*)
echo "run-jest.sh: unknown argument: $1" >&2
exit 1
;;
esac
shift
done
# Resolves to `node_modules/.bin/jest` via `$PATH`. This relies on being
# invoked through pnpm (or another package runner), which prepends the
# workspace's `node_modules/.bin/` to `$PATH` before running the script.
exec jest --runInBand "$@"