or run

npx @tessl/cli init
Log in

Version

Files

docs

api

cookie-management.mdjsdom-constructor.mdresource-loading.mdvirtual-console.mdwindow-dom-apis.md
index.md
tile.json

advanced.mddocs/guides/

Advanced Features and Limitations

Advanced configurations, edge cases, and important limitations of jsdom.

Important Limitations

jsdom has some notable limitations compared to real browsers:

No Navigation

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 update

No Layout

Cannot 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); // 100

No Rendering

Not 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");
});

Additional Features

Canvas Support

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 installed

Encoding Sniffing

When providing binary data (Buffer, ArrayBuffer, TypedArray) instead of strings, jsdom automatically sniffs the encoding following browser behavior:

  • Detects UTF-8 and UTF-16 BOMs
  • Scans for <meta charset> tags
  • Uses contentType option's charset parameter
  • Defaults to UTF-8
const { JSDOM } = require("jsdom");

// With binary data
const buffer = Buffer.from('<!DOCTYPE html><p>Hello</p>');
const dom = new JSDOM(buffer); // Auto-detects encoding

Closing a jsdom

Terminate 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 listeners

Advanced Configuration

Using beforeParse Hook

The 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"

Node Location Tracking

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.

Storage Quota

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");

Using VM Context

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);

Working with the Window

Best Practices

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)

Edge Cases

Fragment Limitations

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 URL

fromURL() Behavior

Important 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() Defaults

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+xml

Performance Considerations

Resource Loading

Resource 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 loading

Memory Management

Remember 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