or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

troubleshooting.mddocs/

0

# Troubleshooting

1

2

Common issues and solutions when using jsdom.

3

4

## Common Issues

5

6

### Process Won't Exit After Using jsdom

7

8

**Symptoms:** Node.js process hangs after running code with jsdom.

9

10

**Cause:** Timers (setTimeout, setInterval, requestAnimationFrame) keep the event loop alive.

11

12

**Solution:**

13

14

```javascript

15

const dom = new JSDOM(html);

16

17

try {

18

// Your work here

19

const { document } = dom.window;

20

// ...

21

} finally {

22

// Always call close()

23

dom.window.close();

24

}

25

```

26

27

### Relative URLs Fail to Resolve

28

29

**Symptoms:** `<img src="logo.png">` shows as relative URL or fetch fails.

30

31

**Cause:** Default URL is `"about:blank"` which can't resolve relative paths.

32

33

**Solution:**

34

35

```javascript

36

const dom = new JSDOM(html, {

37

url: "https://example.com/page.html" // Provide absolute URL

38

});

39

40

const img = dom.window.document.querySelector("img");

41

console.log(img.src); // "https://example.com/logo.png" (resolved)

42

```

43

44

### Scripts Don't Execute

45

46

**Symptoms:** Inline scripts in HTML don't run, event handlers don't work.

47

48

**Cause:** Default `runScripts` is `undefined`, so no scripts execute.

49

50

**Solution:**

51

52

```javascript

53

// For inline scripts

54

const dom = new JSDOM('<script>/* script */</script>', {

55

runScripts: "dangerously" // Allow inline execution

56

});

57

58

// Or run scripts from outside (safer)

59

const dom2 = new JSDOM(html, { runScripts: "outside-only" });

60

dom2.window.eval("/* your code */");

61

```

62

63

### Resources Don't Load

64

65

**Symptoms:** External stylesheets, scripts, images don't load.

66

67

**Cause:** `resources` option not set (default is no resource loading).

68

69

**Solution:**

70

71

```javascript

72

const dom = new JSDOM(`

73

<link rel="stylesheet" href="styles.css">

74

<script src="app.js"></script>

75

`, {

76

url: "https://example.com/", // Required!

77

resources: "usable" // Enable resource loading

78

});

79

```

80

81

### Layout Properties Return Zero

82

83

**Symptoms:** `offsetWidth`, `getBoundingClientRect()`, etc. return 0.

84

85

**Cause:** jsdom doesn't implement layout calculation.

86

87

**Solution:** Mock the values you need:

88

89

```javascript

90

const element = dom.window.document.querySelector(".box");

91

92

// Mock layout properties

93

Object.defineProperty(element, "offsetWidth", {

94

value: 100,

95

writable: true,

96

configurable: true

97

});

98

99

Object.defineProperty(element, "offsetHeight", {

100

value: 200,

101

writable: true,

102

configurable: true

103

});

104

105

// Now they return your mocked values

106

console.log(element.offsetWidth); // 100

107

console.log(element.offsetHeight); // 200

108

109

// Or create a helper

110

function mockLayout(element, width, height) {

111

Object.defineProperties(element, {

112

offsetWidth: { value: width, writable: true, configurable: true },

113

offsetHeight: { value: height, writable: true, configurable: true },

114

clientWidth: { value: width, writable: true, configurable: true },

115

clientHeight: { value: height, writable: true, configurable: true }

116

});

117

}

118

```

119

120

### Navigation Doesn't Work

121

122

**Symptoms:** Setting `window.location.href` does nothing.

123

124

**Cause:** jsdom doesn't implement navigation.

125

126

**Solution:** Create new JSDOM instances for different pages:

127

128

```javascript

129

// Instead of:

130

// window.location.href = "https://example.com/page2"; // Won't work

131

132

// Do this:

133

async function navigateTo(url) {

134

const newDom = await JSDOM.fromURL(url);

135

return newDom;

136

}

137

138

const page1 = await JSDOM.fromURL("https://example.com/");

139

const page2 = await navigateTo("https://example.com/page2");

140

```

141

142

