Detect visual regressions in recordings
A recording’s steps can keep passing while the content quietly drifts. Enable checkpoints to compare each step’s frame against a committed baseline, and add verify guards to assert structural properties of the produced video file. New to recording? Start with Record video.
Before you begin
Make sure you have the following:
- Doc Detective installed.
- A directory where you can create a
.spec.jsonfile. - A headed, visible context to capture a fresh recording. Checkpoints and
verifyguards need a real recording from a visible display. Staleness detection against committed baselines still runs headless. See Detect staleness without re-recording. - A committed baseline folder alongside the recording (the first run seeds it).
The steps in this guide work the same in every authoring style. Write them in a standalone .spec.json, inline in a doc source file, or let Doc Detective detect them from prose.
Detect visual regressions with checkpoints
A recording’s steps can keep passing while the recorded content quietly drifts. A page gets redesigned, a button moves, copy changes. To catch that, enable checkpoints on a record step. Doc Detective then captures a screenshot after every step during the span. It compares each against a committed baseline stored beside the recording, at signup-flow.mp4.checkpoints/ by default or an object-form directory:
On the first run the baselines don’t exist yet, so stopRecord seeds them from the captures. Commit the baseline folder alongside the recording. On every later run, each checkpoint compares against its baseline:
- Within
maxVariation(default0.05): the step passes. - Beyond
maxVariation:stopRecordreturnsWARNING, since the recorded flow’s content has probably changed. A comparison never modifies the baselines.
Tune or relocate the comparison with the object form. Raise maxVariation or disable checkpoints for spans that show dynamic content such as animations, ads, and clocks. Those otherwise produce WARNING noise:
To act on drift instead of only reporting it, set overwrite: "aboveVariation". The span always records, but the new video replaces the existing file only when the span meaningfully changed. That means checkpoint drift beyond maxVariation, an added, removed, or edited step, or a missing recording file. Otherwise Doc Detective discards the fresh capture and leaves the file and baselines untouched, so unchanged recordings stay stable across runs. This mode implies checkpoints. When it refreshes, it updates the baselines with the video in the same step:
Verify the video file itself
Checkpoints validate the scene a recording shows, while verify validates the produced file. Use it to assert structural properties of the video. The classic capture failure modes are a wrong-sized crop, a truncated clip, and an all-black video:
Unlike checkpoint drift, which returns WARNING, a violated verify guard fails the stopRecord step. These are properties you explicitly demand of the artifact:
minDuration/maxDuration: the video’s length in seconds.resolution: the video’s dimensions, with a ±2 pixel tolerance because encoders round to even dimensions.truecompares against the capture plan’s expectation, or pass{ "width": …, "height": … }for literal dimensions.notBlack: fails when black frames cover 95% or more of the video, the symptom of a broken screen capture.
Detect staleness without re-recording
Doc Detective skips the capture in two situations. The first is a headless run, which has no visible display. The second is a target file that already exists under the default overwrite: "false". In both, a span with checkpoints (or aboveVariation, which implies them) still compares its checkpoints against the committed baselines and reports whether the recording has gone stale. stopRecord returns WARNING with stale: true, and a post-run hint points at the fix, which depends on the skip:
- Headless run: re-run the test on a headed context to refresh the recording.
- Target already exists: set
overwriteto"true"or"aboveVariation", or remove the file.
Detection is cheap and continuous, so headless CI watches recording freshness on every run and you only re-record when something actually changed. Skipped spans never write: no video, no baseline seeding, no baseline updates.
stopRecord reports the outcome of all of this in its outputs: $$checkpoints and $$maxCheckpointVariation, $$seededBaselines, $$changed and $$changeReasons, $$stale, $$duration / $$width / $$height, and $$resolutionMatch / $$allBlack. For the full list, see Recording checkpoints, the stopRecord action reference, and the stopRecord schema.
Verify it works
- Checkpoint drift beyond
maxVariationreportsWARNING, so the run continues while flagging that the recorded content probably changed. - A violated
verifyguard reportsFAIL, because you demanded that structural property of the file. - A skipped capture whose baselines diverged reports
WARNINGwithstale: true.
Troubleshooting
stopRecord returns WARNING with stale: true
Cause: the capture was skipped, from a headless run or an existing file, while the committed baselines diverged from the current content.
Solution: re-run on a headed context to refresh the recording, or set overwrite to "true" or "aboveVariation".
A record step is skipped and the file isn’t refreshed
Cause: the default overwrite: "false" skips a record step whose file already exists.
Solution: set overwrite to "true" to always re-record, or "aboveVariation" to re-record only when the span meaningfully changed.
What’s next
- Record video to record a flow, stop it, and choose an engine.
- Record video automatically for every driver-based context.
- Narrate a recording with annotations that stay up across the flow.
- Follow the worked recording example end to end.
- Look up every field in the
recordandstopRecordaction references. - Docker and headless for running captures without a physical display.
- Reporters and artifacts to see where videos and results land.