vercel/next.js
Testing
The Next.js test pyramid has four tiers: unit, development-mode integration, production-mode integration, and end-to-end. They run via Jest with a custom orchestrator at scripts/run-jest.sh plus run-tests.js.
The test matrix
| Mode | Bundler | Command | Used for |
|---|---|---|---|
| dev | Turbopack | pnpm test-dev-turbo <path> |
Default. Dev server with Turbopack |
| dev | webpack | pnpm test-dev-webpack <path> |
Dev server with webpack |
| dev | rspack | pnpm test-dev-rspack <path> |
Dev server with rspack |
| start | Turbopack | pnpm test-start-turbo <path> |
Production build + start with Turbopack |
| start | webpack | pnpm test-start-webpack <path> |
Production build + start with webpack |
| start | rspack | pnpm test-start-rspack <path> |
Production build + start with rspack |
| deploy | webpack | pnpm test-deploy <path> |
Vercel deploy mode |
| unit | n/a | pnpm test-unit |
Pure-Node unit tests under test/unit/ |
| headless | current | pnpm testheadless <path> |
Skip rebuild; reuse current dist/ |
Each test mode is exposed both as a top-level test-* (with rebuild + headless) and a testonly-* (skip rebuild). Use testheadless for the fastest single-test iteration.
Where tests live
| Directory | Purpose |
|---|---|
test/unit/ |
Pure-Node unit tests; no browser, fastest |
test/development/ |
Dev-server scenarios |
test/production/ |
Build + start scenarios |
test/e2e/ |
End-to-end browser tests via Playwright Chromium |
test/integration/ |
Older integration suite that predates test/e2e/ |
test/lib/ |
Shared test helpers (next-test-setup, next-test-utils) |
The big test suite that exercises the App Router lives at test/e2e/app-dir/. The Pages Router e2e suite lives at test/e2e/.
Generating new tests
Use the test generator — it's mandatory rather than optional:
# Interactive
pnpm new-test
# Non-interactive (for agents and scripts)
# pnpm new-test -- --args <appDir> <name> <type>
# appDir: true | false
# name: kebab-case name
# type: e2e | production | development | unit
pnpm new-test -- --args true my-feature e2eThe generator creates a fixture directory with the right shape (app/page.tsx, next.config.js, package.json, etc.) and a *.test.ts skeleton under the chosen suite directory. Driven by turbo gen test from turbo/generators/.
Test conventions
Use real fixture directories, not inline files
// Good
const { next } = nextTestSetup({
files: __dirname, // points at the fixture directory next to this test file
});
// Avoid
const { next } = nextTestSetup({
files: {
'app/page.tsx': `export default function Page() { ... }`,
},
});The pnpm new-test generator follows the first pattern.
Use retry() from next-test-utils, not setTimeout
// Good
import { retry } from 'next-test-utils';
await retry(async () => {
const text = await browser.elementByCss('p').text();
expect(text).toBe('expected value');
});
// Bad — flaky and slow
await new Promise((resolve) => setTimeout(resolve, 1000));check() is deprecated
The legacy check() helper has been replaced by retry() + expect(). Do not use check() in new tests.
Mode-specific tests
Tests that only apply in one mode need skipStart: true plus a manual next.start() in beforeAll after a mode check. The nextTestSetup helper accepts this.
Don't rely on exact log messages
Filter by content patterns (regex match, includes) rather than asserting an exact equality. Look for sequences ("did the system reach this state and then this state?") not positions.
Snapshot tests vary by env flags
If your test uses inline snapshots:
- Update them with the exact env vars the failing CI job uses — they are listed in
pr-statusoutput under "Job Environment Variables" and in.github/workflows/build_and_test.yml. - Watch out for Turbopack-vs-webpack divergence: Turbopack resolves
react-dom/server.edge(norenderToPipeableStream) while webpack resolves the.nodebuild (has it). The same test can produce different stacks in each.
Cache components implies PPR
When __NEXT_CACHE_COMPONENTS=true, most app-dir pages use PPR implicitly. The dedicated ppr-full/ and ppr/ test suites are mostly describe.skip while migrating to cache components. To validate PPR codepaths, run regular app-dir e2e tests with __NEXT_CACHE_COMPONENTS=true.
Router act tests
Tests that need to control the timing of internal Next.js requests (such as prefetches) use createRouterAct and LinkAccordion from the test lib. Never use browser.back() to return to a page where accordion links are visible — BFCache will restore state and trigger uncontrolled re-prefetches. The agent skill router-act documents the patterns in detail.
Analyzing output efficiently
Don't re-run the same test with different greps. Capture once, analyze repeatedly:
HEADLESS=true pnpm test-dev-turbo test/path/to/test.ts > /tmp/test-output.log 2>&1
grep "●" /tmp/test-output.log # failed test names
grep -A5 "Error:" /tmp/test-output.log # error details
tail -5 /tmp/test-output.log # summaryQuick smoke testing
For fast feedback on a hang or crash, generate a minimal fixture and run the dev server directly:
pnpm new-test -- --args true probe e2e
node packages/next/dist/bin/next dev --port 3000 test/e2e/probe
curl --max-time 10 http://localhost:3000This bypasses the full test harness and surfaces errors immediately.
Reproducing CI failures locally
CI matrix and env vars matter. Match them exactly:
| CI env | Effect |
|---|---|
IS_WEBPACK_TEST=1 |
Forces webpack |
IS_TURBOPACK_TEST=1 |
Forces Turbopack |
NEXT_SKIP_ISOLATE=1 |
Skip packing; module-resolution bugs hide |
__NEXT_CACHE_COMPONENTS=true |
Cache components on, PPR implicit |
For module-resolution work, drop NEXT_SKIP_ISOLATE so the test installs Next.js as a real package.
Stable vs flaky
The repo maintains a "Known Flaky Tests" list (used by scripts/pr-status.js) but the rule is:
Assume failures are real until disproven. Use the flaky list as context, not auto-dismissal.
If you are confident a failure is flaky, you can re-run via .github/workflows/retry_test.yml — but spend time verifying first.
Test setup files
| File | Role |
|---|---|
test/jest-global-setup.ts |
Global setup that runs once |
test/jest-setup-after-env.ts |
Per-test-file setup; loads matchers and env |
jest.config.js |
Jest config for webpack/dev/start runs |
jest.config.turbopack.js |
Turbopack-specific Jest config |
test/lib/next-test-setup.ts |
The nextTestSetup helper used by every integration test |
test/lib/next-test-utils.ts |
retry(), helpers for waiting, fetching, asserting |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.