describe(me) docs
early v0.12.1 browser mode · jsdom Live · Foundations
Examples npm GitHub

Environments

describe-me records in Vitest browser mode and in DOM environments such as jsdom. The frames are the same kind of thing in both. What differs is what happens inside the test.

How the plugin picks one #

The plugin reads test.browser.enabled from the Vitest config it receives. When it is set, the plugin wires browser mode. Otherwise it wires a DOM environment. The environment option wins over that check.

Browser mode DOM (jsdom)
Picked when test.browser.enabled is set, or environment: 'browser' it is not set, or environment: 'dom'
Recording render vitest-browser-react goes to @describe-me/react @testing-library/react goes to @describe-me/react/testing-library
Recorded interactions Locator actions and the keyboard-level userEvent methods @testing-library/user-event and fireEvent
Setup file @describe-me/vitest/setup @describe-me/vitest/setup-dom
Also sets optimizeDeps.include for the adapter test.css: true unless you set it, and RTL_SKIP_AUTO_CLEANUP

If you switch browser mode on some other way, so that the config the plugin receives has no test.browser.enabled, say so:

vitest.config.ts

import { describeMe } from '@describe-me/vitest/plugin'

describeMe({ environment: 'browser' })

What the plugin changes lists every config change in full. The redirect matches the exact module name only. Other ways to mount, such as vitest-browser-react/pure, record no render frame, as Tests that are left out explains.

Browser mode #

The typing frame of SearchBox comes from userEvent.type(). What records a frame lists every method. A frame holds the DOM and its stylesheets, not pixels, as What a frame captures explains.

jsdom #

Installation lists the packages and Getting started shows the config. Your tests stay as they are. This is what changes, and what does not.

Rendering

Cleanup

Timers and act

Packages

Limitations in jsdom #

jsdom has no layout engine and loads nothing, so what differs is what happens inside the test. The frames are DOM plus stylesheets in both environments, and a real browser lays them out in the viewer.

Positioned popovers

Tooltips, menus and dropdowns placed by floating-ui, Popper, Radix and similar libraries show up in the top-left corner. The library computes the position from getBoundingClientRect(), which returns zeros in jsdom, and writes it into the DOM as top and left or a transform. The viewer replays those numbers.

Workaround. Record such tests in browser mode. Or keep them in their own files and leave those out with exclude, as Which tests record shows.

Layout, scrolling and observers

Anything the test measures, scrolls or observes behaves differently. getBoundingClientRect() returns zeros, and user-event cannot tell whether another element covers the one it clicks. jsdom has no ResizeObserver or IntersectionObserver unless the test stubs them. Record tests that depend on layout in browser mode.

Fonts in the test

Web fonts never load during a jsdom test, so the test measures text in fallback fonts. The viewer shows the real ones, as Fonts explains. Postcard in browser mode and Postcard in jsdom look the same, although the jsdom test never loaded its Lora caption font.

A stylesheet <link> that a font loader adds never loads either. Each capture makes rrweb-snapshot set a 5 s timer for such a link. The timer does nothing when it fires, and the capture does not wait for it.

Constructable stylesheets

jsdom cannot fill a stylesheet built with new CSSStyleSheet(), so styles added through adoptedStyleSheets are missing in the test and in the frame. The adoptedStyleSheets line of StyledText in browser mode is orange, and in StyledText in jsdom it is not. Record components that adopt stylesheets in browser mode.

Canvas

Without the canvas package, getContext() returns null in jsdom, so the test cannot draw. Snapshots hold no canvas pixels in either environment, so a <canvas> is blank in the viewer.

fireEvent inside your own act()

fireEvent records its frame as soon as it returns. Inside an act() of your own, React flushes only when that act() ends, so the frame shows the DOM from before the update.

Workaround. Call fireEvent outside act(). Testing Library already wraps it in one. Where the act() has to stay, put the block in a step(), whose frame is taken after the block ends.

Limits in both environments #

Concurrent tests are never recorded. Tests that run concurrently, through test.concurrent, describe.concurrent or sequence.concurrent, would share one recorder. They run and report as usual, and the setup file warns once per test file:

describe-me: concurrent tests are not recorded (src/Menu.test.tsx)

Hover and focus are recorded. Each frame records which elements were in :hover, :focus, :focus-visible and :focus-within, and the viewer replays their rules, as in the frame of a ghost button on hover.

Which one to use #

Browser mode for fidelity.

The test runs in a real browser, with real layout and real fonts. Popovers, measuring code and observers behave as they do in your app.

jsdom for speed and existing suites.

No browser to install or start. A Testing Library suite records as it is, with its custom render, fireEvent and fake timers.

The frames look alike either way, because the viewer lays them out in a real browser. The difference is inside the test, as Limitations in jsdom lists. Live mounts a jsdom test in the viewer's browser too, with real layout and web fonts.

Using both

This repository keeps the two apart. Each of its example projects runs one environment, with its own Vitest config and its own .describe-me/.

In one project, give each config its own outDir, then open each output with its own viewer:

vitest.jsdom.config.ts

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import { describeMe } from '@describe-me/vitest/plugin'

export default defineConfig({
  plugins: [react(), describeMe({ outDir: '.describe-me-jsdom' })],
  test: { environment: 'jsdom' },
})
pnpm vitest run --config vitest.jsdom.config.ts
pnpm describe-me dev --data .describe-me-jsdom

One run with a browser project and a jsdom project in test.projects is not tested. Inline jsdom projects are, by pnpm check-projects. Monorepos covers how test.projects paths reach describe-me.