or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mddom-apis.mdindex.mdjsdom-class.mdresources.mdtroubleshooting.mdvirtual-console.md
tile.json

tessl/npm-jsdom

A JavaScript implementation of many web standards including the WHATWG DOM and HTML specifications for use with Node.js

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/jsdom@27.0.x

To install, run

npx @tessl/cli install tessl/npm-jsdom@27.0.0

index.mddocs/

jsdom

jsdom is a pure-JavaScript implementation of many web standards, notably the WHATWG DOM and HTML Standards, for use with Node.js. It provides a complete browser-like environment for testing and scraping web applications without requiring an actual browser.

Quick Start

Best Practice: Always wrap jsdom usage in try-finally to ensure cleanup, even on errors.

Parse HTML and Extract Data

const { JSDOM } = require("jsdom");

const html = '<div class="item"><h2>Title</h2><p>Description</p></div>';
const dom = new JSDOM(html);

try {
  const { document } = dom.window;

  const title = document.querySelector("h2")?.textContent || "";
  const description = document.querySelector("p")?.textContent || "";

  console.log({ title, description }); // { title: 'Title', description: 'Description' }
} finally {
  dom.window.close(); // Always cleanup
}

Setup for Testing

const { JSDOM } = require("jsdom");

function setupDOM(html) {
  const dom = new JSDOM(html || '<!DOCTYPE html><body></body>', {
    url: "https://example.org/",
    referrer: "https://google.com/",
    includeNodeLocations: true
  });
  global.window = dom.window;
  global.document = dom.window.document;
  global.navigator = dom.window.navigator;
  return dom;
}

function teardownDOM(dom) {
  if (dom && dom.window) dom.window.close();
  delete global.window;
  delete global.document;
  delete global.navigator;
}

const dom = setupDOM('<div id="test">Hello</div>');
try {
  const element = document.getElementById("test");
  console.log(element.textContent); // "Hello"
} finally {
  teardownDOM(dom);
}

Scrape a Web Page (with retry)

const { JSDOM } = require("jsdom");

async function scrapePage(url, maxRetries = 3) {
  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    let dom = null;
    try {
      dom = await JSDOM.fromURL(url, {
        url, // Important: set URL for resource loading
        resources: "usable",
        runScripts: "dangerously",
        pretendToBeVisual: true
      });

      const { document } = dom.window;
      const result = {
        title: document.title,
        links: Array.from(document.querySelectorAll("a[href]")).map(a => a.href),
        html: dom.serialize()
      };

      dom.window.close(); // Cleanup on success
      return result;
    } catch (error) {
      lastError = error;
      if (dom) dom.window.close(); // Cleanup on error

      if (attempt < maxRetries) {
        await new Promise(r => setTimeout(r, 1000 * attempt));
      }
    }
  }

  throw new Error(`Failed to scrape ${url} after ${maxRetries} attempts: ${lastError?.message}`);
}

scrapePage("https://example.com")
  .then(data => console.log(data))
  .catch(error => console.error(error));

Quick Reference

TaskCode
Create DOMconst dom = new JSDOM('<p>Hello</p>');
Access documentconst { document } = dom.window;
Parse fragmentconst frag = JSDOM.fragment('<li>Item</li>');
Load from URLconst dom = await JSDOM.fromURL(url);
Load from fileconst dom = await JSDOM.fromFile('page.html');
Serializedom.serialize()
Cleanupdom.window.close();

Decision Tree: Which Option to Use?

Need to parse HTML?
├─ Simple extraction? → JSDOM.fragment() (fastest)
├─ Need full document? → new JSDOM(html)
└─ Need external resources? → new JSDOM(html, { resources: "usable" })

Need to execute scripts?
├─ Trusted content? → { runScripts: "dangerously" }
└─ Untrusted content? → { runScripts: "outside-only" }

Need to test code?
├─ Setup before each test → setupDOM()
├─ Global access needed → global.window = dom.window
└─ Always cleanup → dom.window.close() in finally

