ScreenshotNeo

BlogHow-to

How to Capture Screenshots of Pages That Require JavaScript to Load

Use a JavaScript-capable browser, wait for the content your screenshot needs, then capture the viewport, full page, or a specific element.

By the ScreenshotNeo team4 October 20267 min read

Short answer: use a browser that executes JavaScript, such as Playwright or Chrome Headless. Navigate to the page, wait for the specific content you need to appear, and capture the viewport, full scrollable page, or a selected element. A static HTTP fetch returns source HTML; it does not reproduce the browser-rendered page.

The key decision is usually the wait condition. A navigation event can finish before client-side data or a late-loading component is ready. Waiting for a meaningful element is more dependable than choosing an arbitrary delay, though no wait can guarantee that every site-specific resource has finished loading.

1. Choose what the screenshot should include

  • Viewport: the visible browser area at a chosen viewport size. Use it for a first-screen capture or a fixed layout check.
  • Full page: the document’s scrollable page. Use it for a page overview. Lazy-loaded content may need scrolling or additional site-specific handling before capture.
  • Element: one component, located by a selector. Use it for a chart, card, or other specific region.

Playwright supports viewport and full-page screenshots, as well as screenshots of a locator. These scopes are not interchangeable: decide whether the output should show the current screen, the whole document, or a particular component before automating it.

2. Capture a JavaScript page with Playwright in Node.js

This runnable example opens a page, waits for a page-specific element, and saves a full-page PNG. Replace the URL and selector with the page and content that matter for your capture.

import { chromium } from 'playwright';

const url = 'https://example.com/app';
const readySelector = '[data-testid="results"]';

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({
    viewport: { width: 1440, height: 1000 },
    deviceScaleFactor: 1,
  });

  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
  await page.locator(readySelector).waitFor({
    state: 'visible',
    timeout: 15_000,
  });

  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

Install Playwright and its browser in your project before running the script: npm install playwright and npx playwright install chromium. Save the example as an ES module, for example capture.mjs, and run node capture.mjs.

Use a viewport or capture one element

For the visible viewport, omit fullPage or set it to false. To capture one component, wait for and screenshot its locator:

const chart = page.locator('#sales-chart');
await chart.waitFor({ state: 'visible', timeout: 15_000 });
await chart.screenshot({ path: 'chart.png' });

A locator screenshot captures the element rather than the entire page. Make the selector specific enough to avoid matching a hidden duplicate or the wrong component.

3. Choose a wait condition that matches the page

DOMContentLoaded means the initial document has been parsed; it does not mean an application has finished fetching data or rendering every component. The Playwright example waits for a visible, application-specific selector after navigation. Choose a selector that appears only when the content you need is ready.

When there is no reliable selector, use a known page state or a bounded delay as a fallback. A delay is only a pause: it does not confirm that the required content appeared. Avoid treating a general network-idle condition as proof that every application is ready; some pages keep background requests open, and others render content after their requests finish.

// Fallback when the page has a known, bounded animation or rendering delay:
await page.waitForTimeout(1500);
await page.screenshot({ path: 'page.png' });

Use Playwright’s load-state waiting when a particular document lifecycle state is relevant. Its documentation notes that many actions already wait automatically, so add an explicit wait when it expresses a real requirement for your page rather than stacking waits without a reason.

4. Capture from the command line with Chrome Headless

For a quick one-off viewport screenshot, Chrome Headless can run without a visible browser window. Set a viewport size and a maximum wait before capture:

chrome --headless --window-size=412,892 --timeout=10000 \
  --screenshot=page.png https://example.com/app

Use the Chrome executable name installed on your system if it differs. The --timeout value is in milliseconds and sets a maximum wait before capture even if the page is still loading. It is a time limit, not evidence that a particular widget or data request completed. The CLI example does not provide the same page-specific locator wait as the Playwright workflow.

5. Check the result and make captures repeatable

  1. Open the saved image and confirm the required text, images, and layout are present.
  2. If the image is blank or incomplete, inspect the page in a browser and confirm that the selector or state you wait for actually appears.
  3. For comparisons across runs, keep the browser version, operating system, viewport, scale factor, and relevant settings consistent.
  4. Use a deliberate output format and path, and keep viewport screenshots distinct from full-page captures in your workflow.

Rendering can vary with operating system, browser version, settings, hardware, power source, and headless mode. Consistent settings improve comparisons, but they do not make every website render identically in all environments.

6. Troubleshooting

Symptom Likely cause Fix
The screenshot shows a loading shell or missing content. Navigation completed before client-rendered data or the target component appeared. Wait for a visible selector tied to the content needed, then capture. Check the selector in the live page.
The selector wait times out. The selector is wrong, the element is hidden, the page failed to reach the expected state, or rendering took longer than the timeout. Inspect the page and selector, verify the URL and application state, and adjust the timeout only when slower rendering is expected.
Chrome captures an incomplete page despite --timeout. The timeout is only a maximum wait; it does not detect application-specific readiness. Use Playwright with a meaningful locator wait when the capture depends on a particular component.
A full-page image omits content farther down. Content may load lazily as it approaches the viewport, or the site may render it only after interaction. Scroll through the page or trigger the site’s required state before capture, then inspect the result. Full-page capture alone does not guarantee every lazy resource is loaded.
The same page looks different across runs or machines. Browser, operating system, settings, hardware, or headless rendering can affect appearance. Keep the execution environment and viewport consistent, and treat small rendering differences as possible environment variation.
The script cannot launch Chromium. The Playwright browser may not have been installed in the environment. Install the project dependency and Chromium with npm install playwright and npx playwright install chromium.

7. Performance, reliability, and cost

Capture time depends on navigation, the page’s own scripts and requests, the wait condition, and screenshot scope. Waiting only for the content you need can avoid unnecessary delay; a short fixed timeout may return too early, while an overly long timeout holds the job open without proving readiness.

For repeat captures, reuse a consistent browser setup and record the viewport and environment. Add bounded timeouts so a stalled page does not wait indefinitely, and inspect or classify failed captures instead of silently treating them as valid images. The browser workflow runs on the machine or service where you execute it; its infrastructure and browser-operation costs depend on that environment. The research sources do not establish universal performance figures or prices.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request captures a JavaScript-rendered page, so you do not have to install and operate a browser for this request. See the API documentation for parameters and 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
  • The Free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Can I capture a JavaScript page with an HTTP client alone?

A static HTTP fetch does not execute the page’s scripts, so it cannot by itself reproduce the browser-rendered view. Use a JavaScript-capable browser or a screenshot service that renders pages.

Is waiting for load always enough?

No. Client-side data and components can appear after a document load event. Wait for the application state or element the screenshot needs.

Should I use a viewport, full-page, or element screenshot?

Use viewport for the visible screen, full-page for the scrollable document, and element capture for one component. Check the output for lazy content that may need additional page interaction.

Does a longer timeout guarantee a complete screenshot?

No. A timeout controls how long to wait, not whether a particular component rendered. Prefer a meaningful state condition when one is available.