Skip to navigation

screenshot

The screenshot action captures a PNG image of the current browser viewport or a specific element. It can also perform visual regression testing by comparing the captured image against a previously saved reference image. Reference images can be local files or remote URLs, enabling teams to store reference images in cloud storage like S3.

You can specify the screenshot action in several ways:

  • Boolean Shorthand: Set screenshot: true to capture the full viewport with default settings.
  • String Shorthand: Provide a file path directly as the value for the screenshot key (for example, screenshot: "my_screenshot.png"). This saves the full viewport screenshot to the specified path.
  • Object Format: Use an object for more control over the path, visual comparison, and cropping:
    • path: (Optional) The file path to save the screenshot to, or an http(s):// URL to a remote PNG image. It can include directories. Omitting it generates a default path. Given a URL, Doc Detective downloads the remote image and uses it as a read-only reference for comparison. It writes the new capture to ./doc-detective-runs/<run-timestamp>/ instead of overwriting the remote image.
    • directory: (Optional) The directory where the screenshot will be saved. If path includes a directory, this is ignored. Defaults to the configured output directory.
    • maxVariation: (Optional) The maximum acceptable visual difference (0 to 1) when comparing against a reference image. If the variation exceeds this threshold, the step returns WARNING. If set, visual comparison is enabled.
    • overwrite: (Optional) Controls when to overwrite the reference image. Ignored when path is a URL (remote storage is never mutated). Options:
      • "false": Never overwrite the reference image.
      • "true": Always overwrite the reference image with the new capture.
      • "aboveVariation": Overwrite only if the visual difference exceeds maxVariation (default).
    • crop: (Optional) Crop the screenshot to a specific element. Provide a CSS/XPath selector string (for example, "#myElement") or an object:
      • selector: (Required in object) The CSS selector or XPath selector of the element to crop to.
      • elementText: (Optional) Display text to further specify the element if the selector isn’t unique.
      • padding: (Optional) Add padding around the element before cropping. Can be a single number (all sides) or an object { top, right, bottom, left }.
    • surface: (Optional) The browser session, window, or tab to capture, such as { "browser": "chrome", "tab": "cart" }. It can also be the native app surface to capture, such as { "app": "notepad" } or a specific app window { "app": "notepad", "window": -1 }. On macOS the capture is that window element, and on Windows the switched window. Omit it to capture the active surface, the most recently opened, focused, or explicitly targeted one, whether a browser tab or an app window. Specifying a surface switches the active surface for the steps that follow. See Test across multiple tabs, windows, and browsers, Target an app surface, and App windows.
    • annotations: (Optional) A list of visual annotations to draw onto the screenshot. See Annotations.

Annotations

Annotations mark up a screenshot as part of capturing it, so the callouts regenerate with the image instead of drifting from it. Each annotation names exactly one type, and the type’s value is what it points at:

{
"screenshot": {
"path": "login.png",
"annotations": [
{ "outline": "#username" },
{ "badge": "#password", "label": "2" },
{ "blur": ".account-id" }
]
}
}

Doc Detective composites annotations into the image instead of injecting them into the page. It never modifies the page under test. An annotated screenshot therefore can’t disturb a test or leak into a concurrent recording.

Types

TypeDraws
outlineA box around the target.
arrowAn arrow pointing at the target.
badgeA small marker on the target, showing label. Use for numbered steps.
calloutA text box with a leader line, showing label.
blurA blur over the target, to redact sensitive information.
textA standalone text box, showing label.

Targets

A target can be:

  • A string, a selector or display text, the same as crop accepts. For example, { "outline": "#submit" }.
  • An object, using the same element-finding fields as find. For example, { "outline": { "elementTestId": "submit" } }.
  • A position, a fixed spot for annotations that aren’t tied to an element. For example, { "text": { "position": "top-right" }, "label": "Demo data" }. Accepts a named region (top, bottom, left, right, center, top-left, top-right, bottom-left, bottom-right) or a point ({ "x": 640, "y": 220 }).

An element target waits up to 5000 milliseconds for its element to appear. Set timeout on the target to wait longer:

{ "outline": { "elementTestId": "dashboard", "timeout": 20000 } }

timeout works the same way as find’s. Like find’s, it needs the object form of the target, because the string shorthand has nowhere to put it.