Need to scrape?
├─ Single page → JSDOM.fromURL()
├─ Multiple pages → Shared CookieJar
└─ Need resilience → Retry logic with cleanup

Common Patterns

Execute Scripts Safely

const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
  url: "https://example.org/",
  runScripts: "outside-only"
});

try {
  dom.window.eval(`document.body.innerHTML = '<p>Added</p>';`);
  console.log(dom.window.document.body.innerHTML);
} catch (error) {
  console.error("Script execution failed:", error);
} finally {
  dom.window.close();
}

Capture Console Output

const { JSDOM, VirtualConsole } = require("jsdom");

const virtualConsole = new VirtualConsole();
virtualConsole.on("log", (msg) => console.log("Page:", msg));
virtualConsole.on("error", (msg) => console.error("Page error:", msg));
virtualConsole.on("jsdomError", (err) => console.error("jsdom error:", err.message));

const dom = new JSDOM(`<script>console.log("Hello");</script>`, {
  runScripts: "dangerously",
  virtualConsole
});

Manage Cookies

const { JSDOM, CookieJar } = require("jsdom");

const cookieJar = new CookieJar();

const dom1 = new JSDOM(``, { url: "https://example.com/", cookieJar });
dom1.window.document.cookie = "session=abc123; path=/";

const dom2 = new JSDOM(``, { url: "https://example.com/", cookieJar });
console.log(dom2.window.document.cookie); // "session=abc123"

Integration: Jest Testing

const { JSDOM } = require("jsdom");

let dom;

beforeEach(() => {
  dom = new JSDOM(`<!DOCTYPE html><body id="root"></body>`, {
    url: "https://example.org/",
    includeNodeLocations: true
  });

  global.window = dom.window;
  global.document = dom.window.document;
  global.navigator = dom.window.navigator;
});

afterEach(() => {
  if (dom) dom.window.close();
  delete global.window;
  delete global.document;
  delete global.navigator;
});

test("should manipulate DOM", () => {
  const root = document.getElementById("root");
  root.innerHTML = "<p>Test</p>";

  expect(root.querySelector("p").textContent).toBe("Test");
});

Integration: Mocha/Chai

const { JSDOM } = require("jsdom");
const { expect } = require("chai");

describe("DOM Operations", () => {
  let dom;

  beforeEach(() => {
    dom = new JSDOM(`<div id="app"></div>`, {
      url: "https://example.org/"
    });
    global.window = dom.window;
    global.document = dom.window.document;
  });

  afterEach(() => {
    if (dom) dom.window.close();
    delete global.window;
    delete global.document;
  });

  it("should find elements", () => {
    const app = document.getElementById("app");
    expect(app).to.not.be.null;
  });
});

Integration: Vitest

import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { JSDOM } from "jsdom";

describe("jsdom integration", () => {
  let dom;

  beforeEach(() => {
    dom = new JSDOM(`<!DOCTYPE html><body><h1>Test</h1></body>`, {
      url: "https://example.org/"
    });
    global.window = dom.window;
    global.document = dom.window.document;
  });

  afterEach(() => {
    dom?.window.close();
    delete global.window;
    delete global.document;
  });

  it("should parse HTML", () => {
    const h1 = document.querySelector("h1");
    expect(h1.textContent).toBe("Test");
  });
});

Core APIs

/**
 * Create a new jsdom instance
 * @param input - HTML string or binary data (optional)
 * @param options - Configuration options
 */
class JSDOM {
  constructor(input?: string | Buffer | ArrayBuffer | TypedArray, options?: JSDOMOptions);
  readonly window: Window;
  readonly virtualConsole: VirtualConsole;
  readonly cookieJar: CookieJar;
  serialize(): string;
  nodeLocation(node: Node): LocationInfo | undefined;
  getInternalVMContext(): object;
  reconfigure(settings: { windowTop?: any, url?: string }): void;

