Repository navigation
test_runner: directory arguments to --test fail with MODULE_NOT_FOUND on Windows (regression since v20) #64555
Description
Version
test_runner: directory arguments to --test crash with MODULE_NOT_FOUND instead of being searched (since v21)
Platform
Microsoft Windows NT 10.0.26200.0 x64
Subsystem
test_runner
What steps will reproduce the bug?
Create a directory containing a single test file:
tests/sample.test.mjs:
import test from 'node:test';
test('ok', () => {});Then, on Windows, pass the directory to the test runner:
node --test tests
node --test tests/
node --test tests\All three fail with MODULE_NOT_FOUND — the directory is executed as if it
were a test file instead of being searched for test files.
For contrast, all of the following work:
node --testwith no arguments in the same folder (default discovery finds and runs the test)- the same three commands on Node v20.20.2 on Windows
- passing a directory on Linux/macOS
How often does it reproduce? Is there a required condition?
Always, on Windows, whenever a directory path is passed as an argument to --test.
What is the expected behavior? Why is that the expected behavior?
Passing a directory to node --test should recursively search it for test
files and run them, as documented, as Node v20 does on Windows, and as current
versions still do on Linux/macOS.
What do you see instead?
The directory is treated as a single test file and executed directly, which
fails in the module loader:
PS C:\Users\KIIT\node-bug-repro> node --test tests/
node:internal/modules/cjs/loader:1368
throw err;
^
Error: Cannot find module 'C:\Users\KIIT\node-bug-repro\tests'
at Function._resolveFilename (node:internal/modules/cjs/loader:1365:15)
at defaultResolveImpl (node:internal/modules/cjs/loader:1021:19)
at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1026:22)
at Function._load (node:internal/modules/cjs/loader:1175:37)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)
at node:internal/main/run_main_module:36:49 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}
Node.js v22.18.0
✖ tests (70.2044ms)
'test failed'
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 79.0802
✖ failing tests:
test at tests:1:1
✖ tests (70.2044ms)
'test failed'The output is identical for node --test tests and node --test tests\.
Additional information
This appears to be a regression between v20.20.2 (works) and v22.x (broken).
Possibly related to the test runner's file discovery moving to the internal
glob implementation (createTestFileList in lib/internal/test_runner/runner.js),
where a directory pattern seems to match the directory itself rather than
being expanded to the test files inside it.
Happy to work on a fix if a collaborator confirms this is a bug.
Activity
dhruvvv0913 commentedon Jul 17, 2026
Update after more digging — this is not Windows-specific; I've updated the title.
I retested on Linux x64 (Ubuntu 24.04 via WSL2, official v24.18.0 binary):
node --test testsfails the same way. My "works on Linux/macOS" note in theoriginal report was wrong.
The root cause appears to be the v20 → v22 change in test file discovery:
processPath()callsstatSync()and recursively walks directorieswith
readdirSync(), so directory arguments worked:https://github.com/nodejs/node/blob/v20.20.2/lib/internal/test_runner/runner.js#L91-L126
createTestFileList():https://github.com/nodejs/node/blob/v22.18.0/lib/internal/test_runner/runner.js#L108-L126
A pattern that literally matches a directory returns the directory path
itself (standard glob semantics — no expansion), and the runner then spawns
it as if it were a test file, which crashes with
MODULE_NOT_FOUND.Worth noting: the v20 docs explicitly documented directory arguments, with a
directory in the example (
node --test test1.js test2.mjs custom_test_dir/—"The test runner will also recursively search the directory"):
https://github.com/nodejs/node/blob/v20.20.2/doc/api/test.md
Current docs mention only glob patterns, but there was no deprecation cycle or
clear error — scripts using the documented form went from working to a cryptic
crash.
Proposed fix: in
createTestFileList(), when a user-supplied pattern has noglob magic and stats to a directory, expand it to
<pattern>/<kDefaultPattern>before matching — restoring the documented v20 behavior while keeping the glob
implementation. Alternatively, if directory support is intentionally dropped, a
clear error message would at least remove the confusing crash. Happy to open a
PR either way.
efekrskl commentedon Jul 18, 2026
I think this started in v21 and not v22. It was also a semver major
That being said I see how it is confusing though. I'd at least expect
node --test tests/to workdhruvvv0913 commentedon Jul 18, 2026
Thanks — you're right, the glob-based discovery landed in v21, so v21.0.0 is the actual change point (I only had v20 and v22 installed to compare; I'll update the title). And fair point on it being semver-major.
Given you'd also expect node --test tests/ to work, would a PR along the lines I sketched above be welcome? Concretely: in createTestFileList(), when a user-supplied pattern contains no glob magic and statSync says it's a directory, expand it to join(pattern, kDefaultPattern) before matching — restoring the documented v20 behavior on top of the glob implementation, with a test in test/parallel/. Happy to open it if that approach sounds right.
efekrskl commentedon Jul 18, 2026
It was just my 2 cents.
You are always welcome to open a PR, it might get some eyes and start a discussion. Worst case it gets rejected, but we all learn something.
bitpshr commentedon Jul 21, 2026
Confirming this reproduces, and I think it's actually not Windows-specific. On macOS
node --test testsfails the same way:node@20searches the directory and passes, butnode@22andnode@24both fail withMODULE_NOT_FOUND.It looks like the directory argument is matched as a literal glob pattern, so it resolves to the directory itself and then gets run as a test file, instead of being searched for the test files inside it. Opened #64637 to expand a directory argument back into a search for the default test files within it.
test_runner: restore directory search for --test
test_runner: restore directory search for --test
test_runner: restore directory search for --test
H1 — socle V3 : tests, jetons de design, squelette de page (#2)
Quote the test glob so the runner finds files on Node 24
Give the page one module for what it does in response to the viewport (…
test(board): pass asset tests to node by file
fix: pass explicit test files to node --test (dir arg broken on Node 22)
Fix CI: pass test files explicitly, not the tests directory