Architecture Overview
Server Runtime
Rendering Pipeline
Client Navigation
Caching and Export
Development Tools
Build and Configuration
Ecosystem Packages
Testing Infrastructure
How It Works
The following files were used as context for generating this wiki page:
Test Runners in the Next.js repository encompass repository-level execution scripts, Jest configurations, CLI integration wrappers, and evaluation harness infrastructure designed to validate Webpack, Rspack, and Turbopack runtimes. Because Next.js spans multiple complex compilation and bundling pipelines, testing requires isolating behavior across different build modes (dev, start, deploy), experimental react channels, and testing backends. This subsystem addresses the challenge of coordinating massive parallel suites across CI environments without cross-test state pollution or unstable timing anomalies.
The architecture centers around layered test profiles, structured environment sanitization, and specialized runner adapters. Repository-level orchestration scripts like run-tests.js and run-evals.js control result caching, package packing, and worker allocation. Meanwhile, Jest configuration factories (jest.config.js, jest.config.turbopack.js) integrate Next.js custom transforms with test reports, and the Next.js CLI next-test command provisions Playwright configurations dynamically. This modular setup ensures that engineers can execute targeted tests locally or leverage caching and sharding mechanisms seamlessly in automated CI pipelines.
To optimize CI execution on retry attempts, the test runner maintains the TestProfile class inside run-tests.js. This class isolates environment variables that alter test execution behavior from operational variables that do not affect test outcomes.
Sources: run-tests.js:22-64
When initialized, TestProfile records a snapshot of process.env. It preserves designated behavioral variables (such as IS_WEBPACK_TEST, IS_TURBOPACK_TEST, TURBOPACK_DEV, TURBOPACK_BUILD, BROWSER_NAME, and DEVICE_NAME) while stripping known operational variables like NEXT_TELEMETRY_DISABLED, NEXT_TEST_JOB, and NEXT_JUNIT_TEST_REPORT via the IGNORED_VARS set.
Sources: run-tests.js:65-106
It then constructs a sorted array of entries containing platform metadata, branch names, commit hashes, and node versions to form a deterministic diagnostic profile description.
Sources: run-tests.js:107-124
Note
Caching is enabled exclusively when running in CI (process.env.CI), with a valid GITHUB_SHA, and when flake detection (NEXT_FLAKE_DETECTION) or cache bypassing (NEXT_TEST_SKIP_RESULT_CACHE) are inactive.
Sources: run-tests.js:125-147
Next.js provides extensible Jest configuration generators that adapt test environments depending on whether Webpack, Rspack, or Turbopack is targeted. The root jest.config.js invokes next/jest() to load project-specific Next.js settings asynchronously.
Sources: jest.config.js:1-5
The configuration explicitly sets displayName based on environment flags, defines root directories including packages like packages/next/src and packages/next-codemod, and applies strict Haste settings (throwOnModuleCollision: true) to prevent module warning pollution.
const nextJest = require('next/jest')
const createJestConfig = nextJest()
const customJestConfig = {
displayName: process.env.IS_WEBPACK_TEST ? 'webpack' : 'Turbopack',
testMatch: ['**/*.test.js', '**/*.test.ts', '**/*.test.jsx', '**/*.test.tsx'],
globalSetup: '<rootDir>/jest-global-setup.ts',
setupFilesAfterEnv: ['<rootDir>/jest-setup-after-env.ts'],
verbose: true,
rootDir: 'test',
roots: [
'<rootDir>',
'<rootDir>/../packages/next/src/',
'<rootDir>/../packages/next-codemod/',
'<rootDir>/../packages/eslint-plugin-internal/',
'<rootDir>/../packages/font/src/',
'<rootDir>/../packages/next-routing/',
],
haste: {
throwOnModuleCollision: true,
},
modulePathIgnorePatterns: [
'/\\.next/',
'packages/next/src/compiled/',
],
}
module.exports = createJestConfig(customJestConfig)Sources: jest.config.js:6-48
For Turbopack-specific test suites, jest.config.turbopack.js wraps the default configuration to append <rootDir>/jest-setup-files.turbopack.js into setupFiles.
const createJestDefaultConfig = require('./jest.config.js')
module.exports = async function createConfig() {
const jestDefaultConfig = await createJestDefaultConfig()
const customConfig = {
...jestDefaultConfig,
displayName: 'Turbopack',
setupFiles: [
...(jestDefaultConfig.setupFiles ?? []),
'<rootDir>/jest-setup-files.turbopack.js',
],
}
return customConfig
}Sources: jest.config.turbopack.js:1-16
Test reporting reporters are conditionally injected when NEXT_JUNIT_TEST_REPORT is enabled, mapping output directories depending on whether Turbopack or Rspack environment variables are present.
Sources: jest.config.js:50-85
next-test.ts)The Next.js command-line interface exposes a next test command (packages/next/src/cli/next-test.ts) that acts as a wrapper around test runners like Playwright. When invoked, nextTest resolves the project directory and loads the Next.js configuration for validation.
It enforces that experimental.testProxy is enabled in next.config.js, throwing a fatal error via printAndExit if the setting is missing.
if (!nextConfig.experimental.testProxy) {
return printAndExit(
`\`next experimental-test\` requires the \`experimental.testProxy: true\` configuration option.`
)
}The test runner selection priority follows the rule: CLI option (--test-runner) > Next config property (experimental.defaultTestRunner) > Default fallback ('playwright'). If Playwright is chosen, runPlaywright verifies required dependencies via hasNecessaryDependencies.
If playwright.config.js or playwright.config.ts is missing from the project directory, it automatically generates a default configuration file using defaultPlaywrightConfig.
run-evals.js)The evaluation runner script (run-evals.js) coordinates agent evaluations against locally built next packages. It isolates execution by packing the next package into a tarball inside .tarballs/next.tgz.
Sources: run-evals.js:1-62
The script constructs temporary experiment files that compare two distinct variants: a baseline variant and an agents-md documentation-enhanced variant.
Sources: run-evals.js:63-91
Warning
If packages/next/dist is not present when invoking run-evals.js, the script aborts immediately with an error instructing the user to run pnpm --filter=next build first.
Sources: run-evals.js:92-180
devlow-bench)Turbopack benchmarking leverages the devlow-bench package located under turbopack/packages/devlow-bench. The suite exposes a programmatic scenario description API (describe) and command-line execution interfaces (cli.ts).
The describe function registers scenario configurations, normalizes boolean options into arrays of variant permutations, and coordinates interface injection (interactive, console, json, snapshot, compare).
export function describe<P>(
name: string,
config: ConfigFor<P>,
fn: (props: P) => Promise<void>
): void {
if (currentScenarios === null) {
const scenarios = (currentScenarios = [])
Promise.resolve().then(async () => {
const ifaceNames = process.env.INTERFACE || 'interactive,console'
const ifaces = []
for (const ifaceName of ifaceNames.split(',').map((s) => s.trim())) {
// Dynamic interface loading logic
}
runScenarios(scenarios, compose(...ifaces))
})
}
const normalizedConfig: Record<string, (string | number | boolean)[]> =
Object.fromEntries(
Object.entries(config).map(([key, value]) => [
key,
typeof value === 'boolean'
? [value, !value]
: (value as (string | number | boolean)[]),
])
)
currentScenarios!.push({
name,
config: normalizedConfig,
only: false,
fn: fn as (props: Record<string, string | number | boolean>) => Promise<void>,
})
}Measurement functions validate active scenario contexts and finite numeric bounds before reporting performance metrics.
Sources: package.json:8-57