Skip to content

Latest commit

 

History

History
396 lines (281 loc) · 36.7 KB

TESTING_DOCS.md

File metadata and controls

396 lines (281 loc) · 36.7 KB

Webpack Test Suite Structure

This document explains the structure of the test/ directory in the Webpack project using Jest. The directory is organized into multiple folders and files, each serving a specific purpose in testing various aspects of Webpack’s functionality.

Folder and File Breakdown

Directories come first, in alphabetical order, then the individual files worth their own note. Add a new entry where the alphabet puts it — the sections are deliberately unnumbered, so nothing has to be renumbered around it.

__snapshots__/

  • Purpose: Stores Jest snapshot files for comparing output consistency over time.
  • Usage: Used for testing UI components, serialized data, or expected module outputs.

benchmarkCases/

  • Purpose: Contains test cases for benchmarking Webpack's performance.
  • Usage: Measures build times, memory usage, and optimization impact.
  • Kinds of case (picked from the directory name):
    • build (default, e.g. many-modules-esm/) — a webpack.config.mjs plus an entry; measures one build per scenario (mode-development, mode-development-rebuild, mode-production).
    • *-unit (e.g. js-parser-unit/) — an index.bench.mjs exporting default (bench) => {…}; measures a piece of lib/ directly, with no scenarios.
    • *-runtime (e.g. many-modules-interop-runtime/) — measures the emitted bundle, not the build. The case is compiled once per scenario outside any measured region; the exec task then instantiates the output (runtime bootstrap plus every module factory that runs at import time) and calls the entry's exported run on it. The rebuild scenario is skipped, since it emits the same output.
  • Writing a *-runtime case: the entry must export run(seed), do its work with that opaque seed, and return the result — otherwise the compiler folds the workload away and the bench measures nothing (both are checked and fail the run). The harness forces target: "node" and a commonjs2 library so the output can be instantiated in-process; generate large fixtures from options.mjs (setup()), as the build cases do.

exec is reported per scenario, so a development/production pair shows what scope hoisting and minification are worth at runtime.

cases/

  • Purpose: General test cases covering core functionalities.
  • Usage: Includes unit and integration tests for various modules and features.

configCases/

  • Purpose: Tests related to Webpack configurations.
  • Usage: Ensures that Webpack’s configuration (e.g., loaders, plugins) functions correctly.

external/

  • Purpose: Every git submodule webpack checks out for testing — today the four spec corpora below and terser's, swc's and oxc's own tests. Nothing here is webpack's to edit: each directory belongs to its upstream project, and this repository only pins a commit.

test262-cases/

  • Purpose: ECMAScript test262 conformance test cases.
  • Usage: Git submodule — initialize with git submodule update --init test/external/test262-cases. Test runners: test/specCases/test262.spectest.js, which builds and runs each test in development, in production unminified (a failure there is webpack's own, not a minimizer's), and in production once per JavaScript minimizer: terser, and webpack's printer as experiments.futureDefaults switches it. The minified modes skip the fn-name cases a renamed binding changes and keep function and class names and turn pure_getters off, as test262 reads names and properties that throw; the printer also runs with pure_conversions and pure_heritage off, as test262 converts and extends values that run code or throw on purpose. What each minimizer still fails is listed by reason in its MINIFIED_FAILURES table, and a listed case that passes fails the suite until removed. And test/specCases/minify-corpora.spectest.js, which minifies each test with webpack's printer and with terser.

html5lib-tests/

  • Purpose: WHATWG html5lib-tests tokenizer conformance cases for lib/html/syntax.
  • Usage: Git submodule — initialize with git submodule update --init --depth 1 test/external/html5lib-tests test/external/wpt: the runner reads both corpora. Test runner: test/specCases/html5lib.spectest.js (yarn test:html5lib) compiles every input as a webpack HTML entry to confirm the full pipeline handles it without crashing.

