Chrome Headless Screenshot Crops the Right Side of a Wide Web Page
Diagnose missing right edges in Chrome headless screenshots by checking viewport width, clip bounds, full-page settings, pixel scale, and page overflow.
If a Chrome headless screenshot is missing the right side of a wide page, first check that the requested capture width reaches the page’s intended right edge. Set Chrome’s window size explicitly, then check your automation library’s viewport and any screenshot clip rectangle. Also compare CSS-pixel dimensions with output-image pixels and inspect the page for horizontal overflow. fullPage captures the full scrollable page vertically; it does not guarantee that a narrow capture includes a wide layout.
These checks narrow down the likely cause, but without your command, page, and browser versions, no single cause can be assumed. The examples below show how to inspect and correct the capture geometry.
1. Check the Chrome window and viewport width
For Chrome’s command-line screenshot mode, pass an explicit --window-size=WIDTH,HEIGHT. Chrome’s documented example pairs --screenshot with --window-size=412,892. Replace those dimensions with the viewport you intend to capture.
chrome --headless --screenshot --window-size=1440,1000 https://example.com
Use the executable name available on your system. Some installations expose it as google-chrome or chromium. If your command also specifies a screenshot output path, keep that option and add the explicit window size.
The window size sets the browser’s capture viewport; it does not repair a page whose content overflows that viewport. After capture, check both the image dimensions and the rendered page’s scroll width.
2. Check viewport and clip settings in automation
In Puppeteer and Playwright, the screenshot may use the configured viewport, a clip rectangle, or full-page capture. A clip captures the rectangle defined by its x, y, width, and height. Make sure x + width reaches the intended right boundary.
Puppeteer: set the viewport and inspect page width
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const dimensions = await page.evaluate(() => ({
viewportWidth: document.documentElement.clientWidth,
scrollWidth: document.documentElement.scrollWidth,
bodyScrollWidth: document.body.scrollWidth,
}));
console.log(dimensions);
await page.screenshot({ path: 'page.png' });
await browser.close();
For a viewport screenshot, leave fullPage off. Puppeteer’s fullPage option defaults to false. To capture the full scrollable page, set it to true:
await page.screenshot({ path: 'full-page.png', fullPage: true });
For a clipped capture, check that the rectangle is wide and positioned as intended. If the clipped area extends beyond the viewport, check captureBeyondViewport as well. Puppeteer documents its default as depending on whether a clip is supplied: false without a clip, true with one.
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 1440, height: 1000 },
captureBeyondViewport: true,
});
Playwright: set the viewport and compare scale
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1,
});
await page.goto('https://example.com', { waitUntil: 'networkidle' });
const dimensions = await page.evaluate(() => ({
viewportWidth: document.documentElement.clientWidth,
scrollWidth: document.documentElement.scrollWidth,
bodyScrollWidth: document.body.scrollWidth,
}));
console.log(dimensions);
await page.screenshot({ path: 'page.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
await browser.close();
Playwright describes fullPage: true as capturing the full scrollable page instead of only the visible viewport. It also supports clip coordinates. A clip that stops before the right edge will crop it regardless of the page’s total scroll width.
await page.screenshot({
path: 'region.png',
clip: { x: 0, y: 0, width: 1440, height: 1000 },
});
3. Distinguish CSS pixels from output pixels
A screenshot’s pixel width can differ from the CSS viewport width. Playwright’s screenshot scale can use one image pixel per CSS pixel (css) or output at device-pixel resolution (device). With device scaling on a high-DPI context, the image can contain more pixels than the CSS viewport. That difference can make an image look unexpectedly sized, so check the saved image’s actual dimensions before diagnosing a crop.
// Playwright: output one image pixel per CSS pixel
await page.screenshot({ path: 'css-scale.png', scale: 'css' });
// Playwright: output at device-pixel resolution
await page.screenshot({ path: 'device-scale.png', scale: 'device' });
For a controlled comparison, set deviceScaleFactor: 1 in the browser context, capture once with the default scale, and compare the image’s pixel width with the configured viewport width. Then vary one setting at a time.
4. Inspect horizontal overflow and page layout
If the viewport and clip cover the intended range, measure the page’s scrollable width:
const sizes = await page.evaluate(() => ({
viewport: document.documentElement.clientWidth,
document: document.documentElement.scrollWidth,
body: document.body.scrollWidth,
}));
console.log(sizes);
If scrollWidth is greater than clientWidth, the page has horizontal overflow. The overflow may be intentional, such as a wide table, or caused by a layout element that extends beyond the viewport. Compare the ordinary viewport screenshot with a full-page screenshot and inspect the page’s layout at the configured width. A full-page option concerns scrollable capture; confirm the horizontal bounds separately.
For a wide page, choose the behavior you actually need: set a wider viewport to show more content at once, capture a particular region with a correctly sized clip, or keep the viewport and fix the page’s overflowing layout. A screenshot cannot display content outside its captured rectangle.
5. Check browser and library versions
If the viewport, clip, and scale look correct, reproduce the capture with the same Chrome or Chromium and Puppeteer or Playwright versions. Record versions before and after an upgrade that coincides with the crop.
A Puppeteer issue reports a particular interaction between captureBeyondViewport, deviceScaleFactor, and Chromium compatibility. It is a version-specific report, not evidence that this explains every right-edge crop. Use it as a reason to check the version combination when the geometry settings appear sound. Puppeteer issue #11514
6. Troubleshooting checklist
| Symptom | Likely check | Fix or next step |
|---|---|---|
| Chrome CLI image is narrower than expected | Missing or undersized --window-size |
Set --window-size=WIDTH,HEIGHT explicitly and inspect the saved image dimensions. |
| Only a specific region is missing | Screenshot clip bounds |
Check x and width; ensure x + width reaches the intended edge. |
| Full-page shot still omits horizontal content | Assuming fullPage widens the capture |
Check horizontal viewport and clip bounds independently. Full-page means the full scrollable page, primarily extending capture vertically. |
| Image dimensions do not match CSS width | deviceScaleFactor or screenshot scale |
Compare CSS viewport pixels with image pixels; test with device scale factor 1 or Playwright scale: 'css'. |
| Page’s right edge is outside the viewport | Horizontal layout overflow | Compare scrollWidth with clientWidth; decide whether to widen the viewport, capture a region, or fix the layout. |
| Crop began after a browser or package update | Version-specific capture behavior | Record browser and library versions, reproduce with the prior combination if available, and isolate scale and beyond-viewport settings. |
| Result remains ambiguous | Insufficient reproduction details | Collect the URL or a minimal page, exact versions, launch flags, viewport, device scale factor, screenshot options, output dimensions, and measured scroll width. |
7. Performance, reliability, and cost considerations
Increasing viewport width can require rendering more content at once, and full-page capture can involve more page area than a viewport shot. Use the smallest width and capture region that contain the content you need. Wait for a page state that is stable enough for your use case; if the page is still loading or moving, geometry comparisons may not be repeatable.
For reliable diagnosis, log the viewport, clip, scale, browser and library versions, and measured page widths alongside each capture. Save the exact screenshot options with the output so a later run can be compared. Avoid changing viewport width, device scale, full-page mode, and clip bounds all at once; one-variable comparisons make the cause easier to isolate.
Chrome CLI, Puppeteer, and Playwright run on your own browser infrastructure, so their direct costs depend on the machines and workloads you operate. A hosted screenshot API can remove browser setup, while its own plan and billing rules determine service cost. ScreenshotNeo offers a free plan with 1,000 shots a month and no card; paid plans start at $5 for 3,000. Only clean shots are billed, and responses identify the page verdict and billing status in headers. See ScreenshotNeo API documentation for request options.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Send one GET request with the page URL to receive an image or PDF. Here is a cURL example; replace the target URL and API key with your own values.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
The same endpoint works from Python and Node.js:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. All plans include every feature. See the API docs for the available capture options.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
Frequently asked questions
Does fullPage: true fix a missing right edge?
Not necessarily. It requests the full scrollable page, while the viewport, clip, horizontal overflow, and output scale still affect which pixels appear.
Why is the screenshot pixel width different from my viewport width?
Device-pixel scaling can produce more image pixels per CSS pixel. Check the screenshot scale and device scale factor before treating a size difference as missing content.
What details should I include in a bug report?
Include the browser and automation-library versions, launch arguments, viewport, device scale factor, screenshot options, image dimensions, and measured page scroll width. A minimal reproducible page or URL helps distinguish configuration from page layout.


