Don't stuff jsdom globals onto the Node global - jsdom/jsdom GitHub Wiki

A common antipattern we see when people use jsdom is copying globals from a jsdom window onto the Node.js global, and then trying to run the code—intended for a browser—inside Node.js. This is very bad and you should not do it.

Doing so runs code intended for the web browser in some kind of hybrid franken-environment polluted with a ton of globals that don't make sense there, and loses all benefits of isolating your code into a jsdom window.

It's especially problematic recently, as Node.js has gained support for various web platform APIs. Since you cannot mix jsdom and Node.js APIs (as they are from completely different implementations of the specs), the resulting code rarely works as expected. For example, trying to use a Node.js AbortController with a jsdom EventTarget, or vice-versa, will not work.

Running code inside the jsdom context

Instead, you should run the code inside the jsdom context itself. There are a variety of ways to do this. If you need to run a string of code, you can do

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

window.eval(`
  // This code executes in the jsdom global scope
  globalVariable = typeof XMLHttpRequest === "function";
`);

assert(window.globalVariable === true);

If you want to load a file into jsdom, you can do it by creating a script element:

const { window } = new JSDOM(``, { runScripts: "dangerously" });
const myLibrary = fs.readFileSync("../../whatever.js", { encoding: "utf-8" });

const scriptEl = window.document.createElement("script");
scriptEl.textContent = myLibrary;
window.document.body.appendChild(scriptEl);

An example in a test runner

Here's an example of how to load a library into a separate jsdom window created for each test:

const { JSDOM } = require("jsdom");
const myLibrary = fs.readFileSync("../../whatever.js", { encoding: "utf-8" });

let window;
beforeEach(() => {
  window = (new JSDOM(``, { runScripts: "dangerously" })).window;

  // Execute my library by inserting a <script> tag containing it.
  const scriptEl = window.document.createElement("script");
  scriptEl.textContent = myLibrary;
  window.document.body.appendChild(scriptEl);
});

it("should do the right thing", () => {
  assert.equal(window.myLibrary.doThing("foo"), "bar");
});

This way, each test creates an entirely new window and document. This contrasts with the very bad approach of mashing various properties of window onto the Node.js global, which causes all these properties to be shared not only across all your tests, but across your entire Node.js process.

⚠️ **GitHub.com Fallback** ⚠️