ScreenshotNeo

BlogHow-to

How to capture a webpage screenshot after a specific CSS selector appears

Wait for a CSS selector to reach the right state, then capture a full page or just that element with Playwright or Puppeteer.

By the ScreenshotNeo team4 October 202610 min read

To capture a webpage after a CSS selector appears, wait for that selector to reach the state your screenshot needs, then take a page or element screenshot. In Playwright, use a locator; in Puppeteer, use page.waitForSelector(). Use visible when the element must be displayed, and set a finite timeout so a missing selector fails clearly instead of producing a misleading image.

1. Choose the selector state and screenshot scope

First decide what “appears” means for this capture. An element can exist in the DOM before it is visible or before its content is ready.

Need Playwright state Puppeteer option
Element is in the DOM, whether displayed or not attached Default wait, with visible: false
Element is displayed visible visible: true
Element becomes hidden or is removed hidden or detached Wait for selector with hidden: true

For a screenshot intended to show the element, visibility is usually the useful condition. In Playwright, visible means the element has a non-empty bounding box and is not visibility:hidden. In Puppeteer, the documented visible check excludes display:none and visibility:hidden. Visibility does not prove that data inside the element has finished rendering; if the image depends on specific text or values, wait for that content too.

Choose the capture scope separately:

  • Full page: use the page screenshot API and enable full-page capture. This captures the page’s scrollable content.
  • One element: use the matched locator or element handle screenshot. This produces a clipped image of that element.

An element screenshot scrolls the target into view when needed. If it is inside a scrollable container, only the content currently scrolled into view is included. Another element covering the target can also affect what appears in the resulting image.

2. Playwright: wait for a locator, then capture

Install Playwright and its browser binaries using the instructions for your project in the official Playwright installation guide. This complete Node.js example launches Chromium, navigates to a page, waits for a visible selector, saves a full-page screenshot, and closes the browser even if navigation, waiting, or capture fails:

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

(async () => {
  const url = 'https://example.com';
  const selector = '.ready';
  const browser = await chromium.launch();

  try {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'domcontentloaded' });

    const target = page.locator(selector);
    await target.waitFor({ state: 'visible', timeout: 10_000 });

    await page.screenshot({ path: 'page.png', fullPage: true });
    // To capture just the matched element instead, use:
    // await target.screenshot({ path: 'element.png', animations: 'disabled' });
  } catch (error) {
    throw new Error(`Screenshot failed for ${url} after waiting for ${selector}: ${error.message}`, { cause: error });
  } finally {
    await browser.close();
  }
})();

The 10-second timeout is an example, not a universal recommendation. Choose a bound that fits the page and your capture job. Locator waits default to a zero timeout unless configured, so set a timeout or configure one at the page or context level. A locator is resolved against the current DOM when an operation runs, which helps with pages that re-render elements.

locator.waitFor() defaults to visible. It also accepts attached, hidden, and detached. It returns when the requested condition is already true or becomes true; otherwise, it times out. For a selector that should exist but may be hidden, set state: 'attached' explicitly.

Wait for content as well as the element

A container may appear before its asynchronous data is inserted. If your installed Playwright version supports the assertion APIs shown in the official assertions guide, assert the expected text before capturing:

const { expect } = require('@playwright/test');

await target.waitFor({ state: 'visible', timeout: 10_000 });
await expect(target).toContainText('Order complete', { timeout: 10_000 });
await page.screenshot({ path: 'page.png', fullPage: true });

If you are not using Playwright Test, use the locator’s text or other condition APIs available in your installed Playwright version, or select a more specific readiness marker that is only rendered after the required content is ready. Keep the wait bounded.

Repeatable screenshots

For more stable output, disable animations and optionally mask areas that change on each run. Page and locator screenshots support options such as output path, image type, scale, masking, and temporary stylesheets; use the options appropriate to the installed version.

