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.00
# jsdom
1
2
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.
3
4
## Quick Start
5
6
> **Best Practice:** Always wrap jsdom usage in try-finally to ensure cleanup, even on errors.
7
8
### Parse HTML and Extract Data
9
10
```javascript
11
const { JSDOM } = require("jsdom");
12
13
const html = '<div class="item"><h2>Title</h2><p>Description</p></div>';
14
const dom = new JSDOM(html);
15
16
try {
17
const { document } = dom.window;
18
19
const title = document.querySelector("h2")?.textContent || "";
20
const description = document.querySelector("p")?.textContent || "";
21
22
console.log({ title, description }); // { title: 'Title', description: 'Description' }
23
} finally {
24
dom.window.close(); // Always cleanup
25
}
26
```
27
28
### Setup for Testing
29
30
```javascript
31
const { JSDOM } = require("jsdom");
32
33
function setupDOM(html) {
34
const dom = new JSDOM(html || '<!DOCTYPE html><body></body>', {
35
url: "https://example.org/",
36
referrer: "https://google.com/",
37
includeNodeLocations: true
38
});
39
global.window = dom.window;
40
global.document = dom.window.document;
41
global.navigator = dom.window.navigator;
42
return dom;
43
}
44
45
function teardownDOM(dom) {
46
if (dom && dom.window) dom.window.close();
47
delete global.window;
48
delete global.document;
49
delete global.navigator;
50
}
51
52
const dom = setupDOM('<div id="test">Hello</div>');
53
try {
54
const element = document.getElementById("test");
55
console.log(element.textContent); // "Hello"
56
} finally {
57
teardownDOM(dom);
58
}
59
```
60
61
### Scrape a Web Page (with retry)
62
63
```javascript
64
const { JSDOM } = require("jsdom");
65
66
async function scrapePage(url, maxRetries = 3) {
67
let lastError;
68
69
for (let attempt = 1; attempt <= maxRetries; attempt++) {
70
let dom = null;
71
try {
72
dom = await JSDOM.fromURL(url, {
73
url, // Important: set URL for resource loading
74
resources: "usable",
75
runScripts: "dangerously",
76
pretendToBeVisual: true
77
});
78
79
const { document } = dom.window;
80
const result = {
81
title: document.title,
82
links: Array.from(document.querySelectorAll("a[href]")).map(a => a.href),
83
html: dom.serialize()
84
};
85
86
dom.window.close(); // Cleanup on success
87
return result;
88
} catch (error) {
89
lastError = error;
90
if (dom) dom.window.close(); // Cleanup on error
91
92
if (attempt < maxRetries) {
93
await new Promise(r => setTimeout(r, 1000 * attempt));
94
}
95
}
96
}
97
98
throw new Error(`Failed to scrape ${url} after ${maxRetries} attempts: ${lastError?.message}`);
99
}
100
101
scrapePage("https://example.com")
102
.then(data => console.log(data))
103
.catch(error => console.error(error));
104
```
105
106
## Quick Reference
107
108
| Task | Code |
109
|------|------|
110
| Create DOM | `const dom = new JSDOM('<p>Hello</p>');` |
111
| Access document | `const { document } = dom.window;` |
112
| Parse fragment | `const frag = JSDOM.fragment('<li>Item</li>');` |
113
| Load from URL | `const dom = await JSDOM.fromURL(url);` |
114
| Load from file | `const dom = await JSDOM.fromFile('page.html');` |
115
| Serialize | `dom.serialize()` |
116
| Cleanup | `dom.window.close();` |
117
118
## Decision Tree: Which Option to Use?
119
120
```
121
Need to parse HTML?
122
├─ Simple extraction? → JSDOM.fragment() (fastest)
123
├─ Need full document? → new JSDOM(html)
124
└─ Need external resources? → new JSDOM(html, { resources: "usable" })
125
126
Need to execute scripts?
127
├─ Trusted content? → { runScripts: "dangerously" }
128
└─ Untrusted content? → { runScripts: "outside-only" }
129
130
Need to test code?
131
├─ Setup before each test → setupDOM()
132
├─ Global access needed → global.window = dom.window
133
└─ Always cleanup → dom.window.close() in finally
134
135
Need to scrape?
136
├─ Single page → JSDOM.fromURL()
137
├─ Multiple pages → Shared CookieJar
138
└─ Need resilience → Retry logic with cleanup
139
```
140
141
## Common Patterns
142
143
### Execute Scripts Safely
144
145
```javascript
146
const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
147
url: "https://example.org/",
148
runScripts: "outside-only"
149
});
150
151
try {
152
dom.window.eval(`document.body.innerHTML = '<p>Added</p>';`);
153
console.log(dom.window.document.body.innerHTML);
154
} catch (error) {
155
console.error("Script execution failed:", error);
156
} finally {
157
dom.window.close();
158
}
159
```
160
161
### Capture Console Output
162
163
```javascript
164
const { JSDOM, VirtualConsole } = require("jsdom");
165
166
const virtualConsole = new VirtualConsole();
167
virtualConsole.on("log", (msg) => console.log("Page:", msg));
168
virtualConsole.on("error", (msg) => console.error("Page error:", msg));
169
virtualConsole.on("jsdomError", (err) => console.error("jsdom error:", err.message));
170
171
const dom = new JSDOM(`<script>console.log("Hello");</script>`, {
172
runScripts: "dangerously",
173
virtualConsole
174
});
175
```
176
177
### Manage Cookies
178
179
```javascript
180
const { JSDOM, CookieJar } = require("jsdom");
181
182
const cookieJar = new CookieJar();
183
184
const dom1 = new JSDOM(``, { url: "https://example.com/", cookieJar });
185
dom1.window.document.cookie = "session=abc123; path=/";
186
187
const dom2 = new JSDOM(``, { url: "https://example.com/", cookieJar });
188
console.log(dom2.window.document.cookie); // "session=abc123"
189
```
190
191
### Integration: Jest Testing
192
193
```javascript
194
const { JSDOM } = require("jsdom");
195
196
let dom;
197
198
beforeEach(() => {
199
dom = new JSDOM(`<!DOCTYPE html><body id="root"></body>`, {
200
url: "https://example.org/",
201
includeNodeLocations: true
202
});
203
204
global.window = dom.window;
205
global.document = dom.window.document;
206
global.navigator = dom.window.navigator;
207
});
208
209
afterEach(() => {
210
if (dom) dom.window.close();
211
delete global.window;
212
delete global.document;
213
delete global.navigator;
214
});
215
216
test("should manipulate DOM", () => {
217
const root = document.getElementById("root");
218
root.innerHTML = "<p>Test</p>";
219
220
expect(root.querySelector("p").textContent).toBe("Test");
221
});
222
```
223
224
### Integration: Mocha/Chai
225
226
```javascript
227
const { JSDOM } = require("jsdom");
228
const { expect } = require("chai");
229
230
describe("DOM Operations", () => {
231
let dom;
232
233
beforeEach(() => {
234
dom = new JSDOM(`<div id="app"></div>`, {
235
url: "https://example.org/"
236
});
237
global.window = dom.window;
238
global.document = dom.window.document;
239
});
240
241
afterEach(() => {
242
if (dom) dom.window.close();
243
delete global.window;
244
delete global.document;
245
});
246
247
it("should find elements", () => {
248
const app = document.getElementById("app");
249
expect(app).to.not.be.null;
250
});
251
});
252
```
253
254
### Integration: Vitest
255
256
```javascript
257
import { describe, it, expect, beforeEach, afterEach } from "vitest";
258
import { JSDOM } from "jsdom";
259
260
describe("jsdom integration", () => {
261
let dom;
262
263
beforeEach(() => {
264
dom = new JSDOM(`<!DOCTYPE html><body><h1>Test</h1></body>`, {
265
url: "https://example.org/"
266
});
267
global.window = dom.window;
268
global.document = dom.window.document;
269
});
270
271
afterEach(() => {
272
dom?.window.close();
273
delete global.window;
274
delete global.document;
275
});
276
277
it("should parse HTML", () => {
278
const h1 = document.querySelector("h1");
279
expect(h1.textContent).toBe("Test");
280
});
281
});
282
```
283
284
## Core APIs
285
286
```javascript { .api }
287
/**
288
* Create a new jsdom instance
289
* @param input - HTML string or binary data (optional)
290
* @param options - Configuration options
291
*/
292
class JSDOM {
293
constructor(input?: string | Buffer | ArrayBuffer | TypedArray, options?: JSDOMOptions);
294
readonly window: Window;
295
readonly virtualConsole: VirtualConsole;
296
readonly cookieJar: CookieJar;
297
serialize(): string;
298
nodeLocation(node: Node): LocationInfo | undefined;
299
getInternalVMContext(): object;
300
reconfigure(settings: { windowTop?: any, url?: string }): void;
301
302
static fromURL(url: string, options?: JSDOMOptions): Promise<JSDOM>;
303
static fromFile(filename: string, options?: JSDOMOptions): Promise<JSDOM>;
304
static fragment(string?: string): DocumentFragment;
305
}
306
307
interface JSDOMOptions {
308
url?: string; // Document URL (default: "about:blank")
309
referrer?: string; // Document referrer
310
contentType?: string; // Content type (default: "text/html")
311
includeNodeLocations?: boolean; // Track source locations
312
storageQuota?: number; // localStorage quota (default: 5000000)
313
runScripts?: "dangerously" | "outside-only"; // Script execution mode
314
resources?: "usable" | ResourceLoader; // Resource loading
315
pretendToBeVisual?: boolean; // Enable animation APIs
316
virtualConsole?: VirtualConsole; // Console output handler
317
cookieJar?: CookieJar; // Cookie storage
318
beforeParse?: (window: Window) => void; // Pre-parse callback
319
}
320
```
321
322
## Best Practices
323
324
1. **Always close**: `dom.window.close()` in finally block prevents memory leaks
325
2. **Set proper URL**: Enables relative URLs and cookies
326
3. **Use `runScripts: "outside-only"`**: Safe script execution
327
4. **Handle errors**: Wrap in try-catch for production code
328
5. **Cleanup globals**: Remove test globals after tests
329
6. **Use fragments**: For simple parsing without full document overhead
330
7. **Share CookieJar**: For maintaining sessions across requests
331
8. **Set resources**: Only when needed to avoid unnecessary network requests
332
9. **Enable location tracking**: Use `includeNodeLocations: true` for debugging
333
10. **Monitor console**: Use VirtualConsole to capture and handle page errors
334
335
## Package Information
336
337
- **Package Name**: jsdom
338
- **Package Type**: npm
339
- **Language**: JavaScript (CommonJS)
340
- **Installation**: `npm install jsdom`
341
- **Node.js Requirement**: v20 or newer
342
- **Repository**: https://github.com/jsdom/jsdom
343
344
## Architecture
345
346
jsdom is built around several key components:
347
348
- **JSDOM Class**: Main interface for creating and managing DOM instances
349
- **Window Object**: Browser-like global environment with complete web APIs
350
- **Virtual Console**: Captures console output and jsdom errors for external handling
351
- **Resource Loading**: Configurable system for fetching external resources (scripts, stylesheets, images)
352
- **Cookie Management**: HTTP cookie storage and handling via CookieJar
353
- **Script Execution**: Sandboxed JavaScript execution with configurable security levels
354
- **Parser**: HTML/XML parsing using parse5 for standards-compliant document creation
355
356
## Capabilities
357
358
### JSDOM Constructor
359
360
The primary way to create jsdom instances from HTML strings or binary data.
361
362
```javascript { .api }
363
/**
364
* Create a new jsdom instance
365
* @param input - HTML string or binary data (Buffer, ArrayBuffer, TypedArray)
366
* @param options - Configuration options
367
* @returns JSDOM instance with window, document, and DOM APIs
368
*/
369
class JSDOM {
370
constructor(input?: string | Buffer | ArrayBuffer | TypedArray, options?: JSDOMOptions);
371
372
/** The Window object containing the DOM and browser APIs */
373
window: Window;
374
375
/** The VirtualConsole instance for this jsdom */
376
virtualConsole: VirtualConsole;
377
378
/** The CookieJar instance for this jsdom */
379
cookieJar: CookieJar;
380
381
/** Serialize the document to an HTML string including DOCTYPE */
382
serialize(): string;
383
384
/** Get source code location of a node (requires includeNodeLocations option) */
385
nodeLocation(node: Node): LocationInfo | undefined;
386
387
/** Get Node.js VM context for advanced script execution */
388
getInternalVMContext(): object;
389
390
/** Reconfigure the jsdom after creation */
391
reconfigure(settings: { windowTop?: any, url?: string }): void;
392
}
393
394
interface JSDOMOptions {
395
url?: string;
396
referrer?: string;
397
contentType?: string;
398
includeNodeLocations?: boolean;
399
storageQuota?: number;
400
runScripts?: "dangerously" | "outside-only";
401
resources?: "usable" | ResourceLoader;
402
pretendToBeVisual?: boolean;
403
virtualConsole?: VirtualConsole;
404
cookieJar?: CookieJar;
405
beforeParse?: (window: Window) => void;
406
}
407
```
408
409
[JSDOM Class](./jsdom-class.md)
410
411
### Configuration Options
412
413
Comprehensive options for customizing jsdom behavior including URL handling, script execution, resource loading, and rendering simulation.
414
415
```javascript { .api }
416
interface JSDOMOptions {
417
/** Document URL (default: "about:blank") */
418
url?: string;
419
/** Document referrer */
420
referrer?: string;
421
/** Content type for parsing (default: "text/html") */
422
contentType?: string;
423
/** Enable node location tracking for source mapping */
424
includeNodeLocations?: boolean;
425
/** Storage quota for localStorage/sessionStorage in code units */
426
storageQuota?: number;
427
/** Script execution mode */
428
runScripts?: "dangerously" | "outside-only";
429
/** Resource loading configuration */
430
resources?: "usable" | ResourceLoader;
431
/** Pretend to be a visual browser */
432
pretendToBeVisual?: boolean;
433
/** Virtual console for output capture */
434
virtualConsole?: VirtualConsole;
435
/** Cookie jar for cookie storage */
436
cookieJar?: CookieJar;
437
/** Callback before HTML parsing */
438
beforeParse?: (window: Window) => void;
439
}
440
```
441
442
[Configuration](./configuration.md)
443
444
### Convenience Factory Methods
445
446
Static methods on JSDOM for creating instances from URLs or files.
447
448
```javascript { .api }
449
/**
450
* Create jsdom by fetching from a URL
451
* @param url - URL to fetch (HTTP/HTTPS)
452
* @param options - Configuration options (url and contentType not allowed)
453
* @returns Promise resolving to JSDOM instance
454
*/
455
static fromURL(url: string, options?: JSDOMOptions): Promise<JSDOM>;
456
457
/**
458
* Create jsdom by reading from a file
459
* @param filename - File path relative to current working directory
460
* @param options - Configuration options
461
* @returns Promise resolving to JSDOM instance
462
*/
463
static fromFile(filename: string, options?: JSDOMOptions): Promise<JSDOM>;
464
465
/**
466
* Create a DocumentFragment from HTML (lightweight, no full document)
467
* @param string - HTML string to parse
468
* @returns DocumentFragment with parsed content
469
*/
470
static fragment(string?: string): DocumentFragment;
471
```
472
473
[JSDOM Class](./jsdom-class.md)
474
475
### Virtual Console
476
477
EventEmitter-based console for capturing output from within jsdom, including page console calls and jsdom implementation errors.
478
479
```javascript { .api }
480
/**
481
* Create a virtual console for capturing output
482
*/
483
class VirtualConsole extends EventEmitter {
484
constructor();
485
486
/**
487
* Forward console output to another console
488
* @param anyConsole - Console object to forward to
489
* @param options - Control jsdom error forwarding
490
* @returns This VirtualConsole for chaining
491
*/
492
forwardTo(
493
anyConsole: Console,
494
options?: { jsdomErrors?: "none" | string[] }
495
): VirtualConsole;
496
}
497
```
498
499
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.
500
501
[Virtual Console](./virtual-console.md)
502
503
### Resource Loading
504
505
Configurable system for loading external resources including custom loaders and proxy support.
506
507
```javascript { .api }
508
/**
509
* Resource loader for fetching external resources
510
*/
511
class ResourceLoader {
512
constructor(options?: {
513
strictSSL?: boolean;
514
proxy?: string;
515
userAgent?: string;
516
});
517
518
/**
519
* Fetch a resource from a URL
520
* @param urlString - URL to fetch
521
* @param options - Request options
522
* @returns Promise for Buffer with abort() method
523
*/
524
fetch(
525
urlString: string,
526
options?: {
527
accept?: string;
528
cookieJar?: CookieJar;
529
referrer?: string;
530
element?: Element;
531
}
532
): Promise<Buffer> & { abort(): void };
533
}
534
```
535
536
[Resources](./resources.md)
537
538
### Cookie Management
539
540
HTTP cookie storage and handling compatible with browser cookie behavior.
541
542
```javascript { .api }
543
/**
544
* Cookie jar for HTTP cookie storage (extends tough-cookie.CookieJar)
545
*/
546
class CookieJar {
547
constructor(store?: any, options?: object);
548
549
setCookie(cookieOrString: string | Cookie, currentUrl: string, options?: object, callback?: Function): void;
550
setCookieSync(cookieOrString: string | Cookie, currentUrl: string, options?: object): Cookie;
551
getCookies(currentUrl: string, options?: object, callback?: Function): void;
552
getCookiesSync(currentUrl: string, options?: object): Cookie[];
553
}
554
```
555
556
[Resources](./resources.md)
557
558
### Window and DOM APIs
559
560
Complete browser-like environment with standard web APIs accessible via `dom.window`.
561
562
The Window object provides:
563
- **DOM APIs**: Node, Element, Document, DocumentFragment, and all HTML elements
564
- **Events**: EventTarget, Event classes, addEventListener, dispatchEvent
565
- **Selection and Traversal**: Range, Selection, NodeIterator, TreeWalker
566
- **Storage**: localStorage, sessionStorage (Web Storage API)
567
- **Timers**: setTimeout, setInterval, setImmediate, requestAnimationFrame
568
- **Network**: WebSocket, basic XMLHttpRequest, Headers
569
- **Parsing**: DOMParser, XMLSerializer
570
- **Files**: Blob, File, FileReader, FileList
571
- **Custom Elements**: customElements, CustomElementRegistry
572
- **Navigation**: location, history (limited)
573
- **Console**: console object (connected to VirtualConsole)
574
- **And many more standard web platform APIs**
575
576
[DOM APIs](./dom-apis.md)
577
578
## Common Usage Patterns
579
580
### Creating a simple DOM
581
582
```javascript
583
const { JSDOM } = require("jsdom");
584
585
const dom = new JSDOM(`<!DOCTYPE html><body><div id="app"></div></body>`);
586
const { document } = dom.window;
587
588
const app = document.getElementById("app");
589
app.innerHTML = "<h1>Hello from jsdom!</h1>";
590
```
591
592
### Testing DOM-dependent code
593
594
```javascript
595
const { JSDOM } = require("jsdom");
596
597
// Create a DOM environment
598
const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
599
url: "https://example.org/",
600
runScripts: "dangerously"
601
});
602
603
// Make window available globally for tests
604
global.window = dom.window;
605
global.document = dom.window.document;
606
global.navigator = dom.window.navigator;
607
608
// Now DOM-dependent code can run in tests
609
```
610
611
### Executing scripts safely
612
613
```javascript
614
const { JSDOM } = require("jsdom");
615
616
const dom = new JSDOM(`<!DOCTYPE html><body></body>`, {
617
runScripts: "outside-only"
618
});
619
620
// Execute code in the jsdom context
621
dom.window.eval(`
622
document.body.innerHTML = '<p>Added from script</p>';
623
`);
624
625
console.log(dom.window.document.body.innerHTML); // '<p>Added from script</p>'
626
```
627
628
### Loading a page from a URL
629
630
```javascript
631
const { JSDOM } = require("jsdom");
632
633
JSDOM.fromURL("https://example.com/", {
634
resources: "usable",
635
runScripts: "dangerously"
636
}).then(dom => {
637
console.log(dom.serialize());
638
});
639
```
640
641
### Capturing console output
642
643
```javascript
644
const { JSDOM, VirtualConsole } = require("jsdom");
645
646
const virtualConsole = new VirtualConsole();
647
virtualConsole.on("error", (message) => {
648
console.error("Page error:", message);
649
});
650
651
const dom = new JSDOM(`
652
<!DOCTYPE html>
653
<script>console.log("Hello from page");</script>
654
`, { runScripts: "dangerously", virtualConsole });
655
```
656
657
## Important Notes
658
659
### Security Warning
660
661
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**.
662
663
### Unimplemented Features
664
665
jsdom does not support:
666
- **Navigation**: Cannot change the global object via `window.location.href = "..."`
667
- **Layout**: No visual layout calculation, layout properties return zeros
668
669
### Workarounds
670
671
- **Navigation**: Create new JSDOM instances for each page
672
- **Layout**: Use `Object.defineProperty()` to mock layout values
673
674
### Encoding Sniffing
675
676
jsdom accepts binary data (Buffer, ArrayBuffer, TypedArray) and automatically sniffs encoding using:
677
1. UTF-8 or UTF-16 BOM (takes precedence)
678
2. `charset` parameter in `contentType` option
679
3. `<meta charset>` tag in HTML
680
4. Default: `"UTF-8"` for XML, `"windows-1252"` for HTML
681
682
### Closing jsdoms
683
684
Timers keep Node.js processes alive. Use `window.close()` to terminate all timers and event listeners.
685
686
```javascript
687
dom.window.close(); // Shut down the jsdom
688
```
689
690
## Next Steps
691
692
- [Configuration](./configuration.md) - Detailed configuration options
693
- [DOM APIs](./dom-apis.md) - Available browser APIs
694
- [JSDOM Class](./jsdom-class.md) - JSDOM methods and properties
695
- [Resources](./resources.md) - Resource loading and cookies
696
- [Virtual Console](./virtual-console.md) - Console output handling
697
- [Troubleshooting](./troubleshooting.md) - Common issues and solutions
698
699
## Types
700
701
```javascript { .api }
702
interface LocationInfo {
703
startOffset: number;
704
endOffset: number;
705
startLine: number;
706
endLine: number;
707
startCol: number;
708
endCol: number;
709
startTag?: TagLocationInfo;
710
endTag?: TagLocationInfo;
711
attrs?: Record<string, AttrLocationInfo>;
712
}
713
714
interface TagLocationInfo {
715
startOffset: number;
716
endOffset: number;
717
startLine: number;
718
endLine: number;
719
startCol: number;
720
endCol: number;
721
attrs?: Record<string, AttrLocationInfo>;
722
}
723
724
interface AttrLocationInfo {
725
startOffset: number;
726
endOffset: number;
727
startLine: number;
728
endLine: number;
729
startCol: number;
730
endCol: number;
731
}
732
```
733