wpt/

  • Purpose: web-platform-tests, read two ways. html/syntax/parsing/resources/*.dat is the HTML tree-construction conformance corpus for parseHtml (html5lib-tests dropped its copy in 224991e). The .html documents under html/, conformance-checkers/ and dom/nodes, plus the declarations the css/**/parsing/ tests state a verdict for, are the printers' corpus: minifying must not change the DOM webpack's parser builds, the DOM Chrome builds, or the style Chrome computes.
  • Usage: Git submodule — initialize with git submodule update --init --depth 1 test/external/wpt (the repository is ~161k files, so keep it shallow). Test runners: test/specCases/html5lib.spectest.js (yarn test:html5lib), which also reads test/external/html5lib-tests — initialize both to run the whole suite — and test/specCases/syntaxEquivalence.spectest.js (yarn test:syntax-equivalence), whose browser tiers need a Chrome (CHROME_EXECUTABLE_PATH picks a binary other than the pinned build). A document that is not UTF-8 is skipped: the encoding fixtures are UTF-16, which no string API can read as source.

css-parsing-tests/

  • Purpose: CSS Syntax Level 3 conformance corpus for lib/css/syntax.
  • Usage: Git submodule — initialize with git submodule update --init test/external/css-parsing-tests. Test runner: test/specCases/cssParsing-webpack.spectest.js (yarn test:css-parsing) compiles every input as a webpack CSS entry to confirm the full pipeline handles it without crashing.

