ScreenshotNeo

BlogHow-to

How to Capture a Web Page Screenshot after JavaScript Loads in Playwright

Wait for the page content your screenshot needs, then capture the viewport, full page, or a specific element with Playwright.

By the ScreenshotNeo team4 October 20267 min read

To capture a page after JavaScript has rendered the content you need, navigate with Playwright, wait for a page-specific readiness condition, and then call page.screenshot(). Navigation finishing does not necessarily mean an asynchronously populated component, image, or data widget is ready.

import { chromium, expect } from '@playwright/test';

const browser = await chromium.launch();
const page = await browser.newPage();

try {
  await page.goto('https://example.com');
  await expect(page.getByRole('heading', { name: 'Loaded report' })).toBeVisible();
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

Replace the example heading with a locator or signal that proves the particular content your screenshot must show is present. The same approach works for a chart, product image, table row, or application-specific ready flag.

1. Define what “ready” means for the screenshot

There are two separate milestones: the browser completing a navigation event and the application rendering the content you care about. A page can reach load while client-side code is still fetching or rendering a widget. Choose a condition tied to the screenshot’s required content.

In a Playwright Test test, a web-first assertion waits for the locator to meet its condition:

await page.goto('https://example.com/report');
await expect(page.getByTestId('report-chart')).toBeVisible();
await page.screenshot({ path: 'report.png' });

Outside a test runner, wait for an explicit locator state instead:

await page.goto('https://example.com/report');
await page.getByTestId('report-chart').waitFor({ state: 'visible' });
await page.screenshot({ path: 'report.png' });

Use a stable selector such as a test ID or an accessible role and name where the application provides one. If the component is considered ready only after a particular response, wait for that response or an application readiness signal, then capture. The correct condition depends on the application.

2. Choose a navigation wait condition deliberately

page.goto() supports navigation milestones including commit, domcontentloaded, load, and networkidle. These describe browser or network states; none automatically proves that a specific asynchronous component contains the data needed for the image. See the Playwright Page API.

Condition What it tells you When to use it
commit The response has been received and the document has started loading. When you intentionally need to begin interacting early and will wait for content separately.
domcontentloaded The document’s DOMContentLoaded event fired. When DOM parsing is enough to begin locating elements; still wait for app content separately.
load The page load event fired. When load-event resources matter, while remembering that app data may still be pending.
networkidle There have been no network connections for at least 500 ms. Use cautiously; persistent connections and background requests can make it unsuitable.

The Playwright API documentation discourages using networkidle for testing and says to rely on web assertions to assess readiness instead. That guidance is specifically about networkidle. A locator or app signal is generally a clearer statement of what the screenshot needs.

For example, if you want to begin at DOMContentLoaded but still need a rendered chart:

await page.goto('https://example.com/report', { waitUntil: 'domcontentloaded' });
await page.getByTestId('report-chart').waitFor({ state: 'visible' });
await page.screenshot({ path: 'report.png' });

3. Capture the viewport, full page, or one element

Pick the capture scope based on the output you need. Playwright’s screenshot documentation covers page and locator screenshots, and the API parameter reference documents screenshot options.

Target Example Result
Current viewport await page.screenshot({ path: 'page.png' }); The visible browser page; this is the default.
Full scrollable page await page.screenshot({ path: 'page.png', fullPage: true }); The full page as though it fit on a tall screen.
One element await page.locator('.report').screenshot({ path: 'report.png' }); The selected component rather than the whole page.
Bytes in memory const bytes = await page.screenshot(); Screenshot bytes to pass to another tool or process.

Full-page capture can expose content below the fold. For lazy-loaded images, first ensure scrolling or another application-specific action has caused the images you need to load, then verify their readiness before capture. A full-page option sets the image extent; it does not establish that application data or lazy resources have finished loading.

4. Configure screenshot appearance

Playwright offers screenshot options for controlling the captured output. Check the API reference for your installed Playwright version for the complete current option list and types.

  • fullPage: capture the full scrollable page; defaults to false.
  • path: write the image to a file. Without a path, page.screenshot() returns bytes.
  • clip: restrict capture to a specified rectangle when you need a region of the page.
  • caret: control caret appearance in the screenshot.
  • animations: control animations during capture. The documentation describes finite animations being fast-forwarded and infinite animations being canceled to their initial state, then played after the screenshot.

Animation handling changes how the captured frame looks. It does not prove that JavaScript-driven content has loaded. Keep readiness checks and visual capture options as separate decisions.

5. Complete runnable example

This JavaScript example uses the Playwright Test package for its web-first assertion. Save it as capture.mjs in a project where @playwright/test is installed, then run it with Node.js. Change the URL and locator to match the target application.

import { chromium, expect } from '@playwright/test';

const url = 'https://example.com';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

try {
  await page.goto(url, { waitUntil: 'domcontentloaded' });
  await expect(page.getByRole('heading', { name: 'Loaded report' })).toBeVisible();
  await page.screenshot({ path: 'page.png', fullPage: true, animations: 'disabled' });
} finally {
  await browser.close();
}

The sample’s heading is illustrative, not a selector that will match every site. Use a condition that confirms the content visible in your intended image. If your code is not a test and does not use Playwright Test’s expect, use locator.waitFor() or another explicit signal instead.

6. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It takes one GET request with a URL and returns an image or PDF. See the ScreenshotNeo API docs for its 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

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 cost nothing, and response headers say which outcome occurred. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, with no card required.

7. Troubleshooting

Symptom Likely cause Fix
The screenshot misses data even though navigation succeeded. The navigation event completed before the client-side widget rendered. Wait for a locator, response, or explicit application signal that represents the required data.
networkidle never arrives. The page keeps connections open or makes continuing background requests. Wait for the required content directly instead of treating total network quiet as readiness.
A locator wait times out. The selector is wrong, the content is absent, or the component never reaches the expected state. Inspect the page and selector, confirm the data is available, and choose a condition that matches the application’s actual ready state.
A full-page image omits lazy images. The images have not loaded because their lazy-loading trigger has not occurred. Trigger the relevant scroll or application behavior, then wait for the images or another dependable signal before capture.
An animation looks inconsistent between captures. The capture happened at different points in the animation. Disable animations for a stable capture, or wait for the desired animation state before taking the screenshot.
The saved image is only the visible area. fullPage was omitted; its default is false. Set fullPage: true or use a locator screenshot if only a component is needed.

8. Performance, reliability, and cost considerations

A condition that targets the required component can avoid waiting for unrelated activity to stop. It also gives failures a useful meaning: the content required for the screenshot did not reach the expected state within the wait limit. Set timeouts to fit the application and environment, and surface a clear error when the readiness condition is not met.

Close the browser in a finally block so it is released even when navigation, the readiness wait, or capture fails. For repeatable results, use a fixed viewport and decide whether animations, caret, and full-page capture belong in the output.

Playwright is a browser automation workflow that you run in your own environment; account for the browser runtime and infrastructure you operate. If you need a hosted screenshot API instead, ScreenshotNeo offers a free allowance of 1,000 shots per month without a card. Its paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing gives two months free, and every feature is on every plan. These are plan allowances and prices, not a benchmark of capture speed.

9. FAQ

Does load mean every JavaScript component is ready?

No. It is a navigation event, while a component may still be waiting on application data or rendering work. Check the particular content your screenshot needs.

Should I always use waitForTimeout()?

No. A fixed delay only waits for time to pass; it does not establish that a particular component or resource rendered. Prefer an application-specific signal.

Can I capture a screenshot without saving a file?

Yes. Call page.screenshot() without path to get screenshot bytes that can be processed or passed to another tool.

Does disabling animations wait for data?

No. It affects animation appearance during capture, not application readiness. Wait for the required content separately.

Sources