jsdom gives Node a document, not a display
A JSDOM instance turns an HTML string into a window and document that ordinary browser-facing JavaScript can inspect. The parser supplies implied HTML structure, resolves URLs against a configured origin, applies cookie rules, and exposes many WHATWG DOM and HTML interfaces. That makes jsdom a natural test dependency for components and libraries whose behavior depends on nodes, events, attributes, or selectors but not on painted pixels. Scrapers also gain a browser-shaped programming model without managing a browser process.
The boundary matters. jsdom does not calculate layout or render a page. Its README names navigation and layout as two major features outside scope, and says many geometry properties return zero. Setting location.href will not replace the window and document as a browser would. The pretendToBeVisual option changes visibility values and enables animation-frame callbacks, but it still does not lay out content. If a passing test depends on element position, viewport paint, or actual navigation, jsdom is the wrong witness.
Script execution is useful only with trusted input
Embedded scripts are disabled by default. Turning on runScripts: "dangerously" lets page code change the document, while resources: "usable" can fetch external scripts, stylesheets, and frames. Those switches make controlled application fixtures much more realistic. They also change the security model. The project warns that its sandbox is not foolproof and determined page code may reach the Node.js environment. Feeding it arbitrary pages under that option can expose the machine running the test or scraper.
Safer jobs can leave embedded scripts off and manipulate the page from the outside. The outside-only mode installs fresh JavaScript globals on the window and permits evaluation in that context. Resource loading is also opt-in and configurable through a dispatcher, interceptors, and user-agent settings. Images need the optional canvas package. These controls are useful because a DOM test and a web crawler should not silently inherit the same network behavior.
What happened when we ran it
Our fresh Node 22 sandbox installed jsdom's repository dependencies in 11 seconds. npm added 254 packages and occupied 75 MB, while the checkout itself contained 162,266 files, roughly 4,194,148 source lines, and used 544.2 MB. The project had no build script or target for our harness to call, so that stage was skipped. npm audit reported 0 known vulnerabilities across the installed tree.
Tests were less tidy. The command ran for 46 seconds and exited with code 1. The log tail reported 576 passing and 2 failing tests, followed by several additional passing-group summaries, but it did not include the failing test names or error messages. That is enough to say the checkout did not pass cleanly in our container. It is not enough to blame Node 22, the container, a missing service, or the project itself.
The repository is unusually large for a package that consumers install with one npm command, which makes contributor setup a different decision from dependency setup. The test scripts initialize web-platform tests through a Git submodule before running several suites. Consumers do not need to reproduce that machinery to create a document, but contributors should budget for a substantial checkout and a test surface much wider than the small public API suggests.
Browser compatibility stops at documented gaps
The README is candid about missing behavior. Asynchronous scripts on uncontrolled pages have no universal "finished" signal, so callers may need application hooks or polling. Navigation emits a not-implemented error instead of opening a new document. Layout getters may return zeros. Open issue 1245 still requests HTMLElement.innerText, while issue 1721 requests URL.createObjectURL() and revokeObjectURL(). Both have remained open for years and were updated in 2026, which is evidence of an active gap rather than a forgotten tracker.
Those gaps affect tool choice more than they affect jsdom's quality. A unit test can often replace layout values or create a fresh JSDOM for each page. A visual regression suite cannot sensibly fake the very rendering behavior it is supposed to verify. Playwright is heavier, but its real browser engines can navigate, paint, take screenshots, and report actual geometry. Happy DOM and LinkeDOM are closer alternatives when the question is which in-process DOM best matches a narrower workload.
Current maintenance is active despite a large queue
The repository was pushed on August 26, 2026, and release v30.0.1 arrived on July 29. That release fixed a getComputedStyle() regression involving calc() and sped up range operations on large documents. The GitHub counter showed 404 open issues and pull requests combined, so it describes a sizable work queue rather than 404 confirmed defects. Recent activity includes changes to CSS grid tracks, media events, script execution, and Blob streams.
One open report describes slower style operations after upgrading to jsdom 27, with maintainers and users still discussing it in August 2026. Treat that report as a reason to measure style-heavy suites before a major upgrade, not as our benchmark. Our lab numbers cover installation and repository tests only. The healthier signal is that code, releases, issues, and pull requests all moved recently, while the README states the hard limits without pretending to be a full browser.
Use it when DOM behavior is the thing under test
jsdom remains the sensible default for Node projects that need parsing and DOM semantics inside a process. Its API is direct, its configuration is unusually well explained, and the security warning around page scripts is hard to miss. Pin a current Node runtime, keep hostile scripts disabled, and separate DOM assertions from browser assertions. If a requirement mentions coordinates, screenshots, page-to-page navigation, or adversarial web content, move that part of the suite to a real browser.