  static fromURL(url: string, options?: JSDOMOptions): Promise<JSDOM>;
  static fromFile(filename: string, options?: JSDOMOptions): Promise<JSDOM>;
  static fragment(string?: string): DocumentFragment;
}

interface JSDOMOptions {
  url?: string;                    // Document URL (default: "about:blank")
  referrer?: string;                // Document referrer
  contentType?: string;             // Content type (default: "text/html")
  includeNodeLocations?: boolean;   // Track source locations
  storageQuota?: number;            // localStorage quota (default: 5000000)
  runScripts?: "dangerously" | "outside-only";  // Script execution mode
  resources?: "usable" | ResourceLoader;  // Resource loading
  pretendToBeVisual?: boolean;      // Enable animation APIs
  virtualConsole?: VirtualConsole;  // Console output handler
  cookieJar?: CookieJar;            // Cookie storage
  beforeParse?: (window: Window) => void;  // Pre-parse callback
}

Best Practices

  1. Always close: dom.window.close() in finally block prevents memory leaks
  2. Set proper URL: Enables relative URLs and cookies
  3. Use runScripts: "outside-only": Safe script execution
  4. Handle errors: Wrap in try-catch for production code
  5. Cleanup globals: Remove test globals after tests
  6. Use fragments: For simple parsing without full document overhead
  7. Share CookieJar: For maintaining sessions across requests
  8. Set resources: Only when needed to avoid unnecessary network requests
  9. Enable location tracking: Use includeNodeLocations: true for debugging
  10. Monitor console: Use VirtualConsole to capture and handle page errors

Package Information

  • Package Name: jsdom
  • Package Type: npm
  • Language: JavaScript (CommonJS)
  • Installation: npm install jsdom
  • Node.js Requirement: v20 or newer
  • Repository: https://github.com/jsdom/jsdom

Architecture

jsdom is built around several key components:

  • JSDOM Class: Main interface for creating and managing DOM instances
  • Window Object: Browser-like global environment with complete web APIs
  • Virtual Console: Captures console output and jsdom errors for external handling
  • Resource Loading: Configurable system for fetching external resources (scripts, stylesheets, images)
  • Cookie Management: HTTP cookie storage and handling via CookieJar
  • Script Execution: Sandboxed JavaScript execution with configurable security levels
  • Parser: HTML/XML parsing using parse5 for standards-compliant document creation

Capabilities

JSDOM Constructor

The primary way to create jsdom instances from HTML strings or binary data.

/**
 * Create a new jsdom instance
 * @param input - HTML string or binary data (Buffer, ArrayBuffer, TypedArray)
 * @param options - Configuration options
 * @returns JSDOM instance with window, document, and DOM APIs
 */
class JSDOM {
  constructor(input?: string | Buffer | ArrayBuffer | TypedArray, options?: JSDOMOptions);

  /** The Window object containing the DOM and browser APIs */
  window: Window;

  /** The VirtualConsole instance for this jsdom */
  virtualConsole: VirtualConsole;

  /** The CookieJar instance for this jsdom */
  cookieJar: CookieJar;

  /** Serialize the document to an HTML string including DOCTYPE */
  serialize(): string;

  /** Get source code location of a node (requires includeNodeLocations option) */
  nodeLocation(node: Node): LocationInfo | undefined;

  /** Get Node.js VM context for advanced script execution */
  getInternalVMContext(): object;

  /** Reconfigure the jsdom after creation */
  reconfigure(settings: { windowTop?: any, url?: string }): void;
}

interface JSDOMOptions {
  url?: string;
  referrer?: string;
  contentType?: string;
  includeNodeLocations?: boolean;
  storageQuota?: number;
  runScripts?: "dangerously" | "outside-only";
  resources?: "usable" | ResourceLoader;
  pretendToBeVisual?: boolean;
  virtualConsole?: VirtualConsole;
  cookieJar?: CookieJar;
  beforeParse?: (window: Window) => void;
}

JSDOM Class

Configuration Options

Comprehensive options for customizing jsdom behavior including URL handling, script execution, resource loading, and rendering simulation.