Options

  • label: Text to display. Used by badge, callout, and text.
  • position: Where to place the annotation relative to its target. Accepts a named region, a point, or an offset ({ "offset": { "x": -80, "y": -60 } }).
  • style: Visual overrides for this annotation: color, background, strokeWidth, fontSize, fontFamily, opacity, radius, padding, maxWidth, and intensity (blur strength).
  • all: Annotate every matching element instead of only the first. Needs an object target, because a string could be a selector or display text.

Use all for redaction. { "blur": { "selector": ".secret" } } blurs only the first match. If the page shows several, the rest stay readable in an image that looks redacted.

Annotations resolve after crop, so you can crop to a panel and annotate elements inside it. An annotation whose target doesn’t resolve fails the step rather than quietly producing an unannotated image.

App surfaces

Annotations work on native app surfaces too, including element-anchored ones. Native surfaces match elements through the platform’s accessibility layer rather than the DOM, so only four fields have a native equivalent: elementText, elementId, elementTestId, and elementAria. selector, elementClass, and elementAttribute are browser-only, and a bare string target isn’t accepted on an app surface. Name the field explicitly instead:

{
"screenshot": {
"path": "settings.png",
"surface": { "app": "notepad" },
"annotations": [
{ "outline": { "elementText": "Save" } },
{ "text": { "position": "top-left" }, "label": "Step 1" }
]
}
}

Position-anchored annotations need no element lookup, so they work on every surface.

all isn’t supported on app surfaces yet, because the native element lookup resolves a single match. Rather than annotate the first match and leave the rest, the step fails. Give each element its own annotation instead.

Themes

Set annotationDefaults to style every annotation at once, so images across a doc set stay consistent. Set it on the config, a spec, or a test. A test’s theme overrides a spec’s, which overrides the config’s, and an individual annotation’s style overrides all of them.

{
"annotationDefaults": {
"color": "#E11D48",
"strokeWidth": 3,
"fontFamily": "Inter, system-ui, sans-serif",
"badge": { "background": "#E11D48", "color": "#FFFFFF" },
"blur": { "intensity": 14 }
}
}

Base properties apply to every type. A per-type block, such as badge or callout, overrides them for that type.

Annotations change the image’s pixels. If you compare against a reference image with maxVariation, changing a theme or an annotation invalidates existing reference images.

Visual Regression Testing: If maxVariation is set and a reference image exists at the target path, the action compares the new screenshot to the reference. If the difference is within maxVariation, the step passes. If it exceeds the threshold, the step returns WARNING, and behavior depends on the overwrite setting.

URL Paths: When path is an http(s):// URL pointing to a PNG image, Doc Detective downloads that image once per run. It uses the image as a read-only reference for comparison. The new screenshot is saved to ./doc-detective-runs/<run-timestamp>/<stepId>_<filename>.png for inspection. The overwrite setting is ignored, because remote storage is never mutated. The step result includes outputs.referenceUrl for traceability. This suits teams that store reference images in cloud storage like S3 without managing local files.

URL paths must end in .png (or .PNG) and can include query strings for signed URLs. The step fails if the URL is unreachable or returns invalid image data.

Private addresses: remote reference URLs must resolve to a public host. Doc Detective won’t fetch reference images from private, loopback, or link-local addresses, including their IPv4-mapped IPv6 forms. This guards against server-side request forgery (SSRF). This covers hosts like localhost and 127.0.0.1. It also covers ranges such as 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16, plus the 169.254.169.254 cloud-metadata endpoint. A blocked URL fails the step with a message that starts with Couldn't fetch remote reference image. The message notes it’s Refusing to fetch private/loopback address. To compare against a reference image on an internal address, such as a local development server, set DOC_DETECTIVE_ALLOW_LOCAL_URLS=true.

Outputs: depending on which options you configure, the action exposes the following values in its outputs object. To capture any of them for later steps, use the step-level variables object with the matching expression. Each output is present only when its check applies:

  • $$width and $$height: the saved image’s actual pixel dimensions, after any crop. When the browser floors a requested viewport, these report the size the page rendered rather than the size you asked for. You can then reconcile a caption or report against the file. They’re present when Doc Detective can read the saved image’s dimensions. They’re omitted when the metadata read fails or the image has no readable size.
  • $$cropElementFound: whether the element to crop to was located (when crop is set).
  • $$fitsViewport: whether the cropped element fits within the viewport (when crop is set).
  • $$annotationTargetsFound: whether every annotation’s target resolved (when annotations is set). The step fails if any target is missing.
  • $$annotationsInBounds: whether every annotation landed inside the image (when annotations is set). The step returns WARNING if one falls outside, which usually means the crop left it out of frame.
  • $$aspectRatioMatch: whether the new capture and the reference image share an aspect ratio (when comparing against a reference image).
  • $$variation: the fractional difference between the new capture and the reference image (when comparing against a reference image).

