With Angular's unit-test builder, what changes when specs run in jsdom versus a real browser configured through the browsers option?
answer
- emulated DOM is the default
- no layout without a browser
- a provider package for browsers
- Headless suffix or CI variable
basics
~20 sBy default specs run in Node.js with jsdom (or happy-dom): fast, but no layout or real browser behaviour. Setting browsers runs them in a real browser through a provider like @vitest/browser-playwright: slower, with real layout, CSS and events.
solid answer
~40 sWithout the `browsers` option, the unit-test builder runs Vitest specs in Node.js on an emulated DOM, preferring `happy-dom` if installed and otherwise `jsdom`, one of which must be present. That is fast but has no layout: element sizes are zero, CSS is not applied as a browser would, and browser APIs are whatever the emulator implements. Setting `browsers`, for example `["chromium"]`, runs the same specs in a real browser through a provider such as `@vitest/browser-playwright`; headless is automatic for names ending in `Headless` or when `CI` is set. That costs start-up time and CI setup but gives real layout, focus and events. TestBed and harness APIs behave the same in both.
code
json · 17 lines{
"projects": {
"profile-app": {
"architect": {
"test": {
"builder": "@angular/build:unit-test",
"configurations": {
"browser": {
"browsers": ["chromium"],
"browserViewport": "1280x800"
}
}
}
}
}
}
}go deeper
Recall that specs run in Node.js with jsdom unless the browsers option names a real browser, and that browser runs need a provider package.
Explain what the emulated DOM lacks, layout, applied CSS and full browser APIs, and how browsers, headless and browserViewport configure a browser run.
Decide per spec which environment it needs, spot tests that pass in jsdom for the wrong reason, and keep browser runs a small, deliberate set.
Balance CI cost and fidelity across teams: which suites run emulated, which in browsers, and how that split relates to the end-to-end layer.
## Two places a spec can run The Angular CLI's `@angular/build:unit-test` builder can execute the same compiled specs in two kinds of **test environment**: - **DOM emulation in Node.js** (the default). A library implements the DOM API in JavaScript. The builder uses **happy-dom** if it is installed and otherwise **jsdom**; for runs without browsers one of them must be installed, or the builder stops with an error asking for it. New projects get `jsdom`. - **A real browser**, chosen with the builder's **`browsers`** option. With the Vitest runner this needs a **browser provider** package: `@vitest/browser-playwright` (Chromium, Firefox, WebKit), `@vitest/browser-webdriverio` (Chrome, Firefox, Safari, Edge) or `@vitest/browser-preview` for WebContainer environments. Browser names depend on the provider, for example `chromium` for Playwright. Headless mode is automatic when a browser name ends in `Headless` (such as `ChromeHeadless`) or the `CI` environment variable is set; the `headless` option forces it for every configured browser. ## What changes between them | Aspect | jsdom / happy-dom in Node.js | Real browser via `browsers` | |---|---|---| | Start-up and speed | fast, no browser process | slower, launches and drives a browser | | Layout | none: sizes and positions are zero | real layout, `getBoundingClientRect()` works | | CSS | parsed, but not laid out or painted | computed styles, media queries, transitions | | Events | synthetic, dispatched by the test | real browser event handling and focus rules | | Browser APIs | whatever the emulator implements | the full platform | | CI requirements | Node.js only | browser binaries and a provider package | | Typical use | most component and service specs | layout-dependent and browser-specific behaviour | ## When the emulator is not enough Specs that pass in a real browser can fail or, worse, pass for the wrong reason in jsdom: 1. **Anything that measures**: virtual scrolling, tooltips that position themselves, resize logic, `IntersectionObserver`-driven loading. 2. **Focus and keyboard behaviour** that depends on the browser's own rules. 3. **CSS-driven state**: visibility decided by a media query or a class that only matters once styles are applied. 4. **Browser APIs the emulator lacks or stubs**, which tests must then fake explicitly. A common arrangement is to keep the bulk of specs on the fast emulated DOM and run a smaller, clearly separated set in a browser, either as a second test configuration with `browsers` set or through the end-to-end suite. ## Configuring it - `ng test --browsers=chromium` runs once in a browser without editing `angular.json`. - `"browsers": ["chromium"]` in the `test` target's options makes it the default for that target. - `browserViewport` sets the viewport as `widthxheight` for browser runs. - The Karma runner always runs in browsers; the emulated DOM is a Vitest-runner feature. ## Troubleshooting environment-specific failures - **"A DOM environment is required"**: neither `jsdom` nor `happy-dom` is installed; add one. - **A spec passes in jsdom but the feature is broken in the browser**: the spec probably asserts something the emulator cannot produce, such as a measured size; move it to a browser run. - **A spec fails in jsdom with a missing API**: the emulator does not implement that browser feature; fake it explicitly in the spec or run that spec in a browser. - **Browser runs fail to start in CI**: the provider package or its browser binaries are missing on the agent, or a headed browser was requested on a machine without a display. - **Different results between happy-dom and jsdom**: the two emulators implement different subsets; installing happy-dom silently switches the environment, so pin the choice deliberately. ## What does not change The Angular side is identical in both environments: `TestBed`, `ComponentFixture`, `HttpTestingController` and CDK harnesses behave the same. Harness interactions are simulated in unit tests either way; a real browser changes what the DOM can do, not how Angular's testing APIs work. ## Choosing, as an interviewer expects you to - Default to the emulated DOM for speed; most component logic does not depend on layout. - Move a spec to a browser run when it asserts something only a browser can produce, not as a blanket fix for flaky tests. - Remember that browser runs add CI cost: binaries, provider packages and longer start-up.
- A tooltip component positions itself using getBoundingClientRect(), and its spec passes in jsdom. Why be suspicious?jsdom performs no layout, so every measured rectangle comes back with zero sizes and positions. A positioning assertion can pass because both expected and actual values collapse to zero, not because the logic is right. Run that spec in a real browser via the `browsers` option, or assert the logic with explicitly faked measurements.
- Which runner can use the emulated DOM?Only Vitest. The Karma runner always launches browsers, so choosing Karma means every spec pays browser start-up. The `headless` option also applies only where browsers are configured, and the Karma runner does not support it.
jsdom is a flight simulator: excellent for checking procedures quickly and cheaply, but it cannot tell you how the real aircraft handles crosswind. You still fly the real plane for the few checks that depend on physics.
saying these in an interview costs you the question
- jsdom computes layout, so element sizes are realistic
- Vitest browser mode works without installing a browser provider
- Setting browsers changes how TestBed and fixtures work
- Moving every spec to a real browser is the fix for flaky tests
- The builder silently runs without a DOM if jsdom is missing