await page.screenshot({
  path: 'page.png',
  fullPage: true,
  animations: 'disabled',
  mask: [page.locator('.live-clock')],
});

Locator screenshots also support disabling animations, masking, applying a stylesheet, choosing PNG/JPEG/WebP output, and selecting scale. Review the Playwright Locator API and Page API for the options available in your package version. The Page API discourages page.waitForSelector() in favor of locator waits or web-first assertions.

3. Puppeteer: wait for a visible selector, then capture

Install Puppeteer in your Node.js project according to the official Puppeteer installation guide. This example waits for a visible selector, saves a full-page screenshot, and closes the browser in a finally block. The returned element handle can instead capture just the matched node.

const puppeteer = require('puppeteer');

(async () => {
  const url = 'https://example.com';
  const selector = '.ready';
  const browser = await puppeteer.launch();

  try {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'domcontentloaded' });

    const target = await page.waitForSelector(selector, {
      visible: true,
      timeout: 10_000,
    });

    if (!target) {
      throw new Error(`No element found for ${selector}`);
    }

    await page.screenshot({ path: 'page.png', fullPage: true });
    // To capture just the matched element instead, use:
    // await target.screenshot({ path: 'element.png' });
  } catch (error) {
    throw new Error(`Screenshot failed for ${url} after waiting for ${selector}: ${error.message}`, { cause: error });
  } finally {
    await browser.close();
  }
})();

Puppeteer’s waitForSelector() accepts ordinary CSS selectors, returns immediately if a match already exists, and otherwise waits for one to be added. Its default timeout is 30 seconds; set timeout to your own job’s bound. The visible option requires the element to be present and visible. See the Puppeteer waitForSelector API and screenshot guide.

Selectors inside an iframe

A top-level page selector does not find elements inside a separate iframe. Get the relevant frame and wait there:

const frame = page.frames().find((candidate) => candidate.url().includes('widget'));
if (!frame) throw new Error('Widget frame was not found');
const target = await frame.waitForSelector('.ready', { visible: true, timeout: 10_000 });

Use a reliable way to identify the frame for your page. Puppeteer provides Frame.waitForSelector() specifically to wait within a frame.

4. cURL, Python, and Node.js API alternatives

Browser automation is the right fit when your own code needs to observe a page state and decide when it is ready. If you need a screenshot from a URL without managing a browser, use a screenshot API. ScreenshotNeo accepts a URL in one GET request and returns an image or PDF; its API parameters also support the selector wait and capture options used by other screenshot APIs. See the ScreenshotNeo API documentation for parameter names and configuration.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -d wait_for_selector=.ready \
  -d wait_until=visible \
  -o shot.webp

Python

import requests

response = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://example.com",
        "wait_for_selector": ".ready",
        "wait_until": "visible",
        "format": "webp",
    },
    timeout=90,
)
response.raise_for_status()
with open("shot.webp", "wb") as image_file:
    image_file.write(response.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com',
  wait_for_selector: '.ready',
  wait_until: 'visible',
  format: 'webp',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: HTTP ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
  writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);

Use the exact selector-wait parameter names supported by the API as listed in the current documentation; configure a bounded wait there if the API exposes a timeout option. The examples above illustrate the request shape and expected selector condition. Keep the API key out of public client-side code and logs.

5. Or skip the browser setup

With ScreenshotNeo, send one request for the URL and selector condition. The API returns the screenshot without requiring you to install and manage a browser for this capture.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

See the API documentation for selector-wait parameters and other options. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed; an MCP server lets AI agents take screenshots; and the free plan includes 1,000 screenshots a month with no card, while paid plans start at $5 for 3,000. Sign up free and get 1,000 screenshots a month with no card.

6. Troubleshooting