interface JSDOMOptions {
  /** Document URL (default: "about:blank") */
  url?: string;
  /** Document referrer */
  referrer?: string;
  /** Content type for parsing (default: "text/html") */
  contentType?: string;
  /** Enable node location tracking for source mapping */
  includeNodeLocations?: boolean;
  /** Storage quota for localStorage/sessionStorage in code units */
  storageQuota?: number;
  /** Script execution mode */
  runScripts?: "dangerously" | "outside-only";
  /** Resource loading configuration */
  resources?: "usable" | ResourceLoader;
  /** Pretend to be a visual browser */
  pretendToBeVisual?: boolean;
  /** Virtual console for output capture */
  virtualConsole?: VirtualConsole;
  /** Cookie jar for cookie storage */
  cookieJar?: CookieJar;
  /** Callback before HTML parsing */
  beforeParse?: (window: Window) => void;
}

Configuration

Convenience Factory Methods

Static methods on JSDOM for creating instances from URLs or files.

/**
 * Create jsdom by fetching from a URL
 * @param url - URL to fetch (HTTP/HTTPS)
 * @param options - Configuration options (url and contentType not allowed)
 * @returns Promise resolving to JSDOM instance
 */
static fromURL(url: string, options?: JSDOMOptions): Promise<JSDOM>;

/**
 * Create jsdom by reading from a file
 * @param filename - File path relative to current working directory
 * @param options - Configuration options
 * @returns Promise resolving to JSDOM instance
 */
static fromFile(filename: string, options?: JSDOMOptions): Promise<JSDOM>;

/**
 * Create a DocumentFragment from HTML (lightweight, no full document)
 * @param string - HTML string to parse
 * @returns DocumentFragment with parsed content
 */
static fragment(string?: string): DocumentFragment;

JSDOM Class

Virtual Console

EventEmitter-based console for capturing output from within jsdom, including page console calls and jsdom implementation errors.

/**
 * Create a virtual console for capturing output
 */
class VirtualConsole extends EventEmitter {
  constructor();

  /**
   * Forward console output to another console
   * @param anyConsole - Console object to forward to
   * @param options - Control jsdom error forwarding
   * @returns This VirtualConsole for chaining
   */
  forwardTo(
    anyConsole: Console,
    options?: { jsdomErrors?: "none" | string[] }
  ): VirtualConsole;
}

VirtualConsole emits events for all console methods: "log", "warn", "error", "info", "debug", "trace", "dir", "dirxml", "assert", "count", "countReset", "group", "groupCollapsed", "groupEnd", "table", "time", "timeLog", "timeEnd", "clear", and special "jsdomError" event.

Virtual Console

Resource Loading

Configurable system for loading external resources including custom loaders and proxy support.

/**
 * Resource loader for fetching external resources
 */
class ResourceLoader {
  constructor(options?: {
    strictSSL?: boolean;
    proxy?: string;
    userAgent?: string;
  });

  /**
   * Fetch a resource from a URL
   * @param urlString - URL to fetch
   * @param options - Request options
   * @returns Promise for Buffer with abort() method
   */
  fetch(
    urlString: string,
    options?: {
      accept?: string;
      cookieJar?: CookieJar;
      referrer?: string;
      element?: Element;
    }
  ): Promise<Buffer> & { abort(): void };
}

Resources

Cookie Management

HTTP cookie storage and handling compatible with browser cookie behavior.

/**
 * Cookie jar for HTTP cookie storage (extends tough-cookie.CookieJar)
 */
class CookieJar {
  constructor(store?: any, options?: object);

  setCookie(cookieOrString: string | Cookie, currentUrl: string, options?: object, callback?: Function): void;
  setCookieSync(cookieOrString: string | Cookie, currentUrl: string, options?: object): Cookie;
  getCookies(currentUrl: string, options?: object, callback?: Function): void;
  getCookiesSync(currentUrl: string, options?: object): Cookie[];
}

Resources

Window and DOM APIs

