A JavaScript implementation of many web standards
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Advanced configurations, edge cases, and important limitations of jsdom.
jsdom has some notable limitations compared to real browsers:
Cannot change the global object by clicking links or setting location.href. Use reconfigure() to change URL or window.top, or create new JSDOM instances.
const { JSDOM } = require("jsdom");
const dom = new JSDOM();
console.log(dom.window.location.href); // "about:blank"
// Change the URL (doesn't navigate, just updates)
dom.reconfigure({ url: "https://example.com/" });
console.log(dom.window.location.href); // "https://example.com/"
// The DOM content remains unchanged
// No navigation occurs, just URL updateCannot calculate visual positioning. Methods like getBoundingClientRect() and properties like offsetTop return zeros. Use Object.defineProperty() to mock these if needed.
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body><div id="box"></div></body>`);
const { document } = dom.window;
const box = document.getElementById("box");
// These return zeros
console.log(box.getBoundingClientRect()); // { x: 0, y: 0, width: 0, height: 0 }
console.log(box.offsetTop); // 0
console.log(box.offsetLeft); // 0
// Mock if needed
Object.defineProperty(box, "offsetTop", { value: 100 });
console.log(box.offsetTop); // 100Not a visual browser. Use pretendToBeVisual: true option to make jsdom pretend it's rendering (affects document.hidden, enables requestAnimationFrame).
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
pretendToBeVisual: true
});
console.log(dom.window.document.hidden); // false
console.log(dom.window.document.visibilityState); // "visible"
// Enables requestAnimationFrame
dom.window.requestAnimationFrame(() => {
console.log("Animation frame");
});jsdom supports the HTML5 Canvas API when the optional canvas npm package (v3.x) is installed as a peer dependency. Without it, <canvas> elements behave like <div> elements.
// With canvas package installed
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body><canvas id="myCanvas"></canvas></body>`);
const { document } = dom.window;
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
ctx.fillRect(0, 0, 100, 100); // Works if canvas package is installedWhen providing binary data (Buffer, ArrayBuffer, TypedArray) instead of strings, jsdom automatically sniffs the encoding following browser behavior:
<meta charset> tagscontentType option's charset parameterconst { JSDOM } = require("jsdom");
// With binary data
const buffer = Buffer.from('<!DOCTYPE html><p>Hello</p>');
const dom = new JSDOM(buffer); // Auto-detects encodingTerminate all timers and remove event listeners:
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
// Clean up when done
dom.window.close(); // Terminates timers, removes event listenersThe beforeParse hook is called before HTML parsing begins, useful for adding shims or modifying the environment:
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
beforeParse(window) {
// Add custom APIs before parsing
window.myCustomAPI = () => console.log("Custom API called");
// Inject libraries
window.jQuery = require("jquery");
}
});
// Custom API is available
dom.window.myCustomAPI(); // "Custom API called"Enable location tracking for debugging and source mapping:
const { JSDOM } = require("jsdom");
const dom = new JSDOM(
`<p>Hello
<img src="foo.jpg">
</p>`,
{ includeNodeLocations: true }
);
const pEl = dom.window.document.querySelector("p");
const location = dom.nodeLocation(pEl);
console.log(location);
// {
// startOffset: 0,
// endOffset: 39,
// startLine: 1,
// startCol: 1,
// endLine: 3,
// endCol: 4,
// startTag: {...},
// endTag: {...}
// }Note: Cannot be used with XML content type.
Configure maximum size for localStorage and sessionStorage:
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><html></html>`, {
url: "https://example.org/",
storageQuota: 10000000 // 10 million code units (default: 5 million)
});
const { window } = dom;
window.localStorage.setItem("key", "value");Access the internal VM context for advanced script execution:
const { Script } = require("vm");
const { JSDOM } = require("jsdom");
const dom = new JSDOM(``, { runScripts: "outside-only" });
// Pre-compile a script
const script = new Script(`
if (!this.ran) {
this.ran = 0;
}
++this.ran;
`);
const vmContext = dom.getInternalVMContext();
// Run the pre-compiled script multiple times
script.runInContext(vmContext);
script.runInContext(vmContext);
script.runInContext(vmContext);
console.assert(dom.window.ran === 3);const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
// GOOD: Access window directly
const { window } = dom;
const div = window.document.createElement("div");
// AVOID: Don't merge with Node.js global
// global.window = dom.window; // Don't do this!
// GOOD: Run code inside jsdom context
dom.window.eval('console.log(document.title);');
// GOOD: Use window constructors for type checks
console.log(div instanceof window.HTMLDivElement); // true
console.log(div instanceof HTMLDivElement); // false (different global)DocumentFragments created with JSDOM.fragment() have limitations:
const { JSDOM } = require("jsdom");
const frag = JSDOM.fragment(`<p>Hello</p>`);
// Note: The fragment's ownerDocument has no browsing context
// - ownerDocument.defaultView is null
// - Resources won't load
// - No associated URLImportant notes about fromURL():
const { JSDOM } = require("jsdom");
JSDOM.fromURL("https://example.com/", {
referrer: "https://google.com/"
}).then(dom => {
// Follows redirects automatically
// Cannot specify `url` or `contentType` options (determined from response)
// `referrer` option becomes the HTTP Referer request header
// Cookies from Set-Cookie headers are stored in the cookie jar
// Cookies in the jar are sent as Cookie request headers
});fromFile() has special default behaviors:
const { JSDOM } = require("jsdom");
// `url` defaults to a file URL (e.g., `file:///path/to/test.html`)
// `contentType` defaults to `"application/xhtml+xml"` for `.xht`, `.xhtml`, or `.xml` extensions
// `contentType` defaults to `"text/html"` for all other extensions
// Binary encoding is sniffed from file contents (BOMs, meta charset tags)
await JSDOM.fromFile("./page.html"); // Parsed as text/html
await JSDOM.fromFile("./page.xhtml"); // Parsed as application/xhtml+xmlResource loading is asynchronous. Scripts and stylesheets may not be loaded immediately:
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`
<html>
<head>
<script src="https://example.com/slow-loading.js"></script>
</head>
<body>
<div id="content"></div>
</body>
</html>
`, {
url: "https://example.org/",
resources: "usable",
runScripts: "dangerously"
});
// The page structure is available immediately
console.log(dom.window.document.getElementById("content")); // <div>
// But external scripts may still be loading
// There's no built-in way to know when all resources finish loadingRemember to close jsdom instances when done:
const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
// Use the dom...
// Clean up
dom.window.close(); // Important for long-running processes