### Cookies Not Persisting Across Requests

143

144

**Symptoms:** Cookies set in one jsdom aren't available in another.

145

146

**Cause:** Using separate CookieJar instances.

147

148

**Solution:** Share a CookieJar:

149

150

```javascript

151

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

152

153

const cookieJar = new CookieJar(); // Shared across all instances

154

155

const dom1 = new JSDOM(``, {

156

url: "https://example.com/",

157

cookieJar: cookieJar

158

});

159

dom1.window.document.cookie = "session=abc123";

160

161

const dom2 = new JSDOM(``, {

162

url: "https://example.com/",

163

cookieJar: cookieJar // Same jar

164

});

165

166

console.log(dom2.window.document.cookie); // "session=abc123"

167

```

168

169

### "Not Implemented" Errors

170

171

**Symptoms:** Calling certain web APIs throws "not-implemented" errors.

172

173

**Cause:** Some web platform features aren't implemented in jsdom.

174

175

**Solution:** Use try-catch and provide fallbacks:

176

177

```javascript

178

function safeCall(dom, feature, callback) {

179

try {

180

return callback(dom);

181

} catch (error) {

182

if (error.message.includes("not implemented")) {

183

console.warn(`Feature ${feature} not available in jsdom`);

184

return null;

185

}

186

throw error;

187

}

188

}

189

190

const dom = new JSDOM(html);

191

const result = safeCall(dom, "someFeature", (d) => {

192

return d.window.SomeFeature.doSomething();

193

});

194

```

195

196

### Memory Leaks in Long-Running Scripts

197

198

**Symptoms:** Memory usage grows over time when processing many pages.

199

200

**Cause:** Not cleaning up jsdom instances properly.

201

202

**Solution:** Use proper cleanup and consider limits:

203

204

```javascript

205

async function processPages(urls) {

206

for (const url of urls) {

207

let dom = null;

208

try {

209

dom = await JSDOM.fromURL(url);

210

// Process page

211

const data = extractData(dom.window.document);

212

213

// Return data immediately

214

yield data;

215

} finally {

216

// Always cleanup

217

if (dom) {

218

dom.window.close();

219

dom = null;

220

}

221

}

222

223

// Optional: Force garbage collection hint

224

if (global.gc) global.gc();

225

}

226

}

227

228

// Usage

229

for await (const data of processPages(urls)) {

230

console.log(data);

231

}

232

```

233

234

### Text Encoding Issues

235

236

**Symptoms:** Special characters appear as wrong symbols.

237

238

**Cause:** Encoding not specified or detected incorrectly.

239

240

**Solution:** Specify encoding explicitly:

241

242

```javascript

243

// From string (default is UTF-8)

244

const dom1 = new JSDOM('<p>Test: 测试</p>');

245

246

// From Buffer with explicit content type

247

const buffer = fs.readFileSync('page.html');

248

const dom2 = new JSDOM(buffer, {

249

contentType: "text/html; charset=utf-8"

250

});

251

```

252

253

### Attributes with Special Characters

254

255

**Symptoms:** Attributes with colons or special names don't work.

256

257

**Cause:** Some attribute names are namespaced or special.

258

259

**Solution:** Use proper attribute access:

260

261

```javascript

262

const element = document.createElement("div");

263

264

// For standard attributes

265

element.setAttribute("data-value", "123");

266

267

// For attributes with colons or namespaces

268

element.setAttributeNS(

269

"http://example.com/xmlns",

270

"example:attribute",

271

"value"

272

);

273

```

274

275

### Fragment Context Lost

276

277

**Symptoms:** Using `JSDOM.fragment()` then appending doesn't preserve context.

278

279

**Cause:** Fragments have no browsing context.

280

281

**Solution:** Use in proper DOM tree:

282

283

```javascript

284

const frag = JSDOM.fragment('<li>Item</li><li>Item</li>');

285

286

const dom = new JSDOM('<ul id="list"></ul>');

287

const list = dom.window.document.getElementById("list");

288

289

// Append fragment - proper context now

290

list.appendChild(frag);

291

292

console.log(dom.serialize()); // Includes both items

293

```