Complete browser-like environment with standard web APIs accessible via dom.window.

The Window object provides:

  • DOM APIs: Node, Element, Document, DocumentFragment, and all HTML elements
  • Events: EventTarget, Event classes, addEventListener, dispatchEvent
  • Selection and Traversal: Range, Selection, NodeIterator, TreeWalker
  • Storage: localStorage, sessionStorage (Web Storage API)
  • Timers: setTimeout, setInterval, setImmediate, requestAnimationFrame
  • Network: WebSocket, basic XMLHttpRequest, Headers
  • Parsing: DOMParser, XMLSerializer
  • Files: Blob, File, FileReader, FileList
  • Custom Elements: customElements, CustomElementRegistry
  • Navigation: location, history (limited)
  • Console: console object (connected to VirtualConsole)
  • And many more standard web platform APIs

DOM APIs

Common Usage Patterns

Creating a simple DOM

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`<!DOCTYPE html><body><div id="app"></div></body>`);
const { document } = dom.window;

const app = document.getElementById("app");
app.innerHTML = "<h1>Hello from jsdom!</h1>";

Testing DOM-dependent code

const { JSDOM } = require("jsdom");

// Create a DOM environment
const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
  url: "https://example.org/",
  runScripts: "dangerously"
});

// Make window available globally for tests
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;

// Now DOM-dependent code can run in tests

Executing scripts safely

const { JSDOM } = require("jsdom");

const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
  runScripts: "outside-only"
});

// Execute code in the jsdom context
dom.window.eval(`
  document.body.innerHTML = '<p>Added from script</p>';
`);

console.log(dom.window.document.body.innerHTML); // '<p>Added from script</p>'

Loading a page from a URL

const { JSDOM } = require("jsdom");

JSDOM.fromURL("https://example.com/", {
  resources: "usable",
  runScripts: "dangerously"
}).then(dom => {
  console.log(dom.serialize());
});

Capturing console output

const { JSDOM, VirtualConsole } = require("jsdom");

const virtualConsole = new VirtualConsole();
virtualConsole.on("error", (message) => {
  console.error("Page error:", message);
});

const dom = new JSDOM(`
  <!DOCTYPE html>
  <script>console.log("Hello from page");</script>
`, { runScripts: "dangerously", virtualConsole });

Important Notes

Security Warning

The runScripts: "dangerously" option allows code execution within jsdom. This is not a secure sandbox - scripts can escape and access your Node.js environment if they try hard enough. Only use with trusted content.

Unimplemented Features

jsdom does not support:

  • Navigation: Cannot change the global object via window.location.href = "..."
  • Layout: No visual layout calculation, layout properties return zeros

Workarounds

  • Navigation: Create new JSDOM instances for each page
  • Layout: Use Object.defineProperty() to mock layout values

Encoding Sniffing

jsdom accepts binary data (Buffer, ArrayBuffer, TypedArray) and automatically sniffs encoding using:

  1. UTF-8 or UTF-16 BOM (takes precedence)
  2. charset parameter in contentType option
  3. <meta charset> tag in HTML
  4. Default: "UTF-8" for XML, "windows-1252" for HTML

Closing jsdoms

Timers keep Node.js processes alive. Use window.close() to terminate all timers and event listeners.

dom.window.close(); // Shut down the jsdom

Next Steps

Types

interface LocationInfo {
  startOffset: number;
  endOffset: number;
  startLine: number;
  endLine: number;
  startCol: number;
  endCol: number;
  startTag?: TagLocationInfo;
  endTag?: TagLocationInfo;
  attrs?: Record<string, AttrLocationInfo>;
}

interface TagLocationInfo {
  startOffset: number;
  endOffset: number;
  startLine: number;
  endLine: number;
  startCol: number;
  endCol: number;
  attrs?: Record<string, AttrLocationInfo>;
}

interface AttrLocationInfo {
  startOffset: number;
  endOffset: number;
  startLine: number;
  endLine: number;
  startCol: number;
  endCol: number;
}