For comprehensive options, see the screenshot reference.

Examples

Here are a few ways you might use the screenshot action:

Basic full viewport screenshot (boolean shorthand)

Saves to a default path like output/screenshot_1745032062266.png.

{
"tests": [
{
"steps": [
{
"description": "Take a basic screenshot.",
"screenshot": true
}
]
}
]
}

Full viewport screenshot to specific path (string shorthand)

{
"tests": [
{
"steps": [
{
"description": "Save screenshot to a specific file.",
"screenshot": "./output/images/login_page.png"
}
]
}
]
}

Full viewport screenshot with visual comparison (object format)

Compares against ./output/reference_images/home_page.png. Fails if difference > 5% (0.05). Overwrites reference if failure occurs.

{
"tests": [
{
"steps": [
{
"description": "Take screenshot and compare to reference.",
"screenshot": {
"path": "home_page.png",
"directory": "./output/reference_images",
"maxVariation": 0.05,
"overwrite": "aboveVariation"
}
}
]
}
]
}

Visual comparison against a remote URL

Compares against a reference image stored in S3 or other cloud storage. The new capture is saved locally under ./doc-detective-runs/<run-timestamp>/ for inspection.

{
"tests": [
{
"steps": [
{
"description": "Compare against reference image in S3.",
"screenshot": {
"path": "https://my-bucket.s3.amazonaws.com/reference-images/home_page.png",
"maxVariation": 0.05
}
}
]
}
]
}

Screenshot cropped to an element (object format)

Captures only the element matching the #main-content selector.

{
"tests": [
{
"steps": [
{
"description": "Screenshot only the main content area.",
"screenshot": {
"path": "main_content_area.png",
"directory": "./output/element_shots",
"crop": "#main-content"
}
}
]
}
]
}

Screenshot cropped to an element with padding and visual comparison

Captures the element with text “Submit Button”, adds 10px padding, and compares against a reference image.

{
"tests": [
{
"steps": [
{
"description": "Screenshot a button with padding and compare.",
"screenshot": {
"path": "submit_button_padded.png",
"directory": "./output/reference_elements",
"crop": {
"elementText": "Submit Button",
"padding": 10
},
"maxVariation": 0.01,
"overwrite": "aboveVariation"
}
}
]
}
]
}

Annotated screenshot

Numbers the fields of a form and redacts an email address, all as part of the capture.

{
"tests": [
{
"steps": [
{
"description": "Capture an annotated screenshot of the sign-in form.",
"screenshot": {
"path": "sign_in_annotated.png",
"directory": "./output/images",
"annotations": [
{ "badge": "#username", "label": "1" },
{ "badge": "#password", "label": "2" },
{
"callout": "#submit",
"label": "Sends you a one-time code",
"position": "right"
},
{ "blur": { "selector": ".account-email" }, "all": true }
]
}
}
]
}
]
}

Automatic screenshots

To capture a screenshot after every browser-based step, use the autoScreenshot option. This is useful for:

  • Building a visual record of test runs
  • Detecting UI regressions by comparing screenshots between runs
  • Debugging failures by inspecting the state at each step

Enable at the config, spec, or test level (test overrides spec, which overrides config):

{
"autoScreenshot": true,
"output": "./test-results"
}

Or use the CLI flag:

doc-detective --auto-screenshot

Doc Detective saves auto screenshots under <output>/.doc-detective/runs/<runId>/, nested in the REST-style resource tree. The path structure uses stable IDs derived from file paths and content hashes:

.doc-detective/runs/<runId>/ # <runId> is an ISO timestamp, for example 2026-06-27T04-55-00-000Z
├── testResults.json # or coverageResults.json for coverage runs
├── testResults.html # or coverageResults.html for coverage runs
└── specs/
└── <specId>/
└── tests/
└── <testId>/
└── contexts/
└── <contextId>/
└── screenshots/
└── 01-goTo-s4f2a91c.png

The HTML report provides a human-readable view of the same results. Run folders therefore become self-contained artifacts you can share or compare without the standalone html reporter.

Doc Detective generates deterministic IDs based on content rather than random UUIDs, so the same test step produces the same relative path in every run. This lets you diff two run folders and spot UI changes over time.

Auto screenshot capture skips steps that already define an explicit screenshot, since those steps already produce an image. Doc Detective still captures failed steps, because the failure state is often the most useful frame for debugging.