testing-library/dom-testing-library
github.com/testing-library/dom-testing-library ↗ · 108 files · 481 KB
DOM Testing Library Simple and complete DOM testing utilities that encourage good testing practices.
Overview
DOM Testing Library is an npm library for developers testing browser or simulated DOM interfaces through queries that resemble how users identify controls and content. It combines accessible-role and label queries, synchronous and asynchronous query variants, event construction, scoped query binding, and detailed DOM diagnostics. At commit 6049cc0bc7cf2201625c476c95fa6299d0e2fa8b, its implementation mixes JavaScript and TypeScript with separately maintained public declarations and Jest suites for jsdom and Node environments.
System map
The runtime is an importable DOM utility library with a named-export entrypoint and eight query families. Browser objects belong to the consumer; the implementation binds queries to containers and obtains windows from supplied nodes. Configuration and retry bookkeeping live in memory, while diagnostics provide a limited Node filesystem integration. Distribution and CI are defined by npm metadata, inherited kcd-scripts configuration, and GitHub Actions.
- package.json directs CommonJS consumers to dist/index.js, ESM consumers to dist/@testing-library/dom.esm.js, UMD consumers to dist/@testing-library/dom.umd.js, and TypeScript consumers to types/index.d.ts; Node >=18 is declared.
- src/index.js exports queries, asynchronous utilities, events, screen, diagnostics, configuration, and suggestions; within aliases getQueriesForElement, and queries/queryHelpers are also namespace exports.
- src/queries/index.ts aggregates LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, Role, and TestId; buildQueries supplies shared variant construction for most families.
- screen binds document.body during module evaluation when available; explicit container APIs use getQueriesForElement, and event/wait helpers resolve the supplied node's owning window.
- The inspected state owners are module-level configuration, per-wait promise/timer/observer bookkeeping, and a per-role-query WeakMap; these paths keep state in memory rather than implementing durable storage.
- Role semantics integrate aria-query and dom-accessibility-api; formatting uses pretty-format, playground URL generation uses lz-string, and Node code frames dynamically load fs, @babel/code-frame, and picocolors.
- npm scripts delegate build, lint, tests, validation, and typechecking to kcd-scripts; build excludes test directories and performs a second bundled build, while Rollup explicitly requires named exports.
- GitHub validation targets Node 18 and 24; an upstream push can trigger a dependent production-environment build and semantic release, while a separate workflow runs an alt-text bot on supported community events.
Feature catalog
Users locate elements by accessible semantics, visible text, form values, and identifying attributes. Each query family exposes variants for nullable lookup, required lookup, collections, and asynchronous lookup. Additional APIs scope queries, dispatch events, wait for disappearance, recommend queries, and print diagnostics. These are library APIs used from test code rather than application routes or screens.
- Query variants distinguish cardinality: queryBy returns null for no matches and throws for multiple matches; getAllBy throws for an empty result; getBy requires exactly one; find variants return waitFor-backed promises.
- ByRole supports explicit and implicit roles, accessible names/descriptions, hidden inclusion, fallback role tokens, selected/busy/checked/pressed/current/expanded states, heading level, and ARIA values; unsupported role-option combinations throw.
- ByLabelText resolves native labels, aria-labelledby, and aria-label, deduplicates matches, applies a selector, and distinguishes missing associations from labels attached to non-labellable elements.
- ByText accepts selector and ignore options, includes a matching element container, and uses immediate text nodes or button-like input values; matchers support exact strings, fuzzy strings, regular expressions, functions, and configurable normalization.
- ByDisplayValue matches input/textarea values or selected option text; PlaceholderText matches placeholder; AltText restricts matches to img/input/area/custom tags; Title includes SVG title children; TestId reads the configured attribute.
- within/getQueriesForElement binds a supplied query map to a container; screen provides body-bound default queries plus debug and logTestingPlaygroundURL helpers.
- createEvent and fireEvent provide registry-generated shortcuts for clipboard, keyboard, focus, form, mouse, and other events, including doubleClick aliasing; target values/files and transfer data can be supplied.
- waitFor retries callbacks and handles returned promises; waitForElementToBeRemoved requires initial presence; getSuggestedQuery ranks semantic alternatives; prettyDOM/logDOM and getRoles/logRoles expose DOM and accessibility diagnostics.
Data flow
Query inputs consist of a container, matcher, and options; family-specific candidate selection feeds shared matching and cardinality wrappers. Elements remain owned by the consumer DOM, and results are references or promises for references. Event creation can mutate those elements before dispatch, while asynchronous utilities own transient observation and retry state. Error and debugging boundaries convert DOM/source content into strings, console output, or encoded URLs.
- getQueriesForElement reduces a function map into functions bound with the container as their first argument, mutating the supplied initialValue object when one is provided.
- Text lookup validates the container, selects container/descendant candidates, removes ignored nodes, extracts getNodeText, normalizes the string, and applies the exact or fuzzy matcher.
- Label lookup gathers label metadata through getRealLabels/getLabels, combines label content and aria-label matches, then deduplicates element references and filters by selector.
- Role lookup builds a CSS selector from explicit role tokens and aria-query tag relations, then filters role identity, ordered state conditions, accessible name/description, and accessibility; ancestor exclusion results are cached per call.
- Shared wrappers turn candidate arrays into null, an element, arrays, or exceptions; optional suggestion checks run after successful lookup, and makeFindQuery passes the getter plus container/options to waitFor.
- waitFor owns lastError, promiseStatus, finished, timer IDs, and an observer; callback rejection allows later retries, pending promises block overlapping callbacks, and normal completion clears timers and disconnects the real-timer observer.
- Event initialization merges registry defaults and caller data, applies native value/file/target mutations to the node, constructs an event using its window, installs transfer properties, and returns dispatchEvent's result through eventWrapper.
- getElementError embeds prettyDOM output in an exception; logDOM additionally requests a stack-derived source frame; logTestingPlaygroundURL separately compresses raw innerHTML into a URL fragment and logs/returns it.
Behavior traces
The following traces were reconstructed by reading callers and implementations, without executing them. They cover import-time initialization, synchronous lookup, asynchronous appearance and disappearance, event dispatch, configuration updates, and debugging. Branches such as absent global DOM, fake timers, pending promises, and query errors materially alter execution. The configuration trace describes in-memory state changes rather than a persistence service.
- Initialization: src/index.js imports the query modules; those modules construct variants with buildQueries; role-helpers initializes elementRoleList; screen then binds document.body or installs throwing query stubs when no global body exists.
- Required role lookup: screen.getByRole supplies the bound body → suggestion wrapper → makeSingleQuery → makeGetAllQuery → queryAllByRole → makeRoleSelector and filters; zero results use getMissingError/getElementError, and multiple results include matching-element diagnostics.
- Text lookup: getByText's generated getter calls queryAllByText → checkContainerType → makeNormalizer → getNodeText → matches/fuzzyMatches; successful cardinality checking returns the element, while missing text reports normalization/selector hints.
- Asynchronous appearance: findByTestId → makeFindQuery → waitForWrapper → asyncWrapper → waitFor; real timers observe mutations and poll, fake timers advance through unstable_advanceTimersWrapper, and checkCallback retries getter errors until success or handleTimeout.
- Disappearance: waitForElementToBeRemoved checks initial presence; direct elements become top-parent containment callbacks; waitFor retries while any remain, treats later TestingLibraryElementError as disappearance, and resolves undefined after removal.
- Input change: fireEvent.change → createEvent.change → createEvent merges defaults and target data → setNativeValue selects the prototype or own setter → node-owned Event construction → fireEvent → eventWrapper → dispatchEvent.
- Configuration update: configure accepts an object or calls a function with existing config, shallow-merges the delta into module state, and subsequent ByTestId calls read the new attribute through getTestIdAttribute; tests explicitly restore previous configuration.
- Debugging: screen.debug calls logDOM for one element or each array element → getUserCodeFrame → first non-node_modules stack frame → readFileSync/codeFrameColumns when available → prettyDOM → console.log; unavailable dependencies or unreadable files omit the source frame.
Testing signals
Tests and CI were inspected, not executed; test existence is not a passing result. Jest configuration separates jsdom and Node projects, with separately typechecked public API examples. Inspected suites exercise query semantics, visibility, asynchronous scheduling, removal, events, and diagnostic behavior. This is a bounded source inventory: browser fidelity, actual coverage percentages, inherited thresholds, and release success remain unverified.
- Jest imports coverage settings from kcd-scripts, excludes test directories from coverage, and declares dom/node projects; the DOM project uses jest-environment-jsdom and the Node project selects __node_tests__ files.
- Test support imports jest-dom matchers, serializes MutationRecord objects, suppresses deprecated warnings, restores mocked timers, and offers an innerHTML-based render/rerender helper with body cleanup.
- Role tests assert exclusions for hidden attributes, display:none, visibility:hidden, and ancestor aria-hidden; asynchronous query tests assert success and missing-result rejection across all eight query families plus delayed TestId appearance.
- waitFor tests inspect configurable error delegation, suppression of retries while a promise is pending, pending-promise timeout, and both timer-mode switches; fake-timer tests cover legacy/modern clocks, recursive timers, and requestAnimationFrame.
- Removal tests distinguish mutation from disappearance, reject initially absent elements, accept getter errors as disappearance, check undefined resolution, and cover direct elements and arrays tracked against top-most parents.
- Event tests assert bubbling, shadow-boundary composition, alias dispatch, value/selection mutation, files, and transfer data; the DataTransfer constructor branch is exercised with a locally supplied constructor.
- Node tests model an entire form-submit/delayed-result flow without a global DOM and assert screen's no-document error; type fixtures cover direct/screen queries and custom buildQueries/within bindings, but neither runtime nor type checks ran during this investigation.
- Inspected CI specifies Node 18/24 validation, format checking, and Codecov upload; it does not establish a real-browser test result. Coverage also has explicit blind spots: DOMElementFilter is ignored as a file and the event constructor fallback is ignored as a branch.
Risk map
These are source observations and explicitly labeled hypotheses, not confirmed vulnerabilities. Sensitive boundaries center on diagnostics exposing DOM/source content, stack-derived file reads, and privileged release automation. Correctness hotspots include global configuration, generated query/type coupling, accessibility semantics, and timer transitions. The inspected library paths operate on consumer DOM objects; this bounded review does not establish whole-repository absence of authentication, IPC, shell, or network behavior.
- Node diagnostics read a filename parsed from the first non-node_modules stack frame without a repository-root restriction; logDOM can print that file's code frame. This is an observed filesystem/output boundary, not evidence of attacker control.
- The playground helper compresses raw innerHTML into a logged and returned external-site URL, while element errors serialize DOM content. Neither path shown provides redaction; the playground helper itself performs URL construction rather than a network request.
- Role and label helpers interpolate role strings, aria-labelledby IDs, and for attributes into CSS selectors without escaping in the shown expressions; unusual quotes or selector characters are a correctness hypothesis requiring reproduction. (inferred)
- The role state filter returns at the first supplied selected/busy/checked/pressed/current/expanded/level condition; callers combining such options do not get conjunction across those conditions. ARIA value fields are combined only if execution reaches the value branch.
- Configuration is shared module state and getConfig returns it directly; the expensive-diagnostics helper restores its flag to false rather than its prior value. Nested invocation and test isolation therefore merit targeted verification. (inferred)
- Source buildQueries explicitly notes a deviation from published variadic declarations; makeFindQuery forwards only matcher and options to its getter. Custom-query argument changes can affect runtime and public type compatibility simultaneously.
- Timer-switch branches reject directly rather than calling onDone, while normal cleanup is centralized in onDone; this is a lifecycle-review hotspot despite existing rejection-message tests, and cleanup consequences were not executed or confirmed.
- Release automation has OIDC/content/issue write permissions and installs without a lockfile; correctness also retains documented limitations: partial accessibility exclusion semantics, an ignored legacy constructor fallback, and a regression test recording mismatched query/error ignore behavior.
Extension points
The library exposes factories and wrappers as extension mechanisms for consumers and framework adapters. Query families plug into export tables, and custom query maps can be bound without modifying the default registry. Event shortcuts derive from a declarative map, while configuration injects scheduling, dispatch, and diagnostic behavior. Public declarations form a parallel extension contract that must remain aligned with runtime behavior.
- New built-in query families plug into src/queries/index.ts; that namespace feeds the public entrypoint and screen, so its runtime exports must remain query functions.
- buildQueries accepts an all-query plus multiple/missing error functions and produces five variants; Text illustrates this pattern, while LabelText deliberately assembles variants separately for richer errors.
- getQueriesForElement accepts a custom function map and optional initial object; consumer tests demonstrate extending default queries, and type fixtures bind a custom AutomationId family through within.
- queryAllByAttribute supplies normalized attribute matching, while exported makeSingleQuery, makeGetAllQuery, makeFindQuery, and suggestion wrappers allow custom composition below buildQueries.
- configure injects asyncWrapper, unstable_advanceTimersWrapper, and eventWrapper; waitFor and fireEvent invoke these hooks, and source comments identify framework act integration as their purpose.
- eventMap entries define constructor type and default initialization, automatically generating createEvent/fireEvent methods; eventAliasMap supplies alias dispatch such as doubleClick.
- Diagnostics can be customized through configure.getElementError, prettyDOM's filterNode and pretty-format options, and waitFor's onTimeout/mutationObserverOptions; these hooks affect failure output and retry observation.
- Matcher functions and normalizers customize matching, but normalizer cannot be combined directly with trim/collapseWhitespace; type-level extension uses QueryMethod, BuiltQueryMethods, and BoundFunctions to preserve custom argument and result types.
Contributor handoff
Start with the public export surface, shared query factories, and the smallest family-specific implementation involved in the task. Preserve runtime and declaration parity, query cardinality, node ownership, and timer cleanup when making changes. Tests and CI were inspected, not executed, and this brief is bounded rather than an exhaustive coverage or security assessment. The commands below identify existing validation entrypoints for a separately authorized implementation task; the present investigation remained read-only.
- Read README's DOM-node/user-behavior principles, package.json entrypoints/scripts, src/index.js, and types/index.d.ts first to establish product intent and the shipped runtime/type contracts.
- For query edits, inspect the family module alongside query-helpers and matches; preserve missing/multiple behavior and remember that LabelText bypasses buildQueries to distinguish broken label associations.
- For async edits, follow makeFindQuery through waitForWrapper and waitFor; inspect pending-promise, timer-switch, fake-clock, and removal tests before changing scheduling or completion.
- API changes need review of separate declarations and BoundFunctions; build disables generated TypeScript definitions, and the existing npm run typecheck script builds the types project containing public API fixtures.
- Use existing npm test for relevant Jest checks and npm run typecheck for declaration changes in an authorized execution context; CI's broader entrypoints are npm run format -- --check --no-write and npm run validate. None were executed here.
- Restore global configuration and timer modes in tests; existing suites snapshot getConfig/configure restoration and jest.useRealTimers, while the render helper clears document.body after each test.
- Watch environment-specific traps: screen captures body availability at import time, getWindowFromNode rejects unusable owner windows and mistakenly supplied promises, and event values may require the prototype setter rather than ordinary assignment.
- Review snapshot changes semantically: an existing Text test intentionally records undesired ignore behavior. Keep release changes separately authorized because the upstream workflow builds/releases after validation and maintainers document commit-message-driven versioning.
Languages
| Language | Files | Share of source |
|---|---|---|
| JavaScript | 47 | 59% |
| TypeScript | 33 | 41% |
| Markdown | 12 | docs/config |
| JSON | 4 | docs/config |
| YAML | 2 | docs/config |
Start reading here
Probable source entrypoint: src/index.js — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (61 files)
Source layout — src/
| Path | Files |
|---|---|
src/__node_tests__/ | 3 |
src/__tests__/ | 30 |
src/queries/ | 10 |
src/DOMElementFilter.ts | 1 |
src/config.ts | 1 |
src/event-map.js | 1 |
src/events.js | 1 |
src/get-node-text.ts | 1 |
src/get-queries-for-element.js | 1 |
src/get-user-code-frame.js | 1 |
src/helpers.ts | 1 |
src/index.js | 1 |
src/label-helpers.ts | 1 |
src/matches.ts | 1 |
Repository structure
| Top-level path | Files |
|---|---|
src/ | 61 |
types/ | 17 |
.github/ | 7 |
other/ | 3 |
tests/ | 3 |
.codesandbox/ | 1 |
Docs found: CONTRIBUTING.mdREADME.md
Package
package.json — @testing-library/dom v0.0.0-semantically-released, 17 declared dependencies.
@babel/code-frame@babel/runtime@testing-library/jest-dom@types/aria-queryaria-querybrowserslistcaniuse-litedom-accessibility-apijest-in-casejest-snapshot-serializer-ansijest-watch-select-projectsjsdom
CI & config entrypoints
README.md— Repository readme.github/workflows/validate.yml— GitHub Actions workflow.github/workflows/accessibility-alt-text.yml— GitHub Actions workflowpackage.json— npm scripts: build, format, install:csb, lint, setup, test, test:debug, test:update
Structural health signals
Average score 8.8/10 across 77 analyzed source files; 2 hotspots surfaced.
src/__tests__/element-queries.js— 1320 lines, score 5.7 (Large file, Deep nesting)src/__tests__/wait-for.js— 452 lines, score 6.2 (Large file, Deep nesting)src/__tests__/role.js— 778 lines, score 6.7 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
6049cc0bc7cf2026-09-13 — fix(deps): pin @types/node to a TypeScript 4-compatible version (#1386)e395d5b8ecd42025-11-12 — Switch to trusted publishing (#1368)225a3e4cfaa82025-07-27 — fix(deps): replace chalk with picocolors (#1341)a86c54ccda522024-07-22 — feat: Reduce caught exceptions in prettyDom (reland) (#1323)33555a3b804e2024-07-22 — Test highlighting of `prettyDOM` explicitly (#1324)