C.W.K.
Stream
Lesson 03 of 04 · published

Watch Mode, UI Mode, and Filtering

~15 min · vitest-setup, watch-mode, ui-mode, filtering

Level 0Test Curious
0 XP0/32 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"A test that re-runs on every save is the difference between a feedback loop and a ceremony."

Watch Mode Is the Default

Plain vitest (no arguments) starts in watch mode. It runs the relevant tests once, then sits there listening for file changes. When you save a file, Vitest figures out which tests depend on the change (via its import graph) and re-runs only those. The full suite stays cold until you ask for it.

The reason this matters: 200 unit tests at 30 ms each is six seconds. Running the full suite on every save is fine. But Vitest's smart re-run usually finishes in 200-500 ms — fast enough that you forget the tests are running. The loop becomes: change code, glance at the terminal, see the result, repeat.

The Watch-Mode Keys You'll Actually Use

While watch mode is running, single-key shortcuts let you filter and re-run without leaving the terminal:

  • a — re-run all tests (forces a full pass)
  • f — re-run only failed tests (sweet for chasing a regression)
  • p — filter by filename pattern (live regex)
  • t — filter by test-name pattern
  • q — quit
  • r — manually re-run all

You don't need to memorize them. Vitest prints the menu when you press h for help. The two you'll use daily are p (drill into one file) and f (chase the failures).

Run watch in one terminal, your dev server in another. Don't multiplex — splitting them across panes keeps the failure output legible. tmux or your IDE's split terminal does the job. The few seconds you spend setting that up gets paid back the first time a test fails on a real change.

CI Mode — vitest run

vitest run runs the suite once and exits with a non-zero code on failure. This is the CI shape — no watching, no UI, no interactive controls. The output is plain text suitable for log aggregation. Most projects have both: npm test aliased to vitest (watch) for local, npm run test:ci aliased to vitest run --reporter=verbose for pipelines.

UI Mode — the Browser Dashboard

vitest --ui opens a local web UI alongside the watch-mode runner. The UI shows the full test tree, surfaces the last failure with its diff inline, exposes a per-file re-run button, and overlays coverage on source files if you've enabled it. The UI is most useful for two things: hunting flakes (you see the run history visually) and reviewing coverage gaps (the source overlay is easier to read than a terminal coverage report).

It's not a replacement for the terminal — the terminal is faster for the inner loop. But when something weird is happening (one test passes locally, fails in CI), the UI's history makes the divergence visible.

Filtering is debugging. When a single test fails, don't re-run the suite — filter to that file (p + filename), let it cycle in milliseconds, fix, see the green. The fastest debug loop you have is the one where the test runs in under a second.

Code

Three modes — watch, run, UI·bash
# Local — watch mode (default)
npm test
# or equivalently:
npx vitest

# CI — run once and exit
npm run test:ci
# or:
npx vitest run

# Open the browser UI (alongside watch mode)
npm run test:ui
# or:
npx vitest --ui
Watch mode console — the prompt you'll live in·text
Tests  3 passed (3)
  ✓ src/lib/format.test.ts (2)
  ✓ src/lib/parse.test.ts (1)

Test Files  2 passed (2)
     Tests  3 passed (3)
  Start at  14:23:01
  Duration  127ms (transform 19ms, setup 0ms, collect 14ms, tests 8ms)

 PASS  Waiting for file changes...
       press h to show help, press q to quit
Filtering — interactive (p/t keys) or via CLI args·bash
# CLI arg filtering — useful in CI or when you know what you want

# Only files matching pattern
npx vitest format

# Only test names matching pattern (note the -t flag)
npx vitest -t 'rounds to two decimals'

# Run a single file by path
npx vitest src/lib/format.test.ts

# Run a specific test by name + file
npx vitest src/lib/format.test.ts -t 'rounds to two decimals'
`.only` and `.skip` — sharp tools, ground them after use·typescript
// Source-level focus — for one-test debugging, not commits
import { describe, it } from 'vitest';

describe('parser', () => {
  it.only('handles the weird unicode case', () => {
    // While this is here, only this test runs in the file.
    // Remove .only before commit — eslint rule should catch it.
  });

  it('handles the normal case', () => { /* skipped */ });
});

// Or skip a known-broken test temporarily:
it.skip('handles the broken case (tracked in #1234)', () => { /* skipped */ });

External links

Exercise

Run npm test in watch mode against the project from lesson 1. Edit one of your test files to make a test fail (change toBe(5) to toBe(6)). Notice the watch mode re-runs in milliseconds and shows the failure. Press f to filter to failing tests. Fix the assertion. Watch the green return. Now press p, type a filename pattern, watch the suite narrow. Get a feel for the loop — this is what testing looks like when it isn't ceremony.
Hint
If watch mode doesn't auto-re-run when you save, your editor might be writing through a temp file (atomic save) that confuses the watcher. Look for an atomic save setting in your editor or pass --watch.usePolling true to Vitest.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.