Skip to content

@angular/build:unit-test (vitest): every test fails with "Vitest failed to find the runner" on Windows when the cwd drive-letter casing differs from the on-disk casing #33559

Description

@reifi

Command

test

Is this a regression?

The previous version in which this bug was not present was

No response

Description

On Windows, the drive-letter casing of process.cwd() is whatever the user typed:
cmd.exe preserves it literally, so cd c:\projects\my-app yields a cwd of
c:\projects\my-app while the canonical on-disk path is C:\projects\my-app.

The unit-test builder derives workspaceRoot from the cwd and passes it verbatim to
Vitest as root (packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts,
the startVitest(...) call). Vitest's module runner then executes test files and the
builder-generated init module (angular:test-bed-init) under file:///c:/... URLs,
while the init module's externalized import 'vitest' is resolved by Node to the real
on-disk casing, file:///C:/....

Node's ESM cache is keyed by URL string, so file:///c:/... and file:///C:/...
load the vitest package twice. The second copy has no worker state, and its entry
guard throws — for every test file in the project:

Error: Vitest failed to find the runner. One of the following is possible:
- "vitest" is imported directly without running "vitest" command
- "vitest" is imported inside "globalSetup" (to fix this, use "setupFiles" instead, because "globalSetup" runs in a different context)
- "vitest" is imported inside Vite / Vitest config file
- Otherwise, it might be a Vitest bug. Please report it to https://github.com/vitest-dev/vitest/issues

 ❯ angular:test-bed-init:angular:test-bed-init:16:5

 Test Files  194 failed (194)

The run banner shows which spelling a run used, making the difference easy to see:

 RUN  v4.1.10 c:/projects/my-app     ← fails (cwd typed lowercase)
 RUN  v4.1.10 C:/projects/my-app     ← works

This is easy to hit unknowingly: any cmd session entered via a lowercase cd, or an
IDE whose workspace was registered with a lowercase path (its integrated terminals
inherit the lowercase cwd).

Minimal Reproduction

Reproduced (by human me)

On Windows, in cmd.exe:

ng new repro --defaults
cd /d c:\<path-to>\repro     REM note: lowercase drive letter
ng test --watch=false

Every spec fails with the error above. Re-entering with the canonical casing makes the
same command pass:

cd /d C:\<path-to>\repro
ng test --watch=false

Suggested fix (AI generated)

Canonicalize the workspace root before handing it to Vitest, e.g. with
fs.realpathSync.native(workspaceRoot) in the Vitest executor (or once at CLI
workspace-resolution level, which would cover all builders). realpathSync.native
returns the true on-disk casing and would also normalize two related path-identity
variants that can cause the same double-load: junction/symlink paths and 8.3 short
names.

Note the same casing must be used consistently by the in-memory test-file provider
(its resolveId/load compare paths against workspaceRoot), so normalizing at the
point where workspaceRoot is first established seems safest.

Workaround

Enter the project with the canonical drive-letter casing (cd /d C:\...), or wrap
ng test in a script that re-spawns it with cwd: fs.realpathSync.native(process.cwd()).

Exception or Error


Your Environment

_                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI       : 22.0.5
Angular           : 22.0.6
Node.js           : 24.15.0
Package Manager   : npm 11.17.0
Operating System  : win32 x64

┌─────────────────────────────────┬───────────────────┬───────────────────┐
│ Package                         │ Installed Version │ Requested Version │
├─────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular/build                  │ 22.0.5            │ ^22.0.5           │
│ @angular/cdk                    │ 22.0.4            │ ^22.0.4           │
│ @angular/cli                    │ 22.0.5            │ ^22.0.5           │
│ @angular/common                 │ 22.0.6            │ ^22.0.6           │
│ @angular/compiler               │ 22.0.6            │ ^22.0.6           │
│ @angular/compiler-cli           │ 22.0.6            │ ^22.0.6           │
│ @angular/core                   │ 22.0.6            │ ^22.0.6           │
│ @angular/forms                  │ 22.0.6            │ ^22.0.6           │
│ @angular/google-maps            │ 22.0.4            │ ^22.0.4           │
│ @angular/localize               │ 22.0.6            │ ^22.0.6           │
│ @angular/material               │ 22.0.4            │ ^22.0.4           │
│ @angular/material-luxon-adapter │ 22.0.4            │ ^22.0.4           │
│ @angular/platform-browser       │ 22.0.6            │ ^22.0.6           │
│ @angular/router                 │ 22.0.6            │ ^22.0.6           │
│ @angular/upgrade                │ 22.0.6            │ ^22.0.6           │
│ rxjs                            │ 7.8.2             │ ^7.8.2            │
│ typescript                      │ 6.0.3             │ ^6.0.3            │
│ vitest                          │ 4.1.10            │ ^4.1.10           │
│ zone.js                         │ 0.16.2            │ ^0.16.2           │
└─────────────────────────────────┴───────────────────┴───────────────────┘

Anything else relevant?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions