ScreenshotNeo

BlogHow-to

How to Capture Full-Page Screenshots of Dynamic Pages in Playwright

Capture a dynamic page from top to bottom in Playwright. Wait for the right content, handle scroll-triggered loading, and stabilize the result.

By the ScreenshotNeo team4 October 20267 min read

Use await page.screenshot({ path: 'page.png', fullPage: true }) to capture the full scrollable page in Playwright. For dynamic pages, first wait for the specific content the screenshot needs to show. If content loads as the page scrolls, visit those sections and verify their content before capturing. The full-page option sets the capture extent; it does not guarantee that application data or lazy-loaded content is ready.

1. Set up a runnable Playwright script

This JavaScript example opens a page, waits for a meaningful element, and saves a full-page screenshot. Replace the URL, selector, and expected text with stable values for your page.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

  try {
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    // Choose a page-specific signal that means the required content is ready.
    const heading = page.getByRole('heading', { name: 'Expected page heading' });
    await heading.waitFor({ state: 'visible', timeout: 15000 });

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

Install Playwright and its browser in your project with npm install playwright and npx playwright install chromium. Run the script with node screenshot.js. The readiness selector is deliberately an example: use a locator that corresponds to the content your capture must contain.

2. Wait for the page state you need

Navigation completing is not the same as an application finishing its work. A page can render its shell while data, images, or a result list are still loading. Wait for a meaningful heading, result count, image, or state change that indicates the expected content is present.

await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.locator('[data-testid="results"]').waitFor({ state: 'visible' });
await page.locator('[data-testid="results"] .result').first().waitFor({ state: 'visible' });
await page.screenshot({ path: 'results.png', fullPage: true });

Prefer a condition tied to the page’s purpose over an arbitrary delay. A fixed timeout may be useful for a known animation or delayed widget, but it can still be too short on a slow run and waste time on a fast one.

Playwright discourages networkidle as a general testing readiness strategy. Network quiet does not prove that the content your screenshot needs has appeared; pages may also keep connections open or make background requests. Use a web assertion or locator wait for the expected application state instead. See the Playwright Page API.

3. Handle content that loads as you scroll

Some pages load images, cards, or sections only when they approach the viewport. A full-page screenshot asks Playwright to capture the full scrollable extent, but the API does not promise to trigger every site’s lazy-loading behavior. Scroll to the relevant content first, then wait for it to appear or reach the required state.

await page.goto('https://example.com/catalog', { waitUntil: 'domcontentloaded' });

const lastSection = page.locator('#last-section');
await lastSection.scrollIntoViewIfNeeded();
await lastSection.waitFor({ state: 'visible', timeout: 15000 });

// If a known image is required, wait for it to finish loading.
const image = page.locator('#last-section img').first();
await image.waitFor({ state: 'visible' });
await image.evaluate(img => img.decode());

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

For several sections, scroll through them in order and wait for a site-specific signal after each step. Playwright’s locator.scrollIntoViewIfNeeded() brings a locator into view; it does not know what content your application should load. Check the target page’s behavior and selectors. See the Playwright Locator API.

for (const selector of ['#section-one', '#section-two', '#section-three']) {
  const section = page.locator(selector);
  await section.scrollIntoViewIfNeeded();
  await section.waitFor({ state: 'visible' });
  // Add a page-specific wait here if visibility precedes data or image loading.
}

4. Define what complete means for infinite pages

An infinite feed may continue to grow as you scroll, so it has no useful fixed full-page endpoint until your script defines one. Choose a stopping rule based on the task: a known number of items, a terminal marker, a maximum section, or a bounded number of scrolls. This is application-specific; Playwright cannot infer when an endless feed is complete.

const targetCount = 40;
const items = page.locator('.feed-item');

while (await items.count() < targetCount) {
  const before = await items.count();
  await items.last().scrollIntoViewIfNeeded();
  await page.waitForFunction(
    ({ selector, before }) => document.querySelectorAll(selector).length > before,
    { selector: '.feed-item', before },
    { timeout: 10000 }
  );
}

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

For production automation, make the loop bounded and handle the case where the site stops returning items before the target count. A missing terminal condition can otherwise leave the capture waiting indefinitely or produce an unexpectedly large image.

5. Stabilize the captured appearance

For repeatable captures, Playwright supports disabling animations, masking selected locators, and injecting a screenshot-only stylesheet. These controls reduce visual variation; they do not make missing application data load.

await page.screenshot({
  path: 'stable-page.png',
  fullPage: true,
  animations: 'disabled',
  mask: [page.locator('.timestamp'), page.locator('.advertisement')],
  style: '.volatile-widget { visibility: hidden !important; }'
});

Only mask or restyle regions when hiding them suits the screenshot’s purpose. Leave animations enabled when the live animation state is what you intend to capture. Consult the screenshot options for your installed Playwright version, since available options can depend on that version.

6. Options and configuration to consider

Need Playwright approach Consideration
Whole document fullPage: true Captures the full scrollable page extent, not only the viewport.
Stable visual comparisons animations: 'disabled' Reduces animation-driven variation; it does not wait for data.
Hide volatile regions mask locators or screenshot style Use only if those regions should not appear in the result.
Scroll-triggered content scrollIntoViewIfNeeded() and page-specific waits Selectors and loading signals depend on the target site.
Readiness Locator waits or web assertions Wait for the content state the image must show.

For viewport-only screenshots, omit fullPage or set it to false. Set the viewport when layout dimensions matter, and use a stable browser version and page state when comparing captures. Check the documentation for the installed Playwright release before relying on additional screenshot options.

7. Troubleshooting

Symptom Likely cause Fix
Screenshot is blank or shows a loading shell The capture ran before the app rendered its meaningful content. Wait for a page-specific heading, result, or ready state before capture.
Lower sections or images are missing The site loads them only when scrolled into view. Scroll through the relevant sections and wait for the expected content or image decode.
Intermittent timeout waiting for readiness The selector is unstable, the condition is too strict, or the page did not reach the expected state. Use a stable locator, inspect the page state on failure, and set a timeout suited to the workflow.
Capture varies between runs Animations, timestamps, ads, or other changing regions affect the image. Disable animations or mask/style only the volatile areas that may be omitted.
Capture grows without bound An infinite feed has no defined stopping point. Set an item count, terminal marker, bounded scroll count, or other explicit completion rule.
Network-idle wait hangs or gives a misleading result Background requests prevent quiet, or quiet occurs before the needed content is ready. Wait for the actual content with a locator or web assertion.
Browser launch fails in a clean environment The Playwright browser binary may not be installed for the package version. Install the required browser with npx playwright install chromium and use a compatible runtime.

8. Performance, reliability, and cost

Full-page captures can involve much more image area than a viewport capture, and scrolling through lazy sections adds navigation and wait time. Keep the viewport and page scope appropriate to the deliverable. For long or infinite pages, define a boundary before capture so the image size and work remain predictable.

Reliability comes from waiting for the state that matters, checking that required content exists, and handling timeouts explicitly. When a capture is part of a pipeline, record which readiness condition failed so a missing section is distinguishable from a browser launch or navigation problem. Playwright runs in your browser environment; account for the compute and storage used by your own workflow.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. Its full-page capture loads lazy images. One GET request returns an image or PDF; see the API documentation for parameters and response details.

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 require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Does fullPage: true make Playwright scroll the page first?

It requests a screenshot of the full scrollable page. Do not assume that this triggers every site’s scroll-based loading behavior; scroll and verify content when needed.

Should I use a fixed sleep?

Use a content-specific wait when possible. A sleep can be appropriate for a known timed effect, but it does not confirm that the required page content is ready.

Can an infinite feed be captured completely?

Only after your workflow defines a finite completion rule, such as a target item count or terminal marker.

Can I still capture animations?

Yes. Leave animation handling allowed when the current animated appearance is part of the desired image; disable animations when repeatability matters more.