A DOM for unit and component tests: your tests get the same results as on jsdom, faster. A drop-in environment for Vitest and Jest, written from the WHATWG specifications.
- Same results. On 23 open-source suites (14 we tuned on, such as MUI, React Spectrum, handsontable, Radix, Vuetify, ariakit and svelte, about 43,600 tests; and 9 held out, about 13,500 tests), every test has the status it has on jsdom, apart from flaky tests and a short list of explained differences (below). In 12 CI jobs, 17 of the 25 suites (with two web applications) had no test that differed from jsdom in any round.
- Faster on every suite we tuned on. Drop-in, warpdom was faster than jsdom on all 13 tuning suites in each of three independent runs: a median of about 1.27x, from 1.03x (solid) to 1.89x (MUI) per suite. handsontable, whose core runs jsdom 16.7, ran about 8–9x faster at the measured commit and about 3.2x on its current branch, which has since added its own jsdom speed-ups; it is reported separately.
- Check it on your own suite in one command:
npx warpdom compare.
Status: 0.x, before 1.0; what 1.0 will promise and what is still missing is in docs/road-to-1.0.md, how releases, jsdom, Node and test runner versions are supported in docs/stability.md. Numbers, method and raw data: docs/research/README.md, measured with dom-api-tracer.
Node 22.12 or later. Vitest 2 or later (tested with 2, 3, 4 and 5) or Jest 27 or later (tested with 27 and 30). Fast mode needs Vitest 3.2 or later.
npm install --save-dev warpdomVitest. Vitest takes a path (or a vitest-environment-<name> package)
as test.environment; warpdom exports the path:
// vitest.config.js
import { defineConfig } from 'vitest/config';
import { environment } from 'warpdom/vitest/environment';
export default defineConfig({
test: { environment },
});Or install the small companion package vitest-environment-warpdom too,
and use the name: test: { environment: 'warpdom' }.
With test.projects, set it on the projects that use jsdom today.
Jest.
// jest.config.js
module.exports = {
testEnvironment: 'warpdom/jest',
};Or install the small companion package jest-environment-warpdom too, and
use the name, as with jsdom: testEnvironment: 'warpdom', and
/** @jest-environment warpdom */ in test files (Jest 27 to 30).
The page URL: environmentOptions: { warpdom: { url } } with Vitest
(default http://localhost:3000, as with Vitest's jsdom environment, which
reads environmentOptions.jsdom.url), testEnvironmentOptions: { url }
with Jest (default http://localhost/, as with jest-environment-jsdom).
Test files that choose jsdom with a comment (/** @vitest-environment jsdom */,
@jest-environment jsdom) keep using jsdom; change the comment to switch
them.
Switch those comments too, even if the files pass on jsdom. Vitest groups
test files by environment name and runs each group on its own workers, so
files whose comment names another environment than the config run apart
from the others. In a suite whose files share module state
(isolate: false), that changes which files run before which in a
worker, and with it the result of order-dependent tests: one of solid's
tests passes or fails by that order alone, on jsdom and on warpdom alike.
Use the same name in the config and in the comments (environment: 'warpdom' with vitest-environment-warpdom, and
/** @vitest-environment warpdom */), or remove the comments.
Test suites encode the behavior of the jsdom version they were written
against (for example, getComputedStyle returned empty strings for most
properties before jsdom 29, and initial values since). warpdom detects the
jsdom your project installs (and, for jsdom 26 and earlier, the cssstyle
version it loads) and follows that version's observable behavior. With
jsdom alone, another jsdom version changed test results in 13 of 23
projects we measured (Version dependence). To set
it yourself:
// Vitest
test: { environment, environmentOptions: { warpdom: { jsdom: '26.1.0' } } }
// Jest
testEnvironmentOptions: { jsdom: '26.1.0' }Interfaces, members, event handlers, the window's shape, style objects and
computed values follow the version, as measured on jsdom's releases: for
example, crypto.randomUUID() exists from jsdom 22.1.0, and computed
lengths are px from 30.0.0. The full list, as tables by area:
docs/jsdom-versions.md.
The window has the globals that jsdom version has; interfaces warpdom
has before it (ToggleEvent, TimeRanges, the CSS rule interfaces
before jsdom 29, ...) get their globals with beyondJsdom: true, set
where jsdom is.
warpdom models jsdom 20.0.3 to 30.1.3. With a newer jsdom installed (a later minor or major release), tests get jsdom 30.1.3's behavior and warpdom prints a warning once; a weekly check (upstream-watch) opens an issue when such a release appears.
XMLHttpRequest exists in every jsdom version, so warpdom has it too:
written from the XHR standard on top of the host's fetch, with jsdom's
observable behavior (events and their order, CORS checks and preflights,
timeouts, aborts, errors; jsdom 28 changed several of these when it moved
to undici, and the profile follows). HTTP libraries that look for it
(axios) take their XHR adapter, as with jsdom. Request mocking works as
with jsdom: msw answers the request the XHR makes through fetch (so a
cross-origin mocked response needs Access-Control-Allow-Origin, as on
jsdom), and nock answers XHR itself. The differences are listed in
docs/differences.md.
Before XMLHttpRequest, axios used Node's HTTP client under warpdom. To keep
that (no XMLHttpRequest at all):
// Vitest
test: { environment, environmentOptions: { warpdom: { xhr: false } } }
// Jest
testEnvironmentOptions: { xhr: false }npx warpdom compare # one round: results and an indicative speedup
npx warpdom compare --rounds 3 # numbers worth quoting
npx warpdom compare --correctness # results only (for CI)Run it where you run vitest or jest. It runs your suite on jsdom (or
happy-dom, whichever you use) and on warpdom, and reports every test whose
status changes, grouped by error, and the speedup. Nothing in your project
is edited. It exits 1 if any test that passes on jsdom fails on warpdom. The
report's summary contains no file or test names, so it can be shared.
Monorepos with Vitest projects or workspaces and Jest projects are
supported. Details: docs/compare.md.
In CI, the warpdom compare action runs the same comparison in a non-blocking job on every pull request and puts the differing tests and the time saved in the job summary (and, optionally, one pull request comment), without switching anything:
- uses: sonukapoor/warpdom/compare-action@main # better: a release tag or commit SHAOne caveat: on the warpdom side, compare switches Vitest test files that
pin jsdom with a comment to another environment name than the config's,
so they run in a separate group (see Setup). In a suite with
isolate: false and order-dependent tests, that alone can change a
result; check such a difference with the comments switched.
npx warpdom migrate --dry-run # the plan, nothing changed
npx warpdom migrate # make the changes, then installRun it at the root of your project after warpdom compare (compare needs
jsdom still installed). It switches every place that picks jsdom or
happy-dom to warpdom: Vitest configs, projects and workspace files,
environmentMatchGlobs, Jest configs and package.json "jest",
@vitest-environment / @jest-environment comments and --environment
flags in scripts, each with an exact text edit. It adds warpdom and
vitest-environment-warpdom or jest-environment-warpdom where jsdom was
declared, removes jsdom, happy-dom and their environment packages when
nothing imports them any more, and prints the install command (it does not
install). What it cannot change exactly (a computed environment, one
inherited from another package) is listed as manual, and lines that
mention jsdom (setup files, polyfills, console filters) are listed for
review. It also warns about release-age settings (pnpm
minimumReleaseAge, yarn npmMinimalAgeGate) with the date the new
packages can be installed. warpdom behaves like the jsdom version it finds
installed, so when migrate removes a jsdom version other than the one
warpdom assumes by default, it pins that version next to the environment
(environmentOptions.warpdom.jsdom, testEnvironmentOptions.jsdom) and
your tests keep their current jsdom behavior (--no-keep-jsdom-version
leaves it out). Details:
docs/migrate.md.
warpdom is one setting, so going back is one line (with fast mode, also
remove withFastMode and warpdom vitest run). jsdom (with Jest,
jest-environment-jsdom) must be installed; warpdom migrate removes it,
so reinstall the version you had before first.
// The whole project
test: { environment: 'jsdom' } // Vitest
testEnvironment: 'jsdom' // JestOne test file (wins over the config and the command line; with Jest, as the file's first comment):
// @vitest-environment jsdom/** @jest-environment jsdom */# One run, no file edited
npx vitest run --environment jsdom
npx jest --env=jsdomFor happy-dom: happy-dom (Vitest), @happy-dom/jest-environment
(Jest). To undo warpdom migrate: git restore . (or git revert its
commit), then install. Running both DOMs side by side, CI-only switches,
uninstalling, and reporting a test that needs jsdom's behavior:
docs/switching-back.md.
Fast mode (Vitest). Vitest isolates every test file, and reloading modules per file is a large part of a component suite's time. Fast mode runs files without isolation and resets warpdom's window between them; files that still leak module state are learned and run isolated. How much it gains depends on how much of a suite's time goes into reloading modules per file; measure it on your suite before relying on it.
Measured on GitHub Actions on 16 open-source Vitest suites (up to 5 rounds;
warpdom 2606b3d): where sharing pays it is much faster than both jsdom's
default and warpdom's drop-in, e.g. VueUse 9.6x over jsdom (5.2x over the
drop-in), MUI 6.7x (3.7x), Vuetify 5.6x (3.8x), Radix 4.7x (2.6x),
naive-ui 3.4x (3.0x), tanstack-query 2.4x (1.6x). Where sharing does not
pay it runs the suite isolated (or splits for a gain within noise) and
equals the drop-in (0.98-1.03x on the other suites). Median over the
16: 1.54x over jsdom's default, 1.01x over the drop-in. Against the
fastest jsdom configuration that keeps every result (isolate: false, a
cleanup, or the VM pool), the median is 1.27x, and it is slower on two
suites (naive-ui 0.67x; melt-ui 0.84x). On Vitest older than 3.2,
warpdom vitest run is not available and runs Vitest as configured,
that is, the drop-in. Four of the 16 suites (melt-ui among them) run an
older Vitest and ariakit passes three --project arguments, so those
five were measured with our harness's emulation of the same rules.
| Suite | Mode chosen | vs jsdom default | vs best correct jsdom setup | vs warpdom drop-in |
|---|---|---|---|---|
| VueUse | split | 9.62x | 9.62x | 5.17x |
| MUI | split | 6.67x | 1.54x | 3.73x |
| Vuetify | split | 5.61x | 1.11x | 3.80x |
| Radix | split | 4.73x | 1.47x | 2.59x |
| naive-ui | split | 3.36x | 0.67x | 3.01x |
| tanstack-query | 7 of 19 projects split | 2.41x | 2.41x | 1.59x |
| CopilotKit | vue split | 1.52x | 1.52x | 1.22x |
| ark-ui | partly split | 1.59x | 1.59x | 1.01x |
| floating-ui / melt-ui | isolated | 1.56x / 1.44x | 1.01x / 0.84x | 1.01x / 0.99x |
| 7 more suites | isolated | 1.05-1.35x | – | 0.98-1.03x |
| Median of 16 | 1.54x | 1.27x | 1.01x |
// vitest.config.js
import { withFastMode } from 'warpdom/vitest/fast';
export default withFastMode({ test: { environment: 'warpdom' } });npx warpdom vitest runFast frames (opt-in). requestAnimationFrame normally waits 16.7 ms of
real time per frame, in jsdom and warpdom. With fastFrames, frames run as
soon as the event loop gets to them; suites whose test helpers wait for
frames get much faster (ariakit 1.6x over warpdom's default). A few tests
can change result, so it is off by default: check with
npx warpdom compare --fast-frames.
// Vitest
test: { environment, environmentOptions: { warpdom: { fastFrames: true } } }
// Jest
testEnvironmentOptions: { fastFrames: true }Some checks run on every change, others on a schedule; all of them are public:
- jsdom as a black-box reference. Behavior comes from the WHATWG and W3C specifications. Where jsdom differs from them, or from itself across versions, the difference is measured from outside (scripts that run jsdom 20 to 30 and compare results), never copied from its source. Each measured difference becomes a profile setting (Which jsdom warpdom behaves like) and a test.
- Tests on every pull request and every push to main
(ci.yml:
npm test, about 1,400 tests on Node 22.22 and 24): unit tests, parity tests that run the same code on warpdom and on the matching jsdom version side by side (test/parity.js), and an API test that walks the windows of the newest jsdom of every major from 20 to 30 and fails when warpdom lacks a member jsdom has (test/jsdom-api-superset.test.js; known exceptions are listed in the test). - Real test suites before merging a behavior change. The maintainer starts a comparison (a manually started workflow, not an automatic check) in which open-source component libraries (React, Vue, Svelte, Solid and Angular) and two applications run on jsdom and on warpdom on GitHub-hosted runners, and every test's result is compared (corpus-timing.yml, dom-api-tracer). These suites also drove many fixes, so they are regression checks, not an independent validation; the method and every number are in docs/research/. The remaining differences are listed in Differences from jsdom.
- Web Platform Tests on 17 areas, monthly at the newest WPT (wpt.yml, Web Platform Tests).
- Benchmarks on pull requests (bench.yml): the same scripts on the base and the head, alternating on one runner, posted as a comment (for pull requests from forks and Dependabot, in the run's summary instead).
- Review and supply chain: every change is a pull request with
automated code review and a dependency scan (CVE Lite CLI, in
ci.yml); workflow actions are pinned to
commits except CVE Lite CLI, which follows its
v1tag on purpose; OpenSSF Scorecard runs on pushes to main and weekly (scorecard.yml); releases are published from GitHub Actions with npm provenance (RELEASING.md). - Watching upstream (upstream-watch.yml): a weekly job opens an issue when a jsdom newer than the newest modeled one, a changed inline style surface, or a new Vitest or Jest major appears.
You can run the same comparison on your own suite with
warpdom compare.
All numbers below are from GitHub Actions runs on one frozen commit,
warpdom 8630317 (2026-10-13): hosted Linux runners with 4 cores, pinned
project commits, each project's own runner, config and jsdom, 2 workers.
In each job the DOMs alternate on the same machine: an untimed warm-up,
then 5 rounds with the order rotated. A speedup is the median of the
per-round ratios, so it never mixes machines. Raw data, scripts and every
derived number: docs/research/
(docs/research/data/ci-2026-10-13-208ad49-*).
| Result | |
|---|---|
| Drop-in speed, 13 suites, 3 independent runs | median 1.28x / 1.27x / 1.26x faster than jsdom; every suite faster in every run (1.03x to 1.89x) |
| handsontable (jsdom 16.7, outside the medians) | 8.0-8.9x (593 s to 69 s); 2.5x on the first run after install |
| Held-out suites (8) | median 1.36x (1.01x to 1.96x) |
| Applications | Excalidraw 1.21x, Wire 1.03x, 0 tests changed |
| Same results | 17 of 25 suites: no test changed in any round; the other 45 tests are each explained below |
| happy-dom, for comparison | median 1.25-1.28x faster than jsdom, but changes results in 12 of 13 suites |
| Fast mode, 16 Vitest suites | up to 9.6x over jsdom (VueUse); median 1.54x over jsdom, 1.01x over the drop-in |
| Version dependence (jsdom alone) | another jsdom version changes results in 13 of 23 projects |
| Web Platform Tests | 75.7% of subtests in 18 test-DOM areas (jsdom: 67.7%) |
The first of three identical runs (run 38426985620), sorted by jsdom time. Times are the median of 5 rounds. The range is the suite's speedup in each of the three runs.
| Suite | jsdom | happy-dom | warpdom | warpdom vs jsdom | warpdom, 3 runs | Tests that change result: happy-dom / warpdom |
|---|---|---|---|---|---|---|
| ariakit | 537.3 s | 232.1 s | 407.2 s | 1.32x | 1.32–1.39x | 51 / 7 ¹ |
| CopilotKit | 456.5 s | 48.6 s ² | 366.3 s | 1.24x | 1.23–1.25x | 4,681 / 0 |
| React Spectrum | 419.9 s | 363.1 s | 338.4 s | 1.24x | 1.19–1.26x | 1,441 / 0 |
| MUI | 264.0 s | 185.1 s | 141.2 s | 1.89x | 1.88–1.89x | 1,042 / 0 |
| ark-ui | 205.7 s | 141.5 s | 130.8 s | 1.59x | 1.56–1.61x | 116 / 3 ³ |
| svelte | 91.4 s | 85.5 s | 84.6 s | 1.08x | 1.08–1.09x | 288 / 1 ⁴ |
| Vuetify | 39.6 s | 26.7 s | 26.8 s | 1.49x | 1.49–1.52x | 4 / 0 |
| emoji-picker-element | 37.2 s | 32.3 s | 29.1 s | 1.28x | 1.18–1.28x | 1 / 0 |
| Radix | 29.6 s | 19.7 s | 16.4 s | 1.79x | 1.75–1.86x | 18 / 0 |
| floating-ui | 23.7 s | 18.8 s | 15.5 s | 1.53x | 1.52–1.55x | 14 / 0 |
| analog | 19.3 s | 17.3 s | 16.0 s | 1.20x | 1.19–1.21x | 2 / 0 |
| react-hook-form | 12.8 s | 14.2 s | 10.3 s | 1.24x | 1.21–1.24x | 8 / 0 |
| solid | 3.4 s | 3.1 s | 3.3 s | 1.03x | 1.03x in each | 0 / 1 ⁵ |
¹ Tests that fail on jsdom and pass on warpdom.
² CopilotKit is fast on happy-dom because 4,681 of its tests break there.
³ Tests that fail on jsdom and pass on warpdom, and one that is flaky on
jsdom too (2 without it).
⁴ A deliberate difference (DOMException, below).
⁵ An order-dependent test of solid's, not a DOM difference (below).
- Median: 1.28x, 1.27x and 1.26x in the three runs (95% two-level bootstrap 1.22–1.54x, 1.21–1.54x, 1.19–1.55x), 1.27x over the three runs' rounds pooled. The suites fall into two groups, about 1.0x to 1.3x and 1.5x to 1.9x, so read the table rather than the median. Between runs, a suite's speedup moved by a median of 2.3% and at most 8.6% (emoji-picker-element), although absolute times moved by up to 1.6x with the runner's CPU (Radix on jsdom: 28.9 s to 46.8 s).
- Where the gain is small: a faster DOM only speeds up the part of a run spent in the DOM; compilers, frameworks, module loading and waiting stay as they are. solid (1.03x; single rounds 0.97x to 1.06x) and svelte (1.08x to 1.09x) gain little, as do the held-out taiga-ui (1.01x, 0.99x to 1.03x) and react-router (1.04x) and the Wire web app (1.03x, 1.00x to 1.03x).
- happy-dom is about as fast (median 1.25x, 1.27x, 1.28x over jsdom in the three runs) but changes results in 12 of the 13 suites, and is slower than jsdom on react-hook-form (0.88x to 0.94x).
Reported apart from the medians because its core, React and Vue packages run jsdom 16.7.0, older than the jsdom 20 the medians require. With a warm Jest cache the whole suite ran 8.62x faster in the run above (593.2 s on jsdom, 69.2 s on warpdom) and 7.97x to 8.87x in the three runs; the core package alone 11.2x to 12.5x, the Angular wrapper (jsdom 20.0.3) 1.86x to 1.98x. On the first run after install, when both DOMs pay for a cold transform cache and the Angular wrapper's compile, it was about 2.5x (2.53x to 2.55x). 3 tests differ, all deliberately (below). happy-dom completed no timed round.
On handsontable's current branch (develop at fa6f901, measured 2026-10-14 with warpdom 0.1.1, 5 rounds) the speedup is smaller, 3.17x (251 s on jsdom, 79 s on warpdom; core 3.48x): handsontable has since added its own workarounds for jsdom's slowness (a stylesheet mock and a patch of jsdom's CSS library), which make its jsdom baseline faster. 7 of its new tests test that patch of jsdom's CSS library, which warpdom does not have, so they fail on warpdom.
Held-out. Nine projects warpdom was not tuned on (naive-ui, taiga-ui,
melt-ui, dom-testing-library, tanstack-query, solid-primitives, VueUse,
react-router, downshift; about 13,500 tests). At first contact 58 tests
(0.43%) differed; fixing their causes means the set is no longer unseen.
Drop-in, 5 rounds: a median of 1.36x (95% two-level bootstrap 1.05x to
1.69x) over the 8 projects with valid jsdom rounds, from 1.01x (taiga-ui)
to 1.96x (VueUse); solid-primitives 1.14x from its raw rounds (its jsdom
runs report 901 or 922 tests, so the broken-round rule drops them). In
the 17 CI rounds that ran each of them, only these tests changed result:
tanstack-query 1 (the ownerNode difference below), melt-ui 1
(speed-sensitive, below) and solid-primitives 4 (an order dependence of
the project's own, below), plus flaky tests.
Web applications. Excalidraw ran 1.21x faster (1.20x to 1.23x) and the Wire web app 1.03x (1.00x to 1.03x, about equal), with no test that changed result. happy-dom changed 44 and 63 of their tests and ran Wire at 0.54x of jsdom's speed. Both apps drove fixes when they were first tried (2 and 141 tests differed then), so this is a migration case study, not held-out evidence.
Suites encode the jsdom version they were written against. Measured with jsdom alone, no warpdom: running each project on jsdom 20.0.3, 26.1.0 or 30.1.2 instead of its own version changed the status of tests in 13 of 23 projects (counting tests that ran; files that fail to load under a forced version are left out). It is concentrated: ariakit 258 tests (13.9%) on jsdom 20, React Spectrum 138 on jsdom 30, svelte 38, the others 1 to 16. Even a minor update of cssstyle, the CSS library jsdom 26 and earlier load (4.1.0 to 4.6.0), changed 12 svelte tests. warpdom therefore follows the jsdom (and cssstyle) each project installs. Given the latest jsdom's behavior instead of their own, warpdom would differ from jsdom in more tests in 7 of 23 suites: React Spectrum 0 → 146, svelte 1 → 25, handsontable 3 → 23, downshift 0 → 7, ark-ui 2–3 → 6, CopilotKit 0 → 1, dom-testing-library 0 → 1.
On the 18 WPT areas a test DOM is expected to cover (3,825 files; dom,
domparsing, html/dom, html/semantics/forms and interactive-elements,
css/cssom, css/cssom-view, url, encoding, encoding/streams, FileAPI, WebCryptoAPI,
hr-time, user-timing, the resize and intersection observers,
web-animations/interfaces and xhr), warpdom passes 75.7% of 188,497
subtests (jsdom 30.1.2: 67.7%). It is ahead or level in every area:
html/semantics 3,972 vs 3,838 subtests, domparsing 449 vs 342.
Neither implements WebCrypto's subtle,
the observers or Web Animations (opt-in work, #5-#12), which keeps both totals
low; most xhr tests need WPT's server, which the runner does not have.
warpdom was worked on against the first four areas (87.4% vs 80.5% there
on 2026-10-13), so those are not an independent check. The runner is in
wpt/; the workflow runs all 18 areas monthly at the newest WPT.
Full tables, method, profiles and caveats: docs/research/README.md.
API by API, how warpdom, jsdom, happy-dom and linkedom each support the web platform is on DOMParity, a separate reference site (maintained by warpdom's author).
warpdom matches jsdom's observable behavior where tests depend on it, and otherwise follows the standards. Deliberate differences, each with a test, that changed a test in the 25 measured suites:
| Area | warpdom | jsdom | Seen in |
|---|---|---|---|
DOMException |
An Error in the test's realm, as the standard says |
Not one in Vitest's default pools | 1 svelte test |
CSSStyleSheet.ownerNode |
The owning element in the jsdom 27 and 28 profiles, as in jsdom 29 and later; none in the 20 to 26 profiles, as those versions | 27.0: none; 27.1 and 27.2: null; 27.3 to 28.1: an internal object that is not a Node |
1 tanstack-query test (jsdom 27.4) |
| Style-sheet parse errors | Not reported to the console | jsdom 16 cannot parse CSS nesting, drops such a sheet and reports "Could not parse CSS stylesheet" | 3 handsontable tests of its filter for that message (jsdomThemeCssFilter.unit.js) |
The other differences, in full in docs/differences.md:
| Category | In short |
|---|---|
| Results that change for other reasons | Tests that fail on jsdom and pass on warpdom (ariakit, ark-ui), one speed-sensitive test (melt-ui), two order dependences of the projects' own suites (solid, solid-primitives) |
| Events and focus | isTrusted is a prototype accessor; tabindex="abc" gives the default tab index; a disabled fieldset, optgroup or option is not focusable, as the standard says |
| Standard behavior where jsdom deviates | FormData, formnovalidate, Selection, custom elements, XPath, MutationObserver, document.write, CSSOM, XML serialization, iframes and more |
| Additions jsdom lacks | innerText, ToggleEvent, Range rects and others |
| Styles | MediaList and indexed style properties the same in every profile; spied style setters see assignments only; some cssstyle details and computed values not modeled |
| Platform objects | FileReader, TextDecoder, URL, WebAssembly in iframes, WebSocket |
| XMLHttpRequest | Requests go through the host's fetch: header lists, cookies, sync requests, file: URLs, Referer and a few event orders differ |
Not implemented (jsdom lacks these too): layout (sizes and positions are
0, as in jsdom), inert, navigation to other documents. Not implemented
in warpdom only: loading external style sheets, scripts or pages, which
jsdom does when configured to (resources: 'usable').
import { createWindow } from 'warpdom'; // or const { createWindow } = require('warpdom');
const window = createWindow({ url: 'http://localhost/', jsdom: '30.1.2', fastFrames: false });
window.document.body.innerHTML = '<p>hello</p>';require('warpdom') works in Node and in Jest test files, and TypeScript
types ship with the package. docs/api.md has the options and
the equivalents of jsdom's API (new JSDOM(html, { url }), dom.window,
dom.serialize()) for tests that create windows themselves.
warpdom/compare exports compare(options), the API behind the CLI;
warpdom/migrate exports migrate({ cwd, apply }), which returns the plan.
npm test # unit and parity tests (node:test; parity tests run jsdom side by side)
npm run test:types # the published types, under tsc --strict (test/types)
npm run build # dist/warpdom.cjs, the CommonJS build (also run by npm test and npm pack)
npm run bench:load # cold load and window creation
npm run bench:hot # hot DOM members against jsdom and happy-dom
npm run format # Prettier (CI runs npm run format:check)
npm run lint # ESLint readability rules, such as no nested ternaries (Node 22.13+)
node wpt/run.mjs dom/nodes --engine warpdom,jsdom # Web Platform Testswarpdom is written from the specifications; jsdom is used as a black-box
reference (its source is not read or copied). Plans and decisions:
PLAN.md; architecture: docs/design.md; the
road to 1.0: docs/road-to-1.0.md; versions and
support: docs/stability.md; reporting a
vulnerability: SECURITY.md.
Code is formatted with Prettier and linted with ESLint in CI; existing
nested ternaries are listed in eslint-suppressions.json until they are
rewritten. git config blame.ignoreRevsFile .git-blame-ignore-revs keeps
the formatting commit out of git blame.
Releases are published from GitHub Actions with npm provenance when a
version tag is pushed: RELEASING.md.
The weekly upstream check
(upstream-watch), which opens an
issue when a jsdom release newer than the modeled range appears, also
reads DOMParity's existence data and files
the gaps it finds (scripts/api-gaps.mjs): one
issue (labels parity, missing-api) per interface jsdom has that
warpdom lacks, or with members jsdom has that warpdom lacks, at most 15 a
run; and one tracking issue, "Beyond jsdom: core APIs other test DOMs
have", whose checklist lists the "Test DOM core" APIs neither jsdom nor
warpdom has but happy-dom does, rewritten each run with ticked boxes
kept. What an existing API issue names is skipped, and nothing happens
when the data has not changed and no gaps are pending (a run that hit the
limit of 15 issues files the rest on later runs, with the same data). Run node scripts/api-gaps.mjs locally
for a dry run that prints what it would open.
CI scans the dependencies with CVE Lite CLI (fails on high or critical advisories in production dependencies; results in GitHub Code Scanning) and runs OpenSSF Scorecard weekly.
If you use warpdom in research, please cite it: GitHub's "Cite this repository" button reads CITATION.cff, and each release is archived on Zenodo with a DOI.
MIT. Generated data tables in src/ are derived from the Unicode Character
Database (src/bidi-data.js, Unicode License v3) and from
webref (src/css-data.js,
src/html-elements.js, src/event-handler-names.js; MIT); the scripts
that generate them are in scripts/. The Web Platform Tests
are not included: the runner in wpt/ uses a separate checkout
(WPT is under the 3-Clause BSD License).