Symptom Likely cause Fix
Wait succeeds, but screenshot shows a spinner or empty content The selector appears before its data or child content is ready. Wait for expected text, a final-state marker, or a more specific selector. Keep the wait bounded.
Wait times out even though the element is in the DOM The element is attached but hidden, or the chosen state is stricter than needed. Inspect the page state. Use attached only if DOM presence is enough; use visible when the screenshot needs a displayed element.
Wait times out and the selector never appears The page did not reach the expected state, the selector is wrong, navigation failed, or the element is in an iframe. Log the URL and selector, inspect the rendered DOM, verify the selector in the correct frame, and report the timeout rather than silently saving an incomplete image.
Page screenshot is cropped to the viewport Full-page capture was not enabled. Set fullPage: true in the page screenshot call.
Element screenshot misses content in a scrollable panel The element capture includes only the panel’s currently scrolled content. Scroll the container to the needed position before capture, or capture the page if the full page is the intended artifact.
Screenshot varies between runs Animations, clocks, rotating content, or live widgets change the rendered pixels. Disable animations, mask dynamic regions, or apply temporary screenshot CSS where supported.
Selector cannot be found in an iframe The selector was queried against the top-level page. Wait through the frame’s own selector API.
Script hangs longer than expected The wait has no suitable timeout, or another navigation/readiness condition is unbounded. Set explicit timeouts for selector waits and navigation, and close the browser in finally.

7. Performance, reliability, and cost

  • Wait on a condition, not a fixed delay. A sleep can be too short on a slow run and waste time on a fast run. A selector wait continues as soon as the required condition is true.
  • Avoid using network idle as a universal readiness signal. Pages with ongoing requests may never become idle, and network quiet does not necessarily mean the specific content you need is ready. The selector condition is more directly tied to the capture.
  • Bound the work. Choose a timeout that fits the site and job, log the URL and selector on failure, and treat a timeout as a failed capture. Do not silently save an image that may be misleading.
  • Keep browser lifecycle predictable. Close the browser in a finally block so failed waits and screenshots do not leave browser processes running.
  • Control image size intentionally. Full-page screenshots can contain much more content than viewport images. Choose the scope and output format needed by the next step; use screenshot scale or format options when they suit your downstream workflow.
  • Account for API billing behavior. ScreenshotNeo bills only clean shots; bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses identify the page verdict and billing status in X-Page-Verdict and X-Billed headers. Plans include 1,000 free shots monthly without a card; paid tiers 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.

8. Which approach should you use?

Approach Wait behavior Capture choices Good fit
Playwright locator Defaults to visible; supports attached, hidden, detached; locator wait timeout defaults to zero unless configured. Full page or matched locator; options include animation control, masking, output type, scale, and stylesheets. New Playwright automation and tests that benefit from locator-based waits.
Puppeteer waitForSelector Can wait for visible; documented default timeout is 30 seconds. Full page or returned element handle. Existing Puppeteer workflows and scripts using its page/frame APIs.
Screenshot API Configure the API’s selector-wait condition and timeout parameters. Request an image or PDF without managing a local browser. URL-to-image jobs, services, and AI-agent workflows.

Use Playwright or Puppeteer when the capture is part of a larger browser workflow or needs custom logic in your process. Use an API when the input is a URL and you want to avoid maintaining browser setup. ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

9. FAQ

Can I wait for an element to disappear before taking a screenshot?

Yes. In Playwright, wait for hidden or detached, depending on whether the element may remain in the DOM. In Puppeteer, use its hidden wait option. Then capture the page.

Does visible mean that the element’s content is fully loaded?

No. Visibility describes whether the element is displayed; it does not confirm that asynchronous text, images, or data inside it are complete. Wait for the content or a final-state marker your screenshot depends on.

Should I use a fixed sleep after the selector appears?

Only if you have a separate, specific reason to allow an effect to settle. Prefer waiting for the actual content or state required by the screenshot, since fixed delays can be either insufficient or unnecessarily long.

Can I capture only an element and still get the entire page?

No. An element screenshot is clipped to the matched element. Use a page screenshot with full-page capture when the artifact needs the page’s full scrollable content.