Short answer
In Jest, set testEnvironment to jsdom (and install jest-environment-jsdom, which Jest 28 and later no longer bundle); in Vitest, set environment to jsdom or happy-dom. In a Node.js script that needs to read HTML, use a parser such as jsdom or cheerio instead of document. In Next.js, Nuxt or SvelteKit, move the DOM code into useEffect, onMounted or onMount so it only runs in the browser.
Where the code runs, and the fix
01
Jest tests running in the Node.js environment
“The default environment in Jest is a Node.js environment. If you are building a web app, you can use a browser-like environment through jsdom instead.” Jest, Configuration: testEnvironment
Tests for components that touch the DOM need a simulated browser. Set it for the whole project, or per file with a docblock at the top.
npm install -D jest-environment-jsdom // jest.config.js module.exports = { testEnvironment: 'jsdom' }; /** * @jest-environment jsdom */02
Vitest tests in the default environment
Vitest also defaults to Node.js. Install jsdom or happy-dom and set the environment in the config, or per file with a comment.
// vitest.config.ts import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'jsdom' } }); // or at the top of one test file // @vitest-environment jsdom03
A Node.js script that parses HTML
Node.js has no page and no document. To read or change HTML in a script, load it into a parser.
import { JSDOM } from 'jsdom'; const { document } = new JSDOM(html).window; const title = document.querySelector('h1')?.textContent;04
Server rendering in Next.js, Nuxt or SvelteKit
Components render on the server before the browser. Read document inside useEffect (React), onMounted (Vue) or onMount (Svelte), or load browser-only components on the client only.
useEffect(() => { document.title = `${count} new messages`; }, [count]);
Checked against the vendor’s docs on : Jest: Configuration (testEnvironment).
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
Related: Next.js “window is not defined” · JavaScript “x is not defined” · “self is not defined”