How to Fix BackstopJS Screenshot Clipping on Full-Page Captures
Diagnose clipped BackstopJS full-page screenshots by checking capture mode, viewport-dependent CSS, page readiness, nested scrolling, and engine-specific behavior.
To fix a clipped BackstopJS full-page capture, first confirm the scenario is capturing the intended target: document for the whole document or viewport for only the configured viewport. Then compare both modes at the same viewport size, check viewport-dependent layout such as 100vh, verify the page is truly ready, and determine whether the content lives in a nested scroll container. Try alternate capture paths only when the symptom points to them, and verify behavior in your installed BackstopJS, engine, and browser versions.
BackstopJS documents document as the default selector when none is specified. Full-document output can differ from the ordinary viewport view because page layout or state may behave differently during capture. A reported issue involving a 100vh hero is a reason to investigate that CSS pattern, not evidence that it explains every clipping problem. [BackstopJS README] [BackstopJS issue #820]
1. Confirm the capture target
Check the selector configured in the affected scenario. Use document when the comparison should include the whole page; use viewport when it should include only the configured viewport. If the selector is omitted, BackstopJS defaults to document.
// backstop.json excerpt
{
"scenarios": [
{
"label": "Full document",
"url": "https://example.com/page",
"selectors": ["document"]
},
{
"label": "Viewport only",
"url": "https://example.com/page",
"selectors": ["viewport"]
}
]
}
Adapt this excerpt to the structure of your existing BackstopJS configuration. Capture the same URL with the same viewport dimensions in both modes. If only the document capture looks wrong, the issue is tied to full-page capture behavior or page layout at that capture size. If both look wrong, investigate page state, readiness, viewport configuration, and browser differences before focusing on the full-page path.
2. Compare viewport-dependent layout
Inspect sections whose size depends on the viewport, especially full-screen heroes or panels using height: 100vh or min-height: 100vh. A BackstopJS issue report describes a 100vh hero appearing unexpectedly large in a full-size capture compared with successive viewport captures. That report identifies a possible failure mode; it does not establish a universal root cause.
/* Example pattern to inspect in your own page CSS */
.hero {
min-height: 100vh;
}
Use the comparison to narrow the diagnosis:
- If the hero changes size only in the full-document capture, test whether the page’s viewport-driven layout is sensitive to the capture method.
- If the hero is correct but lower content is missing, inspect document height, overflow rules, and whether the missing region is in a separate scrolling element.
- If the image is clipped at a consistent configured viewport boundary, confirm the scenario did not select
viewportwhen you expecteddocument.
Do not change application CSS solely to compensate for a screenshot artifact until the two capture modes have been compared at matching viewport dimensions.
3. Try the stitched capture path when state is lost
If the full-page operation appears to lose hover state or other scenario state between screen areas, investigate mergeImgHack: true. The BackstopJS Playwright fork describes this option as capturing multiple screen areas and stitching them without rerendering those areas. This documentation is from a fork, and it does not promise to fix every clipping, alignment, or layout defect. Check that the option exists in your installed version and validate the output with your exact engine and browser combination. [BackstopJS Playwright fork]
// Configuration concept; confirm placement and support in your installed version.
{
"engine": "playwright",
"mergeImgHack": true
}
Treat this as a targeted experiment when the symptom involves changing interaction state across a tall capture. It is not a general first step for missing images or a wrong selector. Compare the resulting screenshot with the ordinary capture and keep the same URL, viewport, readiness conditions, and browser version.
4. Wait for actual page readiness
A ready selector only shows that a matching element exists. It does not necessarily prove that the image data, application data, or visual transitions needed for the screenshot have completed. A BackstopJS issue report describes images missing even though a readySelector matched an img element. [BackstopJS issue #610]
Use an application-specific readiness signal where possible. For images, a browser-side check can distinguish a present element from one whose load has finished:
// Browser-side readiness predicate for images on the page.
// Adapt this to the page and to the script hook supported by your BackstopJS version.
() => Array.from(document.images).every(img => img.complete && img.naturalWidth > 0)
This predicate considers all document images; pages with intentionally broken or optional images may need a narrower selector or application-specific condition. Confirm how your installed BackstopJS version accepts readiness scripts before adding one. If an animation or transition is the cause, wait for that specific behavior to finish rather than adding an arbitrary long delay to every scenario.
5. Check nested scrolling and overlays
Find out which element actually scrolls. A page can have a separately scrollable panel while the window remains at the top. Scrolling the window does not necessarily reveal the target content inside that panel, and fixed overlays can obscure it. BackstopJS issue #765 describes fixed overlays hiding content when the window was scrolled instead of the inner container. [BackstopJS issue #765]
BackstopJS documents scrollToSelector to bring a selected element into view. Use it when the target can be identified by a selector, then inspect the screenshot to ensure the correct scrolling context moved. Its documentation does not establish that it handles every custom scrolling container automatically. [BackstopJS README]
// Scenario excerpt: adapt selector and supported options to your installed version.
{
"url": "https://example.com/page",
"scrollToSelector": ".target-section"
}
If the target is still hidden, reproduce the interaction that scrolls the inner container before capture and verify the relevant element is visible. Also check for sticky headers, fixed dialogs, and overlays that cover the content after scrolling.
6. Reproduce engine-specific offset or animation artifacts
For offset or transition artifacts under Puppeteer, one commenter reported success after serializing screenshot calls and passing captureBeyondViewport: false in a patched BackstopJS 5.3.4 CI setup. This is a version-specific report from a patched setup, not a generally supported BackstopJS setting or a guaranteed fix. [BackstopJS issue #766]
If you investigate that lead, first create a small reproducible scenario and record the BackstopJS version, engine, Puppeteer version, browser version, and viewport. Test one change at a time. Do not assume a Puppeteer option can be added to an unmodified BackstopJS configuration unless your installed version exposes a supported way to pass it through.
Diagnostic checklist
- Record the BackstopJS version, engine, browser version, viewport dimensions, and scenario configuration.
- Set the selector explicitly to
documentorviewport, according to the intended result. - Capture both modes at the same viewport and compare the results.
- Inspect
100vhand other viewport-dependent sections if their dimensions change. - Check whether the missing content has loaded, rather than only checking whether its element exists.
- Identify the scrolling element and account for fixed overlays.
- Try the stitched capture option only if the issue involves lost interaction state, and confirm support in your installed version.
- For Puppeteer-specific offsets, reproduce the reported workaround in isolation and treat it as version-dependent.
Common errors and fixes
| Symptom | Likely cause to check | Next step |
|---|---|---|
| Screenshot ends at the viewport boundary | The scenario captures viewport, or the intended target is not configured. |
Set the selector explicitly and compare against document. |
A 100vh section is unexpectedly tall or short |
Viewport-dependent layout behaves differently during full-document capture. | Compare full-document and viewport captures at identical dimensions; inspect the relevant CSS. |
| Images are missing although a ready selector matches | The image element exists but its image data has not loaded, or the app is still rendering. | Wait on actual image completion or an application-specific ready condition. |
| Content in a panel is absent or covered | The panel scrolls independently, or a fixed overlay hides it. | Scroll the correct container, bring the target into view, and check overlays. |
| Hover state or content differs across portions of the tall image | Capture behavior may change page state between screen areas. | Check whether the installed version supports mergeImgHack and validate the stitched result. |
| Screenshot is offset or includes transition artifacts | Engine, browser, or capture timing interaction. | Record versions, reduce the reproduction, and test version-specific leads independently. |
Performance, reliability, and cost considerations
Full-document captures can include much more content than viewport captures, and readiness waits can add time to a scenario. Keep the diagnostic setup focused: use the smallest reproduction that still shows the defect, wait on a condition tied to the page, and avoid broad arbitrary delays. When comparing capture paths, hold URL, viewport, page state, and browser versions constant so the result is interpretable.
The available reports and documentation do not provide controlled benchmark results across BackstopJS versions or engines, nor a general cost figure. Treat issue comments as leads to reproduce in your environment rather than guarantees. Record the exact versions alongside any persistent visual regression so future comparisons use the same capture conditions.
Or skip the browser setup
If you need a clean screenshot without configuring a browser capture pipeline, ScreenshotNeo provides a website screenshot API and MCP server for developers. One GET request returns an image or PDF. See the ScreenshotNeo API documentation for the request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners are accepted and removed before capture; known newsletter popups and chat widgets are removed too, and each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for Claude, Cursor, and other MCP clients. - The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Does a full-page capture always resize the page?
The cited reports do not establish one universal capture mechanism. Compare the configured document and viewport captures and inspect the result in your installed engine and browser.
Will mergeImgHack fix every clipped screenshot?
No. The BackstopJS Playwright fork documents it as an alternate stitched capture path. Verify that your version supports it and use it as a targeted experiment, especially when state changes are involved.
Is readySelector: "img" enough to wait for images?
Not necessarily. It can show that an image element exists without proving that the image data has loaded. Use a condition tied to actual readiness.
What details should I include when reporting a reproducible defect?
Include the BackstopJS version, capture engine, browser version, viewport dimensions, selector, scenario readiness settings, and a minimal page or scenario that demonstrates the difference.


