`@angular/build:unit-test` virtual `init-testbed.js` guards `initTestEnvironment()` behind a once-per-worker symbol → stale DomAdapter under vitest ≥4.0.5 + `isolate: false`
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 55/100
- Type d'issue
- Bug
- Clarté
- Clairement spécifiée
- Activité
- Calme
- Stack technique
- angular, typescript
- Domaine
- build-system, testing-qa
Piste de recherche
Commencez par packages/angular/build/src/builders/unit-test/runners/vitest/build-options.ts et plugins.ts, puis exécutez à plusieurs reprises la suite jsdom signalée avec ng test --force, isolate: false et vitest 4.x. Vérifiez que l'erreur stale-DomAdapter ne se produit plus entre les fichiers de spec, tout en préservant le comportement de configuration des unit-tests du builder ; l'issue propose le pattern reset-and-reinitialize de analogjs/analog#2244 et indique la documentation comme suivi secondaire.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
This is a follow-up to the now-closed/auto-locked #32754 with the missing details. That issue was dismissed as potentially analog-specific because the reporter had
@analogjs/vitest-angularin their deps. This report uses@angular/build:unit-testonly and pins the bug to specific lines in@angular/build's own source.
Which @angular/* package(s) are the source of the bug?
@angular/build
Is this a regression?
Yes — surfaces with vitest ≥4.0.5 (see vitest-dev/vitest#8944).
Description
@angular/build:unit-test's vitest runner combines two defaults that interact badly under vitest ≥4.0.5:
plugins.ts:254hardcodesisolate: falsefor the vitest pool (intentional, "align with the Karma/Jasmine experience"). Worker module graphs are therefore reused across spec files.build-options.ts:73-89— the injected virtualinit-testbed.jswrapsgetTestBed().initTestEnvironment(...)in anif (!globalThis[ANGULAR_TESTBED_SETUP])guard. The comment on line 79 explicitly says "the guard condition above ensures that the setup is only performed once". That's the anti-pattern.
The first spec file in a worker initializes a platformBrowserTesting whose DomAdapter captures the jsdom document as a closure reference. Subsequent spec files in the same worker skip the init block entirely, so the DomAdapter keeps being reused. When jsdom's document swaps between spec files — and vitest ≥4.0.5 no longer re-executes setup files between spec files under isolate: false (vitest-dev/vitest#8944) — _getDOM().getDefaultDocument().createElement(tagName) returns something that is not a real HTMLElement, and DOMTestComponentRenderer.insertRootElement crashes:
```
TypeError: rootElement.setAttribute is not a function
at DOMTestComponentRenderer.insertRootElement (@angular/platform-browser/testing)
at _TestBedImpl.createComponent
```
Different spec files fail each run; each failing spec passes in isolation. Classic test-isolation bug.
The analog project had the analogous pattern in setupTestBed() and fixed it in analogjs/analog#2244 by calling resetTestEnvironment() + initTestEnvironment() on every setup invocation instead of guarding with a once-only singleton.
Reproduction
I can put up a public minimal repro if helpful, but the bug is visible from the builder source alone — the conditions are:
- Angular 22 (or 21 with vitest ≥4.0.5) monorepo using
@angular/build:unit-testwith jsdom. runnerConfigunset, so the builder's defaultisolate: falseapplies.- Suite of ~50+ spec files to make the race frequent.
- Run
ng test --force(or equivalent) several times. A different 1–10 specs crash each run with thesetAttributetrace.
Confirmed environment:
```
Angular CLI: 22.0.0-next.6
@angular/build: 22.0.0-next.6
@angular/core: 22.0.0-next.9
vitest: 4.1.4 (via ^4.0.17)
Environment: jsdom
Runtime: Node 22 / Bun 1.x
OS: Windows 11 (also reported on Ubuntu CI via analogjs/analog#2222)
```
Exception
```
TypeError: rootElement.setAttribute is not a function
❯ DOMTestComponentRenderer.insertRootElement node_modules/@angular/platform-browser/fesm2022/testing.mjs:24
❯ _TestBedImpl.createComponent packages/core/testing/src/test_bed.ts:420
```
(The #32754 variant Cannot set base providers because it has already been called is the same root cause but a different downstream symptom — it fires when the user's own test-setup.ts re-calls initTestEnvironment. Projects that don't re-call it land on setAttribute is not a function instead.)
Proposed fix
Primary — mirror analogjs/analog#2244. Replace the if (!globalThis[ANGULAR_TESTBED_SETUP]) guard in build-options.ts:73-89 with a reset-and-reinit pattern:
```ts
getTestBed().resetTestEnvironment();
getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
// ...
});
```
Even under isolate: false, if the setup file re-runs (or a user hook calls it), each spec file gets a fresh platform targeting the current jsdom.
Secondary (defensive) — either flip the default to isolate: true with a documented opt-out for projects that want the Karma-style speed, or make DOMTestComponentRenderer.insertRootElement throw a clearer error when rootElement.setAttribute is not callable (e.g. "TestBed's DOM adapter is referencing a document that has been torn down — check your vitest `isolate` setting"). Today the TypeError has no breadcrumb to the root cause.
Docs — the unit-test builder docs should warn that isolate: false + vitest ≥4.0.5 + jsdom silently bleeds DOM state across spec files.
Local workaround
Override to isolate: true via a project-level vitest-base.config.ts (possible because runnerConfig: true merges user config on top of the builder defaults). 10 consecutive ng test --force runs then pass deterministically. Wall-time cost is ~10–30%. This is a workaround, not a fix — the builder's guard is what should change.
References
- vitest-dev/vitest#8944 — vitest 4.0.5 regression that triggers this class of flake.
- angular/angular-cli#32754 — closed/auto-locked predecessor of this report. Same root cause, different surface symptom, closed as "insufficient information."
- analogjs/analog#2222 — same error stack downstream in analog's vitest-angular stack.
- analogjs/analog#2244 — reference fix pattern.
- Langage dominant
- TypeScript
- Étoiles
- 27k
- Forks
- 11.8k
- Merge moyen
- 17 h 25 min
- PR mergées (30 j)
- 183
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de angular/angular-cli
-
Can't use an array of hostnames in --allowedHosts cli parameter in @angular/build:dev-server Ouvertearea: @angular/build gemini-triaged
Difficulté 2/5 1-3 heures Accessibilité débutants 74/100
angular/angular-cli#33955 ·
-
area: @angular/cli gemini-triaged
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
angular/angular-cli#33055 · 1 commentaire · 3 réactions ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34131 · 1 personne assignée ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34130 · 1 personne assignée ·
-
angular/build:library area: @angular/build gemini-triaged
angular/angular-cli#34128 · 1 personne assignée ·
Toutes les issues de angular/angular-cli
Issues similaires
-
Difficulté 2/5 1-3 heures Accessibilité débutants 65/100
-
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
-
bug v2
Difficulté 2/5 1-3 heures Accessibilité débutants 75/100
modelcontextprotocol/inspector#2458 · 1 commentaire ·
-
Difficulté 1/5 Moins d'une heure Accessibilité débutants 75/100
railmapgen/rmp-gallery#4068 ·
-
Mend: dependency security vulnerability status: needs triage 🕵️♀️
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
carbon-design-system/ibm-products#9907 ·