294

295

## Debugging Tips

296

297

### Enable Node Location Tracking

298

299

```javascript

300

const dom = new JSDOM(html, {

301

includeNodeLocations: true // Enable source mapping

302

});

303

304

const element = dom.window.document.querySelector("p");

305

const location = dom.nodeLocation(element);

306

307

console.log(`Found at line ${location.startLine}, col ${location.startCol}`);

308

```

309

310

### Structured Error Handling

311

312

```javascript

313

async function safeScrape(url, options = {}) {

314

let dom = null;

315

316

try {

317

const fetchOptions = {

318

url,

319

...options

320

};

321

322

dom = await JSDOM.fromURL(url, fetchOptions);

323

324

// Validate response

325

if (!dom.window.document.body) {

326

throw new Error("No body element found in document");

327

}

328

329

return {

330

success: true,

331

dom,

332

error: null

333

};

334

} catch (error) {

335

return {

336

success: false,

337

dom: null,

338

error: {

339

message: error.message,

340

type: error.name,

341

stack: error.stack

342

}

343

};

344

} finally {

345

if (dom) {

346

dom.window.close();

347

}

348

}

349

}

350

351

// Usage

352

const result = await safeScrape("https://example.com");

353

if (result.success) {

354

const { document } = result.dom.window;

355

console.log(document.title);

356

}

357

```

358

359

### Capture jsdom Errors

360

361

```javascript

362

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

363

364

const virtualConsole = new VirtualConsole();

365

366

virtualConsole.on("jsdomError", (error) => {

367

console.error(`jsdom error [${error.type}]:`, error.message);

368

369

// Inspect error details

370

console.log("Error details:", error);

371

});

372

373

const dom = new JSDOM(html, { virtualConsole });

374

```

375

376

### Log All Console Output

377

378

```javascript

379

const virtualConsole = new VirtualConsole();

380

381

virtualConsole.on("all", (...args) => {

382

console.log("[jsdom]", ...args);

383

});

384

385

virtualConsole.on("log", (...args) => console.log("[log]", ...args));

386

virtualConsole.on("error", (...args) => console.error("[error]", ...args));

387

virtualConsole.on("warn", (...args) => console.warn("[warn]", ...args));

388

389

const dom = new JSDOM(`

390

<script>

391

console.log("Info");

392

console.error("Error");

393

console.warn("Warning");

394

</script>

395

`, {

396

runScripts: "dangerously",

397

virtualConsole

398

});

399

```

400

401

### Inspect DOM State

402

403

```javascript

404

function inspectDOM(dom) {

405

const { document } = dom.window;

406

407

console.log("Document URL:", document.URL);

408

console.log("Document title:", document.title);

409

console.log("Element count:", document.querySelectorAll("*").length);

410

console.log("Cookies:", dom.window.document.cookie);

411

console.log("LocalStorage:", Object.keys(dom.window.localStorage));

412

console.log("Location:", dom.window.location.href);

413

}

414

415

const dom = new JSDOM(html);

416

inspectDOM(dom);

417

```

418

419

### Verify Resource Loading

420

421

```javascript

422

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

423

424

class LoggingResourceLoader extends ResourceLoader {

425

fetch(url, options) {

426

console.log("Fetching:", url);

427

console.log("Options:", options);

428

429

const promise = super.fetch(url, options);

430

431

promise.then(() => {

432

console.log("Success:", url);

433

}).catch((error) => {

434

console.error("Failed:", url, error.message);

435

});

436

437

return promise;

438

}

439

}

440

441

const dom = new JSDOM(`

442

<link rel="stylesheet" href="styles.css">

443

<script src="script.js"></script>

444

`, {

445

url: "https://example.com/",

446

resources: new LoggingResourceLoader()

447

});

448

```

449

450

## Performance Optimization

451

452

### 1. Use JSDOM.fragment() for Simple Parsing

453

454

```javascript

455

// More efficient for simple parsing

456

const frag = JSDOM.fragment('<p>Test</p>');

457

const text = frag.querySelector("p").textContent;

458

```

459

460

### 2. Disable Resource Loading When Not Needed