xml-conformance-suite/

  • Purpose: the W3C XML Conformance Test Suite (lddubeau's packaging) for parseHtml(…, { xml: true }) and the XML printer.
  • Usage: Git submodule — initialize with git submodule update --init --depth 1 test/external/xml-conformance-suite. Test runner: test/specCases/xmlconf.spectest.js (yarn test:xmlconf) compares every document the suite gives an expected output for to James Clark's canonical form, and checks that every document, minified or beautified, reads back to the canonical form of its source. Documents needing an external entity or DTD, and XML 1.1 ones, are counted rather than run. Without the submodule the suite registers a no-op and passes, so a green run then says nothing.

swc/

  • Purpose: swc's repository, read only under crates/swc_ecma_minifier/tests: its fixtures with their configs, the tests exec.rs and mangle.rs write inline, and the libraries it measures itself on. Each is held to terser's bytes like the terser corpora; a test with an expected.stdout, and every exec.rs test, also has its outputs run, and webpack's output may be no bigger than each fixture's recorded output.js, nor than what swc's port of a terser case records when its passing.txt lists it, unless SWC_SMALLER lists why.
  • Usage: Git submodule — initialize with git submodule update --init --depth 1 test/external/swc. Test runner: test/specCases/minify-corpora.spectest.js (yarn test:minify-corpora).

oxc/

  • Purpose: oxc's repository, pinned to the commit that released webpack's oxc-minify devDependency and read only under crates/oxc_minifier/tests: the source each test…( helper call in its Rust tests passes first, but TypeScript. Each is held to terser's bytes like the other corpora, and under the default minimizer's options every source of every corpus but test262 is also minified by oxc-minify: webpack's output may be no bigger than oxc's unless OXC_SMALLER lists why, and an oxc output that misprints is no lead.
  • Usage: Git submodule — initialize with git submodule update --init --depth 1 test/external/oxc. Test runner: test/specCases/minify-corpora.spectest.js (yarn test:minify-corpora), which also fails when the pin and the installed oxc-minify disagree.

terser/

  • Purpose: terser's own repository, pinned to webpack's terser devDependency. Its test/compress cases and test/input files are two of the seven corpora lib/javascript/syntax-printer.js is held to (test262, swc's fixtures, exec.rs and mangle.rs tests, and oxc's tests are the other five): each source is minified by terser as published and by webpack's printer under several option sets, and the outputs, or the errors, must be byte-for-byte the same, but for what webpack's correct phase fixes. A case stating its expect_stdout also has each output run in terser's sandbox, which must print it — or, for expect_stdout: true, do whatever its input does, a throw or silence included.
  • Usage: Git submodule — initialize with git submodule update --init --depth 1 test/external/terser. Test runner: test/specCases/minify-corpora.spectest.js (yarn test:minify-corpora), which also fails when the pin and the installed terser disagree, so bumping the dependency means moving the pin with it. A new corpus is one entry in its CORPORA list.

fixtures/

  • Purpose: Stores sample/mock data used in tests.
  • Usage: Helps in creating consistent test cases with predefined inputs.

harness/

  • Purpose: What runs the suites, as opposed to what they test. Jest's lifecycle (globalSetup.js, globalTeardown.js, setupTestFramework.js), the patch-node-env.js test environment, runtimeCrashReporter.js, the case runner/, the snapshot/ resolver and matchers, benchmark/, and runtimes/.
  • runtimes/: The preload and setup files that let jest run under a non-Node runtime — bun-preload.js and bun-sandbox-setup.js for yarn test:bun, deno-worker-setup.js and deno-import-map.json for yarn test:deno. Wired in through jest.config.js and the test:base:bun / test:base:deno scripts.
  • Not helpers: a reusable assertion or fixture belongs in helpers/; harness/ is only for machinery the test runner itself loads.

helpers/

  • Purpose: Utility functions and scripts to assist in testing.
  • Usage: Provides reusable functions for mock data generation, cleanup, and assertions.

hotCases/

  • Purpose: Focuses on Webpack’s Hot Module Replacement (HMR) functionality.
  • Usage: Ensures live reloading and hot updates work correctly.
  • Every HMR change is covered twice: a case for the default output (JSONP on web, require on node, importScripts on webworker) and a twin under hotCases/esm-output/ with output.module: true and chunkFormat: "module". The ES module hot update wraps runtime code in a function, so it breaks in ways the default output never shows. A Worker twin there needs publicPath: "auto": a Node ES module worker can't import the harness's https:// default.

hotPlayground/

  • Purpose: A hand-driven counterpart to hotCases/ — those assert what an update does, this one lets you watch it happen in a browser. One panel per module type and ECMAScript form: an html entry, CommonJS, CSS, CSS modules, JSON via an import attribute, all three asset types, async and source-phase WebAssembly, import defer and an async chunk, plus a panel driving the HMR API itself (data/dispose, addStatusHandler, check, invalidate). Nothing here runs in CI.
  • Usage: yarn playground serves it on http://localhost:8080 with hot reloading on; edit any file and watch the page. See test/hotPlayground/README.md for what each file demonstrates.

memoryLimitCases/

  • Purpose: Contains test cases related to memory limits.
  • Usage: Ensures Webpack doesn’t exceed memory constraints.

sizeCoreReports/

  • Purpose: Whole applications for yarn test:size (below), whose report gives each one's total download in a Sample apps table.
  • A case is a directory with a webpack.config.mjs (or .js; an array builds one compiler per entry, each reported on its own row) and, optionally, an options.mjs whose exported setup() runs first to generate or install its sources (into generated/, which git ignores). The harness sets only context, output.path, mode: "production" when unset and quiet logging; everything else is the application's own configuration.
  • Today, each target with script (or CommonJS) and ES module output (-esm):
    • web: react-100, react-1k, react-5k and react-1k-esm, React apps of that many components (lazy routes, CSS modules, JSON, SVG) from the realistic-app benchmark's generator (test/harness/benchmark/create-app.mjs).
    • node: node-ssr and node-ssr-esm, that app rendered on the server by an http server with react-dom/server (test/harness/size/create-server.mjs).
    • webworker: webworker and webworker-esm, a message loop lazily loading 40 data tasks built on lodash-es and date-fns (test/harness/size/create-worker.mjs).
    • library: one small library built once per output.library.type, the types read from the schema.

specCases/

  • Purpose: Holds the runners for specification-conformance suites.
  • Files:
    • test262.spectest.js — yarn test:test262 --parts=3: defined whole, its ~175k tests overflow Jest's stack, so it always runs in parts, as CI does
    • test262-parser.spectest.js — yarn test:test262-parser
    • html5lib.spectest.js — yarn test:html5lib
    • syntaxEquivalence.spectest.js — yarn test:syntax-equivalence
    • cssParsing-webpack.spectest.js — yarn test:css-parsing
    • xmlconf.spectest.js — yarn test:xmlconf
    • minify-corpora.spectest.js — yarn test:minify-corpora; CI runs it in three parts, each claiming the next 100 sources of a group no other part took, and the last part to finish checks no table entry went unmet
    • js-fuzz.spectest.js — yarn test:js-fuzz: programs from test/helpers/jsFuzzProgram.js, in the sloppy ES5 terser's ufuzz writes, in modern syntax, as modules importing a fixed dep and as webpack-shaped bundles, minified under ufuzz's option sets, webpack's and random ones; the parser is held to acorn's tree on each program and output, the printer to the tree it read and to its own output, each output to what its program prints and exports, the default set's source map to its output and source, and the output to what it prints minified again. Needs no submodule; JS_FUZZ_SEED=<n> replays one seed, JS_FUZZ_SEED=random runs a new one named in the test's title, and JS_FUZZ_PROGRAMS sets the programs per seed.

statsCases/

  • Purpose: Tests focused on Webpack’s statistical outputs.
  • Usage: Verifies correct bundle sizes, dependencies, and optimizations.

templates/

  • Purpose: The suite drivers — TestCases.js, ConfigTestCases.js, HotTestCases.js, WatchTestCases.js — each exporting describeCases(config).
  • Usage: The *.test.js / *.basictest.js / *.longtest.js files at the top of test/ are thin shims that call describeCases with one suite's options, so one driver serves every variant (targets, devtools, cache modes). Jest parallelizes per file, which is why the variants stay separate files rather than being folded into one.
  • Variants: variants.js holds the option sets the TestCases* and HotTestCases* shims run under, re-exported from each driver as variants. Add a variant by adding an entry there and one shim naming it — the shims stay separate files because jest parallelizes per file.
  • Note: templates/ is a sibling of the case directories it runs (cases/, configCases/, hotCases/, watchCases/), so a driver resolves paths from the shared test/ root via testRootDirectory (path.join(__dirname, "..")), never against __dirname.

typesCases/

  • Purpose: Type-checking tests, likely for TypeScript integration.
  • Usage: Ensures proper type definitions and compliance.

unitCases/

  • Purpose: Contains *.unittest.js unit tests for various functionalities.
  • Usage: Ensures individual modules and functions work as expected.
  • Layout: the syntax tests sit in one folder per language — javascript/ (tokenizer, parser, scope, printer), css/ and html/ (tokenizer, parser, printer), xml/ (printer) and json/ (printer, web-manifest). A printer's file holds both of its modes and the language's minifier, which drives the printer in its minify mode. The tests of the tooling/ scripts sit in tooling/. The rest, webpack's own classes such as JavascriptParser, stay at the top level.

watchCases/

  • Purpose: Tests for Webpack’s watch mode functionality.
  • Usage: Ensures file changes trigger correct rebuild behavior.

CodeSizeTestCases.size.js

  • Purpose: Measures how large the code webpack generates is, so a change to lib/ that grows (or shrinks) every bundle is visible.
  • Usage: yarn test:size builds every configCases/ case, plus every application in sizeCoreReports/ (reported as apps/<case>) — one plain Node.js process, outside jest, no worker pool — and writes a JSON report of what each case emitted: the raw, gzip, brotli and zstd size of every asset, plus a per-runtime-module breakdown (total bytes over the suite, how many cases emit it, the biggest single instance) — which is what shows which runtime grew, which is no longer emitted at all, and which one is simply large. The CI job (.github/workflows/code-size.yml) compares the report against the one main last uploaded, posts it as a pull request comment (updated in place on every push) and repeats it in the job summary.
  • What the comparison shows: how many cases, runtime modules and assets are changed / new / deleted / unchanged, then one row per changed asset — raw before → after, and the percentage each of gzip, brotli and zstd moved. A suite-wide total is deliberately not reported: a single number over 1400 cases hides which file moved, which is the only actionable part. The applications are the exception: a Sample apps table gives each one's whole download (gzip and raw, summed over its assets), which per-asset rows hide once code moves between chunks. With no baseline to compare against, the same table ranks the biggest assets instead.
  • It never fails. There is nothing to assert here and no size budget to breach: a growing bundle is information, not a defect. Cases whose expected result is a build error emit nothing and are reported as such, with the reason, rather than counted as failures.
  • Options: --output <file> (report path), --baseline <file> (report to compare against), --summary <file> (append the markdown comparison, e.g. $GITHUB_STEP_SUMMARY), --filter / --negative-filter (regexps matched against <category>/<case>, also read from FILTER / NEGATIVE_FILTER).
  • Note: the cases are built with the defaults a user gets — minification on, no output.pathinfo — not with the ConfigTestCases ones. Needs Node.js >= 22.15 for zstd.

unitCases/BannerPlugin.unittest.js

  • Purpose: Tests Webpack’s BannerPlugin functionality.
  • Usage: Ensures that the plugin correctly adds banners to the bundled files.

Writing a test

Behavior across rebuilds is a watchCases/ case, never a unit test calling compiler.run in a loop — output.clean, HMR update files, caches, anything one build leaves for the next. Each numbered step directory is one rebuild; its tests read WATCH_STEP, STATS_JSON and COMPILER, and the config can read the step from test/helpers/currentWatchStep. What the output can't show (which files a plugin touched) is recorded by a plugin in the config, which pushes a compilation.errors entry when it differs (watchCases/clean/removed-assets). A step needing time to pass waits in its own it with a longer timeout (watchCases/clean/hot-update-slow-rebuild).

Snapshot printed code; assert everything else. When the thing tested is generated output — bundles, minified CSS/HTML, serialized ASTs, stats text — use toMatchSnapshot(), not expect(...).toBe(...) on fragments (which pins one substring and ignores every other byte). For behavior, invariants, equivalences and error paths use explicit expects; a snapshot there only records what happened to be true. Never snapshot a value some machine can't produce (a snapshot skipped without an optional browser or native binary is reported obsolete and fails the run there), and keep control characters out of snapshots (one NUL makes git treat the file as binary and hide its diff).

Example Test Case Structure

Many Webpack tests simulate small projects that are compiled during the test run.

For example, a configuration test may look like:

test/configCases/entry/simple/ index.js webpack.config.js expected.txt

Explanation:

  • index.js – entry file for the test project
  • webpack.config.js – configuration used by webpack
  • expected.txt – expected output or snapshot comparison

During the test run, webpack compiles this project and compares the result with the expected output to ensure behavior remains consistent.

Testing Framework

  • Jest is used for running tests.
  • Snapshots help maintain consistency in output.
  • Unit tests verify individual module functionality.
  • Integration tests ensure multiple components work together.

Splitting one file across workers

Jest runs each file in one worker, so a long suite in one file leaves the runner's other cores idle. --parts=<n> (read by jest.config.js) runs every matched file in n workers at once, as n projects whose coverage merges as usual; global setup and teardown run once. A file splits its own work with test/helpers/testParts.js: isInPart(key) tells whether this part runs the work named key, by a hash of the name, so nothing to measure or store; claimInPart(key) gives the work to the first part that asks, so a part that ends early takes more and the parts end together even where costs vary; and currentPart() returns [part, parts], e.g. to run a check once in part 1. Without the flag a file is one part and runs everything; a file that doesn't use the helper runs whole in every part, so pass the flag only where it runs files using it — e.g. CI's yarn cover:spec --parts=auto --maxWorkers=100%, where auto takes one part per core and 100% one Jest worker per core, as Jest's default leaves one core idle. CI's spec job also runs the CSS, HTML and XML suites, which take a minute together and so run whole in every part.

Suites that drive a real browser

unitCases/ProfilingPlugin.unittest.js, specCases/syntaxEquivalence.spectest.js and WebpackDevServer.longtest.js launch a browser through test/helpers/launchBrowser.js, which drives all three engines with playwright-core. A browser that will not launch fails the suite — it is never skipped, so no environment can report these checks as passing without having run them. The helper only launches the builds playwright-core pins (Chrome for Testing for Chrome), never the machine's own Chrome, which moves with the machine rather than the lockfile: run yarn setup:browsers chrome first, or set CHROME_EXECUTABLE_PATH to point at another binary.

syntaxEquivalence.spectest.js is the one that reads more than one engine: EQUIVALENCE_BROWSER takes chrome, firefox or webkit, or a list of them that gives each part of a --parts run its own engine: CI runs EQUIVALENCE_BROWSER=chrome,firefox,webkit with --parts=3, all three at once on one runner. Fetch the browser first — yarn setup:browsers (all three, or name one; --with-deps also installs the system libraries Firefox and WebKit need) — which installs into playwright's own cache (PLAYWRIGHT_BROWSERS_PATH, defaulting to ~/.cache/ms-playwright on Linux). Firefox and WebKit are playwright's own builds of those engines, patched for automation, so FIREFOX_EXECUTABLE_PATH and WEBKIT_EXECUTABLE_PATH must point at such a build, never a stock browser; Chrome is the stock Chrome for Testing, and CHROME_EXECUTABLE_PATH takes any Chrome. It compares effects, never CSSOM text: a stylesheet by the styles elements built from its selectors compute, at the widths its queries switch on; a page by its DOM and its render, with the dark color scheme read through each frame's own. Media is emulated on Chromium only, the engine the filed defects were measured on, so only there is a stylesheet naming print also read under it. A defect only one engine has is filed against it with a reason opening <engine> only: rather than tolerated in all three, and each list must match exactly, so a defect the engine fixes fails until its entry goes. A workaround in the suite's own code gets the same treatment through a probe in ENGINE_QUIRKS.

Every engine is the build the lockfile pins: the ones the installed playwright-core ships. Dependabot bumps it in a browsers group of its own, so a new engine arrives in one pull request, and a defect or quirk it fixes fails there rather than under unrelated changes.

test:bun and test:deno run the Chrome-driven ProfilingPlugin.unittest.js and WebpackDevServer.longtest.js too, on the same pinned Chrome.

Generated code is held to output.environment

No old browser runs in CI, so test/helpers/ecmaConformance.js asks the two questions one would answer, over every configCases case in ConfigTestCases and ConfigCacheTestCases. See that file for how it reads an environment.

  • Will it parse? Always on: every runtime module a build renders is parsed against the environment it was rendered for.
  • Will it run? restrictEnvironment: true in test.config.js removes from the bundle's realm what the target lacks, so webpack's guards are taken rather than stepped over. Web targets only.

Two more test.config.js fields: ecmaConformance: true widens the parse check to every emitted asset, so the case's own sources are held to its output.environment too, and ecmaConformanceExpected declares findings deliberate as regexps, each with its reason — one that stops matching fails the case.

ESM output is held to what a foreign bundler reads

test/helpers/analyzableConformance.js walks every output.module case the way another tool would: it takes the specifiers a lexer reports plus the new URL(…, import.meta.url) names only an AST sees, follows the literal ones from each entry, and asks two questions of what it reaches.

  • Does every chunk have a name someone else can follow? A chunk only __webpack_require__.e reaches is a finding, unless the build recorded why it kept the runtime form — performance.analyzableBailouts reads the same reasons — or an eval devtool hid every specifier inside a string.
  • Does every name it writes exist? A specifier naming a place inside the output that neither the assets nor the directory holds is a finding.

analyzableConformanceExpected in test.config.js declares findings deliberate as regexps, each with its reason — one that stops matching fails the case.

Under ecmaVersion/, the es5-* cases cover one runtime-emitting feature each (jsonp, importScripts, require and read-file chunk loading, workers and asset urls, css, hot updates, wasm, the library wrappers, Module Federation, the neutral platform); es-versions sweeps every rung of the esX ladder; environment-flags turns each flag off on its own against an otherwise current target, which a version sweep cannot do; and esm-environment repeats both over ESM output.

External test corpora

Upstream test suites webpack runs against but doesn't maintain — upstream's to change, ours only to pin.

Git submodules, all under test/external/, checked out on demand: yarn setup doesn't fetch them, and each CI job fetches only its own, one commit deep.

git submodule update --init --recursive --depth 1   # check out the commits the repo pins
git submodule update --init --recursive --remote --depth 1 # move every pin to its upstream tip

Keep --depth 1 (wpt alone is ~161k files). --remote changes the recorded commits, so git status shows the paths modified — commit that only once CI is green on them, or git submodule update back to the pins.

One upstream corpus is vendored rather than pinned as a submodule:

  • test/fixtures/acorn-corpus.json — acorn's test suite — the one upstream corpus vendored rather than pinned, because acorn's npm tarball ships no tests. unitCases/javascript/parser.unittest.js holds both webpack parser entry points to it and owns recording it (no generator script or package.json entry): it replays acorn's test/tests*.js against a recording driver, keeping sources and options but never expected trees, which come from acorn itself. Bumping the acorn devDependency moves the corpus; to refresh, clone acorn at the new version into node_modules/.cache/acorn-<version> and re-run with WEBPACK_UPDATE_ACORN_CORPUS=1. With that checkout present the run checks the vendored corpus against it; without it (CI, most machines) the corpus stands on the version it names, which the run pins to the installed acorn.

Running one integration case

Run one integration case by name (<category> <case-name>, e.g. css basic):

yarn test:basic --testPathPatterns="ConfigTestCases" --testNamePattern="<category> <case>"

Swap in StatsTestCases, HotTestCases, WatchTestCases, … (full matrix in below). Prefix WEBPACK_TEST_STATS=1 to also write each build's verbose stats.txt (stats.<step>.txt for watch cases) next to its output under test/js/; it's off by default because it costs about as much as the build. The test262, html5lib, syntax-equivalence and css-parsing suites need submodules — run git submodule update --init --depth 1 test/external/test262-cases test/external/html5lib-tests test/external/wpt test/external/css-parsing-tests first, or they fail confusingly.

A configCases/ case is a mini project: index.js (assertions; a throw fails) plus webpack.config.js; the emitted bundle is executed, so it must run. Optional: errors.js / warnings.js export matcher arrays for expected diagnostics (otherwise any error/warning fails the case); test.filter.js returns false to skip (e.g. by Node version when the fixture itself needs newer syntax — see Target the Node baseline); test.config.js customizes the run (e.g. findBundle). Put DOM changes such as a server-rendered <link> in setupDocument(document, { options, index, target }), which runs once per run before any bundle executes, never in moduleScope, which runs again for every module and so repeats them.

How to Run Tests

More scripts

Every command is a package.json script; AGENTS.md lists the few whose use isn't obvious. The rest:

  • yarn setup — Install dependencies and link the checkout as webpack; non-interactive off a TTY.
  • yarn tsc — Type check the lib/ JSDoc.
  • yarn validate:changeset — Validate pending .changeset/ files.
  • yarn test:unit — All *.unittest.js.
  • yarn test:integration — Integration suites (basictest/longtest/test).
  • yarn test:test262 / test:html5lib / test:css-parsing / test:xmlconf — Spec-conformance suites.
  • yarn test:minify-corpora — webpack's JS minifier vs the published one it replaces, byte for byte, over every JS corpus (details).
  • yarn test:syntax-equivalence — HTML/CSS printers vs a real browser's reading of their output (configCases, wpt).
  • yarn test:size — Generated-code size over all configCases/ (per asset, plus runtime modules per runtime).
  • yarn cover:unit — Unit-test coverage.
  • yarn types:cover — Share of lib/ that is precisely typed.
  • yarn build:examples — Build examples/ (verify after changing options).

To execute all tests:

yarn test

Choose test command based on modified directory:

Modified directory/file Command
test/unitCases/**/*.unittest.js yarn test:base --testPathPatterns="<filename>"
test/specCases/ Run the matching yarn test:<suite> command
test/cases/ yarn test:basic
test/configCases/ yarn test:basic --testPathPatterns="ConfigTestCases"
test/statsCases/ yarn test:basic --testPathPatterns="StatsTestCases"
test/watchCases/ yarn test:base --testPathPatterns="WatchTestCases"
test/hotCases/ yarn test:base --testPathPatterns="HotTestCases"
test/benchmarkCases/ FILTER="<case-name>" yarn benchmark
lib/runtime/ yarn test:size (size of the generated code; --filter "<category>/" narrows it)
test/external/test262-cases/ yarn test:test262 (requires git submodule update --init test/external/test262-cases first)
test/external/html5lib-tests/ yarn test:html5lib (requires git submodule update --init --depth 1 test/external/html5lib-tests test/external/wpt first)
test/external/wpt/ yarn test:html5lib + yarn test:syntax-equivalence (require git submodule update --init --depth 1 test/external/html5lib-tests test/external/wpt first)
test/external/css-parsing-tests/ yarn test:css-parsing (requires git submodule update --init test/external/css-parsing-tests first)
test/external/xml-conformance-suite/ yarn test:xmlconf (requires git submodule update --init --depth 1 test/external/xml-conformance-suite first; without it the suite passes as a no-op)

Running a single test case with --testNamePattern. The test name format is <category> <case-name> (e.g., css basic, asset url):

yarn test:basic --testPathPatterns="ConfigTestCases" --testNamePattern="css basic"

Multiple patterns can be combined with |:

yarn test:basic --testPathPatterns="ConfigTestCases" --testNamePattern="css basic|css url"

Contribution Guide

  • Add new test cases in the appropriate folder.
  • Use Jest assertions and mocks for consistency.
  • Run yarn test before pushing changes to validate functionality.