A custom test reporter for node:test designed specifically for large-scale projects with hundreds of test files and thousands of tests. Focuses on visibility, progress tracking, and especially diagnosing flaky/stuck tests.
- ๐ File-level visibility: Pretty print each test file as it's processed with pass/total counters
- ๐งช Test-level visibility: Show each individual test with nesting support (describe โ test)
- โณ Incomplete test detection (critical): When Ctrl-C is pressed, print exactly which tests never completed
- ๐จ Real-time progress: Show what's currently running for large test suites
- โก Slow test detection: Identify potential flaky tests by duration
- ๐จ Beautiful TTY output: Colors, unicode icons, and elegant formatting
- ๐ Clean CI output: Line-based, grep-friendly format when piped
- Node.js 22+ (the reporter uses modern Node.js test runner APIs)
npm install --save-dev @matteo.collina/test-reporter# After installing
node --test --test-reporter=@matteo.collina/test-reporter
# Or download and use directly
node --test --test-reporter=./node_modules/@matteo.collina/test-reporter/reporter/reporter.jsOptions can be set via environment variables:
# Set options via environment
NODE_TEST_REPORTER_OPTIONS="timeout-warning=3000,stuck-threshold=10000,progress=off" \
node --test --test-reporter=@matteo.collina/test-reporterThe reporter supports parallel test execution:
node --test --test-reporter=@matteo.collina/test-reporter --test-concurrency=4const reporter = require('@matteo.collina/test-reporter');
// Use with node:test runner| Option | Default | Description |
|---|---|---|
timeout-warning |
5000 |
Threshold (ms) for considering a test "slow" |
stuck-threshold |
30000 |
Threshold (ms) before warning about potentially stuck tests |
show-passing |
true |
Show passing tests (set to false to see only failures) |
show-skip |
true |
Show skipped tests |
progress |
auto |
Progress mode: auto, on, or off
|
๐ src/services/user.service.test.ts [3/4]
โ UserService
โ should create user ................................... 45ms
โ should validate email ................................ 12ms
โณ should handle edge cases ............................. ~5s โขโขโข
โ should delete user ................................... 150ms
AssertionError: Expected user to be deleted
at src/services/user.service.test.ts:44:17
๐ src/api/routes.test.ts [2/2]
โ GET /users
โ returns 200 with user list ........................... 23ms
โ POST /users
โ creates new user ..................................... 67ms
^C
โ ๏ธ INCOMPLETE TESTS DETECTED
These tests started but never completed. The test at the bottom ran longest
and is most likely the one blocking:
โณ src/db/connection.test.ts
โโ Database โบ should handle concurrent connections (45s ago)
โณ src/cache/redis.test.ts
โโ RedisClient โบ should reconnect (12s ago)
โณ src/queue/worker.test.ts
โโ Worker โบ should process jobs (8s ago)
๐ก Tip: Check the last test for: infinite loops, blocking sync calls,
unawaited async, database deadlocks, or hanging network requests
โ TEST SUITE COMPLETE โ 47 files | 312 passed | 3 failed | 5 skipped | 45.2s
โ FAILURES (3):
1. src/services/user.service.test.ts:44 โ should delete user
2. src/api/auth.test.ts:23 โ should reject invalid token
3. src/db/connection.test.ts:87 โ should rollback on error
โก SLOW TESTS (potential flaky tests):
1. 12.4s src/db/connection.test.ts โ Database โบ should pool connections
2. 8.7s src/cache/redis.test.ts โ RedisClient โบ should reconnect
3. 5.2s src/queue/worker.test.ts โ Worker โบ should process jobs
FILE: src/services/user.service.test.ts [3/4]
PASS UserService > should create user [45ms]
PASS UserService > should validate email [12ms]
WARN UserService > should handle edge cases [~5s]
FAIL UserService > should delete user [150ms]
AssertionError: Expected user to be deleted
at src/services/user.service.test.ts:44:17
### PROCESS INTERRUPTED - INCOMPLETE TESTS
The following tests started but never completed:
(Ordered by start time - the last one likely caused the hang)
[45s] src/db/connection.test.ts::Database > should handle concurrent connections <-- LONGEST
[12s] src/cache/redis.test.ts::RedisClient > should reconnect
[ 8s] src/queue/worker.test.ts::Worker > should process jobs
In large projects with hundreds of test files, tests can hang due to:
- Infinite loops in async code
- Unawaited promises
- Database connection deadlocks
- Network request timeouts
- Blocking synchronous calls
Without visibility, you just see a hung test runner with no idea which test is stuck.
This reporter tracks every test that receives test:start and compares it to test:complete. If a test never completes (common on SIGINT), it reports exactly which tests were stuck.
The longest-running incomplete test is shown last โ that's most likely your culprit.
reporter/
โโโ index.js # Entry point
โโโ reporter.js # Main async generator - handles node:test events
โโโ state-tracker.js # Tracks running/finished test state
โโโ printer.js # Formatting utilities
-
node:testsends events through the async generator -
reporter.jsreceives events (test:start,test:pass,test:fail, etc.) -
state-tracker.jsmaintains a map of tests by file and completion status -
printer.jsformats output for TTY or non-TTY environments - On
SIGINT/SIGTERM/exit, incomplete tests are reported
Please see the GitHub repository for source code and issues.
MIT