Viewer and CLI
describe-me dev serves the viewer, repaints it after every test run and can
mount a test in Live. describe-me build writes the
recorded frames as a static site, with Live under --live. This page covers
both commands and everything the viewer does.
The CLI #
The describe-me package installs one binary. It always needs a command:
describe-me dev [--data .describe-me] [--port 6006] [--no-live] [--root <dir>]
describe-me build [--data .describe-me] [--out describe-me-dist] [--no-vendor-fonts]
[--live] [--root <dir>]
| Command or flag | Default | What it does |
|---|---|---|
describe-me dev |
— |
Starts a Vite dev server for the viewer and serves the data directory under
/__data. When the reporter rewrites manifest.json, the
server tells the page, and the viewer loads the manifest again. Vite prints the
URL. Then it starts the live preview and prints its address
after Live preview:. It never downloads fonts.
|
describe-me build |
— |
Writes the viewer as a static site to --out and copies the data
directory into it as __data/. With --live it builds the
live preview into __live/ and writes __live.json next to
the viewer. Then it downloads web fonts into the copy of the data (fonts in the static site). The data directory itself never changes.
|
describe-me --help |
— |
Prints the usage. So does describe-me without a command, also when it
has flags.
|
--data |
.describe-me |
dev and build. The directory the reporter wrote,
relative to the current directory. When you set the plugin's
outDir, pass the same directory here.
|
--out |
describe-me-dist |
build only. Where the static site goes. The directory is emptied
first, so never point it at a folder that holds other files.
|
--port |
6006 |
dev only. The port of the dev server. When it is busy, Vite moves on
to the next free port. Read the URL it prints.
|
--help, -h |
— | Any command. Prints the usage instead of running it. |
--live |
on in dev,off in build |
build builds the live preview into the site, so
the static site shows Live (Live in the static site).
dev has it on anyway. The site then holds the compiled code of your
components, the test files and the setup files with their test data, readable
because it is not minified, and your project's public/. Publish it
only where that code may be seen. A failed live build stops the command with exit
code 1 (why it stops).
|
--no-live |
on in dev,off in build |
dev starts the viewer alone, without the live preview. The
Live toggle is then hidden. On build it is the
default: the site has no Live.
|
--root |
from the manifest |
dev and build --live. The project of the live preview,
relative to the current directory. Without it, the preview uses the
root and
configFile of the
manifest. With it, the preview uses that directory and the config file Vitest
picks there, the first vitest.config.*, then
vite.config.*. Use it before the first test run, when the manifest
comes from another machine, or on the __data/ of a built site, whose
manifest names the project by an id.
|
--no-vendor-fonts |
fonts are downloaded |
build only. Leaves web fonts on their hosts, so the build makes no
network requests. Use it in CI without network access or under privacy rules.
|
-
Any first word other than
build, a typo included, starts the dev server. Use--helpfor the usage. -
A flag of the other command is ignored, so
--portdoes nothing onbuild. Neither does--rootwithout--live. -
Without data,
buildwarnsno data at …; run vitest firstand still finishes. The site then says "no manifest yet — run vitest first". - An unknown flag stops the CLI. It prints the error and exits with code 1:
describe-me: Unknown option '--prot'. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- "--prot"
Finding your way #
- Header. The number of tests, how many passed and failed, the issues chip, the time of the last run and the theme switch.
-
Sidebar. The search field, then one block per test file with its
describeblocks and tests in source order. When the config has groups, the files sit in them. A file with a single top-leveldescribeshows its name over the file path. Each test shows its status, its tags and its number of frames. A click on a file ordescriberow opens its overview. The arrow in front of it collapses it. Drag the sidebar's right edge to make it narrower or wider, between 216 px and half the window. A window narrower than 592 px allows up to 296 px. A double-click on the edge sets it back to 296 px. Tab and ← → work too, as the keyboard table says. The browser keeps the width per project, like the collapsed rows. It is not part of the link. Without storage it lasts until you reload. When the run found tokens or the project uses Material UI, a Foundations entry above the test files opens Foundations. -
Stage. The test's path and the
viewport toolbar with the Live toggle, the
sizes and the background swatches, above the replayed frame or the test in Live. A path
too long for the row gives up its
describeblocks first, then ends the test's name in an ellipsis, and its title shows it whole. A note under a replayed frame names the fonts and stylesheets that did not load in your browser, and says when the frame has only a light variant to show on the dark canvas. - Frame timeline. One button per frame, with its kind, its label and the time into the test. A click shows that frame and leaves Live.
- Inspector. The test's status and duration, the component with its props, the current frame as "frame 2 / 3" with its label, and the errors of a failed test. In Live, a Live section takes the place of the frame. Up to 12 props show in full. With more, the table shows the first 10 and Show N more, and Show fewer folds it again. It stays unfolded for that test, across frames and Live, until you reload the page.
The Counter increments test in the browser-mode example has three frames. Step through them with ← and →.
Groups
With sidebar.groups, the files sit in named groups, which can nest. A group's row shows an arrow and the
name. A click anywhere on it opens or closes the group. Groups come in the config's order,
a group's subgroups before its files, and files without a group come after every group.
A group without subgroups whose one file has a single describe of the group's
name, in any case, shows as that file alone, so a Button group does not
repeat its Button file. It has no row to close. The file's own arrow still
collapses the file. A manifest without groups shows the files in path order. The sidebars
of the browser-mode and the
shadcn/ui examples show groups.
Hiding the panels
The first row of a test, an overview and Foundations starts and ends with a toggle. The one on the left hides or shows the sidebar, the one on the right the inspector, and the center takes the space. [ and ] do the same. A toggle looks pressed while its panel shows. / and a click on a tag in the inspector show a hidden sidebar, since both need it.
Until you toggle a panel, the window's width decides: below 1100 px the inspector starts hidden, below 800 px the sidebar too, and a window that grows past those widths shows them again. A panel you toggled stays as you left it. The browser keeps that per project, like the sidebar's width. It is not part of the link. Without storage it lasts until you reload.
Theme
The three buttons at the right end of the header set the viewer's own colors.
system, the default, follows your system. light and
dark override it. The choice is the same for every project. The browser keeps
it in localStorage, and it is not part of the link.
Without storage the switch still works until you reload.
Under auto, the default of the
background switch, the theme picks the canvas and the
frame's color scheme for a page that declares no scheme and paints no dark background. The
dark theme gives the dark canvas, with the frame's dark variant, and
system follows your system. Under light, dark and
transparent the theme changes nothing on the stage. The specimens of
Foundations never follow it. So a link without
bg can look different in another theme, and bg=light or
bg=dark fixes the canvas.
Search and collapsing #
The field at the top of the sidebar filters the tests. Every word you type must occur,
case-insensitive, in the test's full name with its suites, in its file path, in its
component's name, in its tags or in the names of its file's groups. Different words may
match different fields, so counter.test reset finds the Counter test that
resets, by its file and its name. A search shows matches inside collapsed groups and
suites too, and leaves out groups without a match. The search is not part of the link and
is not stored.
A test's Vitest tags, its own and those
of its describe blocks and file, show next to its name and in the inspector.
A click on a tag in the inspector narrows the sidebar to the tests with that tag, and a
second click shows every test again, as does the × next to the tag above the tree. The tag
is part of the link, and ↑ ↓ follow it like the
search. docs and no-docs do not show, because they only pick
which tests become stories.
The arrow in front of a file or describe row collapses it, and a click on a
group's row opens or closes the group. The browser keeps the
collapsed rows in localStorage, per project, so several projects on
localhost:6006 do not mix. Without storage, as in a private window that
blocks it, collapsing still works until you reload. Opening a test expands the rows around
it and opens its groups too.
Keyboard #
| Key | Where | What it does |
|---|---|---|
| ← → | a test | The previous or next frame. In Live, they leave Live and show the frame that was selected before, frame 0 for a test that opened in Live. |
| ↑ ↓ | a test | The previous or next test the sidebar shows, so they follow the search and skip collapsed groups and suites. From a test hidden in a collapsed group or suite, they go to the nearest row the sidebar shows. From a test the search does not match, ↓ goes to the first match and ↑ to the last. |
| ↑ ↓ | an overview | ↓ opens its first test, ↑ its last. |
| L | a test | Turns Live off or on, once the live preview is ready. A test opens in Live. The key works in either case, and not with Ctrl, Meta or Alt held. |
| / | anywhere | Focuses the search and selects its text. In Foundations, it focuses the token filter instead when the page has one. Not with Ctrl, Meta or Alt held, which leaves the key to the browser. |
| [ ] | anywhere | Hides or shows the sidebar and the inspector, as the panel toggles do. Not with Ctrl or Meta held, except as part of AltGr, which some keyboard layouts need for a bracket. |
| Esc | the search field | Clears the search and leaves the field. |
| Enter ↓ | the search field | Opens the first match and leaves the field, so the arrow keys work again. |
| ← → | the sidebar's edge | Narrows or widens the sidebar by 16 px. Tab reaches the edge after the sidebar's rows. |
Shortcuts are off while you type in an input, a textarea, a select or an editable element, and until the manifest has loaded. They are also off while the sidebar's edge has focus. While the frame of a test in Live has focus, keys go to the component, as Live explains. In Foundations, only /, [ and ] work, and / focuses the token filter when the page has one.
Viewport size and background #
The toolbar above the stage sets the size of the replayed page in CSS pixels, and what the
frame stands on. The presets are 100%, 768px and
375px. The W and H fields take any width and height.
- Sizes are whole numbers up to 10000. W starts at 1 and H at 120. A smaller H is raised to 120. Anything else, an empty field included, means auto.
- An auto width fills the stage.
100%is the auto width. - An auto height fits what the frame paints, at least 120 pixels, and is measured again once the web fonts have loaded. Content sized to the viewport, such as a full-height layout or a dialog on a backdrop, gets the height of the stage. A dialog taller than the stage gets room for all of it.
-
A viewport wider than the stage is scaled down to fit, and the toolbar shows the zoom,
such as
52%.
Background
Frames have no card. A frame shows only what its page's CSS paints, on the stage's canvas. The swatches after the presets pick the canvas:
-
lightanddarkset the canvas and the frame's color scheme. Underdark,prefers-color-scheme: darkmatches inside the frame, so the component shows its own dark rules. -
Under
auto,lightanddarkthe canvas takes the color the page paints on itshtml, or on itsbodywhenhtmlpaints none, when that color is at least half opaque. So a page that paints its body shows no band at its edges. This holds in replay, in Live and in the overview's thumbnails. -
darkalso puts the dark variant that Foundations found on the frame's root: the.darkclass or the[data-*="dark"]attribute. It does so in replay, in the overview's thumbnails and in Live, and so does anautothat resolves to dark.light,transparentand anautothat resolves to light leave the root as the test recorded it, so a frame recorded in its dark variant stays dark. -
Without tokens, or with
foundations: false,darksets only the color scheme. A theme in JavaScript, such as Material UI withoutcssVariables, wrote its colors into the recorded CSS, so a frame keeps them. -
A replayed frame on the dark canvas that is not dark by itself gets a note under the
stage when its CSS has no dark variant: no dark tokens, no
prefers-color-scheme: darkrule and nolight-dark(). The note says that the frame has only a light variant. With Material UI it adds thatcssVariables: truerecords both. -
transparentis a checkerboard, with a light color scheme. It never takes the page's color. -
auto, the default, takes the scheme the page declares on its root, or dark when the page paints itshtmlorbodydark, and otherwise the viewer theme. Anautothat resolves to dark isdark, color scheme and dark variant included. - A page that sets its own
color-schemekeeps it. -
The overview's thumbnails follow the choice. It is part of the
link as
bgand is not stored. - Foundations has the same switch, at the top of its view.
Neither React example paints its page, so their components stand right on the canvas. Both
have dark tokens under prefers-color-scheme: dark, which the dark canvas
turns on, as in this
Disclosure test
of the jsdom example. The shadcn/ui and Material UI examples paint their page with their
design system's background, and the canvas takes it, so no band shows. Under
dark, both paint their dark one, as in this
Button test of the
shadcn/ui example and this
PlanCard test of the
Material UI example.
Live #
Live mounts the selected test for real, in a frame of the live preview, in place of its recorded frame. When the live preview is ready, a test opens in Live. The component starts as the test's arrange phase set it up: everything before the test's first render runs, and nothing after it. So Live is a fresh start: it does not replay the test's interactions to reach a later frame. Writing stories explains which part of a test runs.
Turning it off and on
- Live is the default once the live preview is ready.
- The Live toggle, the first button of the viewport toolbar, and L in a test switch it, as the keyboard table says.
- ← →, a click on the frame timeline and a thumbnail of the overview show recorded frames.
-
live=0orframein the link opens the recorded frame.live=1, from links of earlier versions, still opens Live.
Try it on the Menu test of the browser-mode example: open Actions and pick Rename.
While it mounts
- Until the live page reports a status, the recorded frame stays under the live frame, dimmed, with a shimmer.
- With no recorded frame of the test on the stage, the shimmer runs on the empty canvas. That happens after Restart of a mounted test, after picking another test in Live, and when a link opens the test in Live before its recorded frame shows.
- The dot of the Live toggle pulses, and the inspector says "mounting…".
- Any status ends it, one that went wrong too. The live frame fades in and the recorded frame fades out.
-
With
prefers-reduced-motion: reducethere is no shimmer and no fade, and the toggle's dot is dim instead of pulsing.
What is real
- Clicks, typing, timers and time are real.
-
Each call of a
vi.fn()prop shows as an action under the prop's name, such asonSelect("Rename"). - Errors the component throws show in the inspector.
The inspector
While Live shows, a Live section takes the place of the frame section:
- A status dot and words, such as "mounted as the test arranged it". Troubleshooting explains each status.
- Restart mounts the test again from a fresh start and clears the lists.
- The detail of a status that went wrong, in red.
-
Actions, the calls of
vi.fn()props, oldest first. It keeps the last 50. - Errors, the last 20 errors of the component.
-
Notices, the last 20 notices of the live page. Each
vi.mockorvi.doMockcall sends one, because the page loads the real module.
The component section shows the props the test rendered with. Once the live preview is ready, the keyboard hint at the bottom adds L live.
Size and focus
- The toolbar's presets, W, H and the background apply without reloading the frame, so the component keeps its state. So does the dark variant: the viewer posts it to the live page, which applies it once the test has rendered.
- The canvas takes the background the live page reports, with its status and after every change, such as a switch to its dark scheme. A live page of an earlier version reports none, and the canvas keeps the switch's color.
-
Under
auto, Live takes the scheme of the last recorded frame the stage showed. When that page declares none and paints no dark background, or the stage showed none, it takes the viewer theme. A theme change applies without reloading the frame. - The auto height follows the height of the content, but stops at the visible height of the stage. Taller content scrolls inside the frame. An explicit H wins.
- Keys you press while the frame has focus go to the component, not to the viewer. Click outside the frame to use the viewer's keys again.
Moving around
- ← and → leave Live and show the frame that was selected before, frame 0 for a test that opened in Live. A click on a frame of the timeline leaves Live and shows that frame.
- ↑ and ↓ keep the mode, and so do a click in the sidebar, Enter in the search and a test in the issues list. In Live they mount the other test. Otherwise they show its recorded frame 0.
- An overview and its gallery never show Live. Back in the test, the same frame shows, with its state. A thumbnail of the gallery opens its recorded frame, not Live.
When the toggle is hidden or greyed out
-
Hidden when the live preview is off: under
--no-live, withlive: falsein the project's config, and on a static site built without--live. - Greyed out while the live preview starts, with the tooltip "The live preview is starting".
-
Greyed out when the live preview did not start. The tooltip then gives the reason, which
the CLI also prints as
describe-me: <reason>. Troubleshooting lists the reasons and their fixes.
The viewer never waits for the live preview, so the recorded frames show while it starts.
Once the preview is ready the test switches to Live. It does not if you stepped through
frames meanwhile, or if the link has live=0 or frame.
Where it runs
The live preview is a second Vite dev server, on your project's own Vite and config. It
loads the project and config of the manifest, or of
--root. describe-me dev starts it on a free port on localhost and serves it
through its own port, under /__describe-me/, so one address reaches the
viewer and Live, also through a forwarded port in a container or a web IDE such as
Codespaces or StackBlitz. It prints the address, such as
http://localhost:6006/__describe-me/. The viewer learns the preview's state
from /__live.json on its own dev server: starting,
ready with the preview's base, __describe-me/,
off, or error with the reason. On a static site built with
--live, the page is a static build under __live/, and
__live.json next to the viewer says ready with that base (Live in the static site).
Links #
The address holds the view, so you can copy it to share a frame at a size:
#test=a7f4d7422f6a&frame=1&w=375
-
testis the test's id,framecounts from 0, andwandhare the viewport size.frame=1is the inspector's "frame 2". In Live the link has neitherframenorlive. On the recorded frame 0 it haslive=0, and on a later frame onlyframe. It leaves out an auto size. -
suitetakes the place oftestfor an overview. -
foundationsopens Foundations at one of its sections:colors,typography,spacing,radius,shadows,breakpoints,motionorother. It takes the place oftest, assuitedoes, and the selected test stays in the link, so going back leads to it. It goes withbg, whosedarkshows the dark variant. An unknown or empty section shows the first one with tokens, and a manifest without tokens drops the parameter unless it names Material UI. -
bgis the background:light,darkortransparent. The link leaves outauto. -
live=0opens the recorded frame 0 instead of Live, so it needstest. live=1, from links of earlier versions, still opens Live.-
tagnarrows the sidebar to the tests with that tag. Withouttest, the first of them opens. Setting or clearing it adds a history entry. - Opening a test or an overview adds a history entry, so Back and Forward move between them. Opening Foundations from the sidebar adds one too, and switching its sections replaces it. Stepping through frames, moving with ↑ ↓, turning Live on or off and changing the size or the background replace the current entry.
- A test id stays the same on other machines and when other tests are added, removed or reordered. Renaming a test or moving it to another suite or file changes it. A link to a test that no longer exists opens the sidebar's first test.
- A link written by 0.4 holds Vitest's own test id. It still opens its test, and the viewer rewrites the address.
That link opens the second frame of a Disclosure test in the jsdom example, 375 pixels wide.
Component overview #
A click on a file or describe row in the sidebar opens its overview. It shows
the props of each component its tests render, which values the tests cover, and the last
frame of every test as a thumbnail. Try the
Button overview
in the browser-mode example.
Writing stories explains how coverage is counted.
A long props table folds as in the inspector and stays unfolded for
that component until you reload the page.
Issues and the version banner #
The header opens with the sygnet and the name of the project, the name of the
nearest package.json at or above the Vitest root, else the root directory's
name. The tab's title carries it too, as <project> · describe-me, so
two viewers tell apart.
The issues chip in the header counts what this run could not document: setup problems, anonymous components, components without props docs, missing assets, fonts that nothing loads and remote stylesheet hosts. It opens a list with links to the tests, with setup warnings first, in a section of their own. When there is nothing to report, the chip is not there. Troubleshooting explains each entry.
The manifest names the describe-me release that wrote it. When its major.minor differs from the viewer's own, a banner under the header says so, for example:
Written by @describe-me/vitest 0.6.0. This viewer is describe-me 0.5.2, so frames may not show correctly. Install every describe-me package at the same version.
The versions in it are those of your packages. Data from 0.5.0 and older names no writer
and never shows the banner. Upgrade describe-me and
@describe-me/vitest together, run the tests again and reload (one version for all packages). How it works explains which versions read
which data.
Hosting the static site #
describe-me build writes plain files: the viewer, its assets and
__data/. Any static file server or host can serve them.
-
Any sub-path. Every URL in the site is relative, the data included, so
it works at
/docs/or under a GitHub Pages project path as well as at the root. -
No rewrites. The view lives in the part after
#, so the server only ever servesindex.htmland files. -
No paths of your machine. In the copy of the manifest,
rootis an id of the project, not a path. Snapshots, test errors and the live build name files relative to the project root, such assrc/Button.tsx, and a package outside the root from itsnode_modules/folder. How it works has the details. Values that your tests put into a frame, such as props and the text they render, stay as they are. The manifest in.describe-me/keeps the absolute root, whichdescribe-me devandbuild --liveread. -
Not from disk. The viewer is a module script that fetches its data,
which browsers do not allow from
file://. Serve the folder, even to look at it locally:
python3 -m http.server 8080 --directory describe-me-dist
Then open http://localhost:8080/. An open page checks the manifest every 60
seconds and repaints only when a new run has been deployed.
Build the docs in CI has a GitHub Actions workflow that runs the tests, builds the site and publishes it to GitHub Pages.
Live in the static site
describe-me build --live puts the live preview next to the viewer, as
__live/ and __live.json. Live then works as it does under
describe-me dev:
-
Same origin. The viewer frames
__live/live.htmlfrom its own origin, and the page posts its messages to that origin only. -
Any sub-path.
__live.jsonnames the base__live/, relative to itself, and the live page uses relative URLs too. -
Only with
__live.json. A site built without--livehas neither file, and the toggle stays hidden. - Your code is in it. The site holds the compiled code of your components, the test files and the setup files with their test data, unminified. Publish it only where that code may be seen.
-
Fonts load from their hosts. The live page links the remote stylesheets
of the
previewHeadas they are, because font vendoring covers the recorded frames only.