A JavaScript implementation of many web standards including the WHATWG DOM and HTML specifications for use with Node.js
npx @tessl/cli install tessl/npm-jsdom@27.0.0jsdom 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.
Best Practice: Always wrap jsdom usage in try-finally to ensure cleanup, even on errors.
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
}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);
}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));| Task | Code |
|---|---|
| Create DOM | const dom = new JSDOM('<p>Hello</p>'); |
| Access document | const { document } = dom.window; |
| Parse fragment | const frag = JSDOM.fragment('<li>Item</li>'); |
| Load from URL | const dom = await JSDOM.fromURL(url); |
| Load from file | const dom = await JSDOM.fromFile('page.html'); |
| Serialize | dom.serialize() |
| Cleanup | dom.window.close(); |
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 cleanupconst 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();
}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
});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"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");
});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;
});
});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");
});
});/**
* 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
}dom.window.close() in finally block prevents memory leaksrunScripts: "outside-only": Safe script executionincludeNodeLocations: true for debuggingnpm install jsdomjsdom is built around several key components:
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;
}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;
}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;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.
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 };
}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[];
}Complete browser-like environment with standard web APIs accessible via dom.window.
The Window object provides:
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>";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 testsconst { 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>'const { JSDOM } = require("jsdom");
JSDOM.fromURL("https://example.com/", {
resources: "usable",
runScripts: "dangerously"
}).then(dom => {
console.log(dom.serialize());
});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 });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.
jsdom does not support:
window.location.href = "..."Object.defineProperty() to mock layout valuesjsdom accepts binary data (Buffer, ArrayBuffer, TypedArray) and automatically sniffs encoding using:
charset parameter in contentType option<meta charset> tag in HTML"UTF-8" for XML, "windows-1252" for HTMLTimers keep Node.js processes alive. Use window.close() to terminate all timers and event listeners.
dom.window.close(); // Shut down the jsdominterface 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;
}