All free skills / Other majors
happy-dom 20.x
Use happy-dom ^20.0.0 as a headless DOM/browser implementation in standalone Bun or Node scripts, with v20 security defaults and lifecycle cleanup in mind.
- For
happy-dom@20.14.0- Verified
- 2026-09-07, 5 examples executed
- Skill package
@modootoday/pmcp-happy-dom@20.0.0
Use this skill for free
No PMCP login or subscription is required. Download the original instructions, or install the content-only archive with your project's package manager.
Download SKILL.md · Download package archive
npm install --save-dev https://pmcp.build/skills/happy-dom/20/package.tgz
PMCP discovers the installed package's instructions. Connect your runtime after installation.
Archive integrity
sha512-JExCSijGBN9PZX5KupQl37J+YKukwnt21ZmsTEm9MH3p6aFFbD9pCZOzVdkoSI+GdGl/KCeitRkGZilXB0Q71A==Complete instructions
Verified against happy-dom@20.14.0 on 2026-09-07. 5 of 5 examples executed.
happy-dom
Scope
happy-dom provides a headless DOM implementation and a programmatic browser API. The examples below are standalone scripts: each can be run as bun example.ts with only happy-dom installed.
Window: create and manipulate a DOM
Import Window from happy-dom. Pass a URL when code under test depends on the document location. Access the DOM through window.document.
import { strict as assert } from 'node:assert';
import { Window } from 'happy-dom';
const window = new Window({ url: 'https://localhost:8080' });
const document = window.document;
document.body.innerHTML = '<div class="container"></div>';
const container = document.querySelector('.container');
assert.ok(container);
const button = document.createElement('button');
container.appendChild(button);
assert.equal(document.body.innerHTML, '<div class="container"><button></button></div>');
assert.equal(window.location.href, 'https://localhost:8080/');
await window.happyDOM.abort();
window.close();
Use window.happyDOM.abort() when pending Happy DOM work should be stopped, and call window.close() when the window is no longer needed.
JavaScript evaluation is disabled by default in v20
Do not assume that scripts embedded in markup execute. Since v20, JavaScript evaluation is disabled by default for security reasons. Enable it explicitly with settings: { enableJavaScriptEvaluation: true } only when the evaluated code is trusted. A VM context is not an isolation boundary; untrusted JavaScript can pose an RCE risk.
import { strict as assert } from 'node:assert';
import { Window } from 'happy-dom';
const window = new Window({
settings: { enableJavaScriptEvaluation: true }
});
window.document.write(`
<script>
document.body.setAttribute('data-evaluated', 'yes');
</script>
`);
assert.equal(window.document.body.getAttribute('data-evaluated'), 'yes');
await window.happyDOM.abort();
window.close();
A common outdated pattern is to rely on HTML scripts executing without this setting. In v20 that default is intentionally off.
Browser and pages
Import Browser and, when configuring error handling, BrowserErrorCaptureEnum. Create a page with browser.newPage(), navigate with page.goto(), use page.mainFrame.document, and wait with page.waitUntilComplete().
The documented browser flow navigates to a URL and therefore requires the browser's page-loading environment. The standalone example below only exercises the constructible, offline portion of the API and does not make a network request.
import { strict as assert } from 'node:assert';
import { Browser, BrowserErrorCaptureEnum } from 'happy-dom';
const browser = new Browser({
settings: { errorCapture: BrowserErrorCaptureEnum.processLevel }
});
const page = browser.newPage();
assert.ok(page);
assert.ok(page.mainFrame);
assert.ok(page.mainFrame.document);
await page.waitUntilComplete();
await browser.close();
For a loaded page, the documented sequence is:
const browser = new Browser({
settings: { errorCapture: BrowserErrorCaptureEnum.processLevel }
});
const page = browser.newPage();
await page.goto('https://example.test/');
page.mainFrame.document.querySelector('a')?.click();
await page.waitUntilComplete();
await browser.close();
Always close the browser when finished. The browser/page API is not the same shape as the older examples that only construct a Window.
GlobalWindow
GlobalWindow extends Window but evaluates document scripts in the global scope rather than in a Node.js VM context. This changes global state, so use it only when that behavior is required. Enable JavaScript evaluation explicitly in v20.
import { strict as assert } from 'node:assert';
import { GlobalWindow } from 'happy-dom';
const key = '__happyDomSkillExampleValue';
const window = new GlobalWindow({
settings: { enableJavaScriptEvaluation: true }
});
window.document.write(`
<script>
globalThis.${key} = 'Hello world!';
</script>
`);
assert.equal((globalThis as Record<string, unknown>)[key], 'Hello world!');
delete (globalThis as Record<string, unknown>)[key];
await window.happyDOM.close();
The older GlobalWindow example shape that expects scripts to run without an explicit setting is not appropriate for v20.
Finding the owning window with PropertySymbol
PropertySymbol exposes the internal symbol used to retrieve the Happy DOM window associated with a document. Current code uses PropertySymbol.window; the setup documentation also shows an older PropertySymbol.ownerWindow name, so use the fallback when supporting both shapes.
import { strict as assert } from 'node:assert';
import { PropertySymbol, Window } from 'happy-dom';
const window = new Window();
const document = window.document as typeof window.document & {
[PropertySymbol.ownerWindow]?: Window;
[PropertySymbol.window]?: Window;
};
const owningWindow =
document[PropertySymbol.ownerWindow] || document[PropertySymbol.window];
assert.equal(owningWindow, window);
await window.happyDOM.abort();
window.close();
Test-environment integrations are separate
Happy DOM can be used by test tools, but those integrations are not needed for the standalone Window and Browser APIs:
- Jest requires the separate
@happy-dom/jest-environmentpackage. - Node's native test runner requires
@happy-dom/global-registratorto be imported before tests. - Global DOM globals require
@happy-dom/global-registrator, for exampleGlobalRegistrator.register({ url: 'http://localhost:3000' }). - In Vitest, the documented timer workaround replaces the host timer functions with the window's timer functions. The relevant window is obtained through
PropertySymbol.ownerWindowor the currentPropertySymbol.windowfallback.
Do not copy runner globals such as describe, it, or expect into a direct Bun script; they are not provided by happy-dom.
Server rendering is a separate package
SSR/SSG command-line rendering belongs to @happy-dom/server-renderer, not the happy-dom package itself. Its command-line JavaScript option is separate from the programmatic Window setting. The server renderer keeps JavaScript disabled by default and enables it with its --javascript option.
Not covered
This skill does not cover the complete DOM API, browser navigation and network behavior, every browser/page lifecycle method, Vitest/Jest configuration, the @happy-dom/global-registrator package API, or @happy-dom/server-renderer usage beyond identifying that these are separate integrations. It also does not establish a security boundary for evaluated JavaScript.