461

462

```javascript

463

// Faster if you don't need external resources

464

const dom = new JSDOM(html); // Default: no resource loading

465

```

466

467

### 3. Close Unused jsdoms Promptly

468

469

```javascript

470

function processAndClose(html) {

471

const dom = new JSDOM(html);

472

try {

473

const data = extract(dom.window.document);

474

return data;

475

} finally {

476

dom.window.close(); // Free memory immediately

477

}

478

}

479

```

480

481

### 4. Reuse CookieJar for Multiple Pages

482

483

```javascript

484

const cookieJar = new CookieJar();

485

486

// More efficient than creating new jar for each page

487

for (const url of urls) {

488

const dom = new JSDOM(``, { url, cookieJar });

489

// Use and close

490

dom.window.close();

491

}

492

```

493

494

## Performance Monitoring

495

496

### Track Memory Usage

497

498

```javascript

499

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

500

501

function createDOMWithMemoryTracking(html) {

502

const memBefore = process.memoryUsage();

503

504

const dom = new JSDOM(html);

505

const { document } = dom.window;

506

507

// Do work

508

const elements = document.querySelectorAll("*");

509

510

// Track memory after

511

const memAfter = process.memoryUsage();

512

513

const heapUsedMB = (memAfter.heapUsed - memBefore.heapUsed) / 1024 / 1024;

514

console.log(`Memory used: ${heapUsedMB.toFixed(2)}MB`);

515

console.log(`Elements processed: ${elements.length}`);

516

517

return dom;

518

}

519

520

let dom = createDOMWithMemoryTracking('<div>' + '.'.repeat(100000) + '</div>');

521

// Always cleanup

522

dom.window.close();

523

```

524

525

### Batch Processing with Limits

526

527

```javascript

528

async function processPagesWithLimit(urls, options = {}) {

529

const {

530

maxConcurrent = 3,

531

delayBetweenPages = 100,

532

memoryLimitMB = 500

533

} = options;

534

535

const results = [];

536

let currentMemory = process.memoryUsage().heapUsed / 1024 / 1024;

537

538

for (let i = 0; i < urls.length; i += maxConcurrent) {

539

const batch = urls.slice(i, i + maxConcurrent);

540

541

const batchResults = await Promise.all(

542

batch.map(async (url) => {

543

if (currentMemory > memoryLimitMB) {

544

throw new Error(`Memory limit exceeded: ${currentMemory.toFixed(2)}MB`);

545

}

546

547

const dom = await JSDOM.fromURL(url);

548

const result = { url, title: dom.window.document.title };

549

dom.window.close();

550

551

return result;

552

})

553

);

554

555

results.push(...batchResults);

556

currentMemory = process.memoryUsage().heapUsed / 1024 / 1024;

557

558

// Delay between batches

559

if (i + maxConcurrent < urls.length) {

560

await new Promise(r => setTimeout(r, delayBetweenPages));

561

}

562

}

563

564

return results;

565

}

566

```

567

568

## Still Having Issues?

569

570

### Check jsdom Version

571

572

```javascript

573

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

574

console.log(require("jsdom/package.json").version);

575

```

576

577

### Verify Node.js Version

578

579

jsdom requires Node.js v20 or newer:

580

581

```bash

582

node --version # Should be v20.0.0 or higher

583

```

584

585

### Enable Debug Logging

586

587

```javascript

588

// Enable debug logging

589

process.env.DEBUG = "jsdom:*";

590

591

// Or just specific parts

592

process.env.DEBUG = "jsdom:virtualconsole jsdom:*_tough-cookie";

593

```

594

595

### Search for Similar Issues

596

597

Check the [jsdom GitHub Issues](https://github.com/jsdom/jsdom/issues) for your problem.

598

599

### Minimal Reproduction

600

601

Create minimal code that demonstrates the issue:

602

603

```javascript

604

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

605

606

const html = "<!-- minimal HTML that breaks -->";

607

const dom = new JSDOM(html, { /* minimal options */ });

608

609

// Minimal code that demonstrates issue

610

// ...

611

612

console.log("Error: [your issue]");

613

```

614