Best Screenshot APIs with a JavaScript Rendering Wait Option
Compare screenshot APIs by how they detect page readiness: navigation events, selectors, functions, or delays—and choose a wait that fits your page.
For a JavaScript-rendered page, choose a screenshot API that can wait for the condition that means your content is ready. A navigation event such as load or network idle is useful when the page’s requests indicate readiness. A selector wait is usually more precise when a known element appears after rendering. A custom function or event can express application-specific readiness. A fixed delay is a fallback, not proof that the page has finished rendering.
ScreenshotNeo is the first API to try for a straightforward screenshot request: it accepts one GET request and returns an image or PDF, removes supported consent banners and other overlays before capture, and bills only clean shots. For APIs whose docs expose explicit rendering-wait controls, ScreenshotOne and Browserless document useful options; Urlbox also documents lifecycle and selector waits. The documentation reviewed here does not establish a speed or reliability winner, and no comparative render test was conducted.
Readiness and capture scope are separate decisions. First decide how the API knows the page is ready; then decide whether you need the viewport, full page, or a particular element.
1. What a JavaScript rendering wait can—and cannot—guarantee
A screenshot service navigates a browser to a URL, waits according to its configuration, and captures the browser’s current rendered state. JavaScript may fetch data, hydrate a framework app, reveal a component, or continue changing the page after navigation has completed.
No single wait condition means “everything on the page is visually settled.” Long polling, analytics, chat connections, lazy images, animations, canvas drawing, and delayed third-party scripts can all complicate the definition of ready. Choose a signal tied to the content you need and use a bounded timeout.
| Wait signal | What it observes | Use it when | Common limitation |
|---|---|---|---|
| Navigation lifecycle | An event such as DOM content loaded or full load | The page’s initial navigation is a useful readiness boundary | Client-side rendering can continue after the event |
| Network idle | Few or no active network requests for an interval | The page becomes quiet after fetching its content | Persistent connections can prevent idleness; quiet does not prove visual completion |
| Fixed delay | Elapsed time | A known animation or delayed transition needs a small settling period | Can wait too long on fast runs and too little on slow runs |
| Selector | An element matching a CSS selector appears, and sometimes becomes visible | A page-specific component marks the content you need | DOM presence can precede visible or complete content |
| Function or event | A custom predicate or application signal | You control the page or can identify a precise ready state | Requires a correct predicate and may need careful timeout handling |
2. Recommended APIs and their documented wait options
ScreenshotNeo — first API to try
ScreenshotNeo is a website screenshot API and MCP server. A single GET request accepts a URL and returns PNG, JPEG, WebP, or PDF. The product’s supplied feature information covers 63 options, including viewport and device presets, full-page capture with lazy images loaded, element capture, custom CSS and JavaScript, selector or delay or network-idle waits, and caching with a chosen TTL. Check the ScreenshotNeo API documentation for current parameter details.
Its wait controls let you select a practical readiness condition; the supplied product facts do not specify the exact semantics of each wait parameter, so use the docs for the current syntax and behavior. ScreenshotNeo’s distinguishing capture behavior is relevant when the page has consent banners or overlays: it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots; responses identify the page verdict and billing status in headers.
Other available options include dark mode, retina scale, PDF page settings, HTML or CSS input, click-before-capture, hiding selectors, request blocking, custom headers and cookies, user agent and Authorization, timezone and geolocation, transparent backgrounds, resizing, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage API, and OpenAPI specification. Parameter names used by other screenshot APIs also work to make switching easier.
ScreenshotOne — lifecycle, network-idle, delay, and selector waits
ScreenshotOne documents wait_until values load, domcontentloaded, networkidle0, and networkidle2. Its docs define the network-idle conditions over a 500 ms interval: zero active connections for networkidle0, or no more than two for networkidle2. You can also set delay in seconds and wait_for_selector. Selector waiting means DOM presence, not necessarily visibility. Multiple selectors use an at-least-one behavior by default; the documented algorithm option can require a count. If the same selector is already the screenshot target, the selector wait is ineffective. For custom scripts, scripts_wait_until is a post-script navigation-event wait; it is not a guarantee that all asynchronous work has ended. See the ScreenshotOne options documentation.
Browserless — selector, function, event, and timeout preconditions
Browserless documents shared request configuration for waits by timeout, selector, function, or event, and says this configuration applies to its screenshot endpoint. Selector waits can request visible or hidden state and specify a timeout; an unmet selector can return a non-200 error. A function wait can use a JavaScript function that returns a truthy value. Its screenshot API accepts a JSON request body and supports full-page and selector capture. See the request configuration docs and screenshot API docs.
Urlbox — lifecycle and selector waits
Urlbox documents wait_until lifecycle choices, wait_for to wait for a selector in the DOM, wait_for_state for attached or visible state, and wait_timeout. It also documents waiting for a selector to leave the DOM. Its documented defaults may allow the capture to continue after a wait selector is not found unless failure behavior is explicitly enabled. Read the Urlbox options documentation for current behavior and parameter syntax. The sources reviewed do not establish a directly comparable custom-function readiness interface for Urlbox.
| API | Documented readiness controls | Request pattern in docs | Good fit |
|---|---|---|---|
| ScreenshotNeo | Selector, delay, network idle (see product docs for syntax) | GET request with URL and API key | Simple capture flow, clean shots, broad capture options, or MCP agent use |
| ScreenshotOne | Lifecycle, network idle, delay, selector, post-script navigation wait | Query parameters | Picking among documented navigation and selector conditions |
| Browserless | Timeout, selector, function, event; shared preconditions | JSON POST request | Expressing a custom page-specific predicate or event |
| Urlbox | Lifecycle, delay, selector presence or visibility, selector departure | Options-based screenshot API | Waiting for a specific element or for a loading marker to disappear |
This is a feature comparison based on the providers’ documentation, not a quality ranking. Pricing, quotas, latency, and reliability were not verified for the other providers in this guide.
3. Choose a readiness condition
- Identify the content you need. Find the element that appears only after the data or component you care about is rendered. A result list, chart container, or page-specific heading may be a better signal than a generic body element.
- Prefer a content signal over elapsed time. Use a selector when its appearance marks readiness. Use a function or event when a selector alone cannot express the condition. Use lifecycle or network idle when initial navigation state is sufficient.
- Check the signal’s semantics. Does it mean present in the DOM, visible, hidden, or absent? Does a list of selectors mean any one or all? Does a failed wait stop the capture, or return a best-effort image?
- Set a timeout that fits the operation. Bound navigation and readiness waits. Leave enough time for normal slow responses, but avoid unbounded jobs. Treat timeout output as a failed or incomplete capture according to your workflow.
- Set capture scope separately. A selector used as the wait target may not also work as the capture target on every API. Verify the endpoint behavior. For long pages, consider full-page capture and lazy-load handling.
- Repeat with the same conditions. Dynamic content can vary between captures. Keep URL, viewport, wait, timeout, format, and capture scope stable when diagnosing a difference.
4. Runnable wait examples from the provider documentation
ScreenshotOne: wait for one of the target page’s readiness conditions
This cURL request waits until the page has no more than two active network connections for the documented 500 ms interval, then saves the response. Replace the URL with the page you own or are authorized to capture.
curl --fail --get "https://api.screenshotone.com/take" \
--data-urlencode "access_key=YOUR_SCREENSHOTONE_ACCESS_KEY" \
--data-urlencode "url=https://example.com" \
--data-urlencode "wait_until=networkidle2" \
--output screenshot.png
To wait for DOM presence instead, replace the wait parameter with --data-urlencode "wait_for_selector=.results-ready". For a fixed settling interval, use --data-urlencode "delay=2"; the documented unit is seconds. Do not assume selector presence means visibility or that the delay proves data is complete.
Browserless: wait for a visible selector on the screenshot endpoint
Browserless documents JSON POST requests and shared preconditions on the screenshot endpoint. This example asks for a visible element, then saves the image response:
curl --fail-with-body -X POST \
"https://production-sfo.browserless.io/screenshot?token=YOUR_BROWSERLESS_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"waitForSelector": {
"selector": ".results-ready",
"visible": true,
"timeout": 10000
},
"options": {
"type": "png",
"fullPage": true
}
}' \
--output screenshot.png
When using this provider, a timeout can produce a non-200 response. Handle that as an error rather than assuming the saved file is a valid screenshot.
5. Or skip the browser setup
Make one GET request to ScreenshotNeo and save the returned image. The examples use the supplied API pattern; find current options in the ScreenshotNeo docs.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo removes cookie banners, supported consent overlays, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed as clean shots; response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. All features are on every plan. Sign up for 1,000 free screenshots a month, with no card required.
6. Troubleshooting waits and screenshots
| Symptom | Likely cause | What to change |
|---|---|---|
| Screenshot shows a loading shell or missing data | The navigation event fired before client-side rendering finished | Wait for a page-specific selector, function, or event that tracks the content you need |
| Selector wait finishes but the element looks empty or invisible | The selector only guarantees DOM presence, or the component appears before it is populated | Choose a selector that appears later, request visible state where supported, or use a predicate that checks meaningful content |
| Network-idle wait times out | Long polling, analytics, streaming, or other requests keep the connection count above the threshold | Use a page-specific selector or function; use a bounded delay only if a known settling period is appropriate |
| Wait returns immediately when it should not | The selector already exists as a placeholder or skeleton | Wait for a later state marker, test for non-empty content, or wait for the skeleton to disappear |
| Selector is never found | Wrong selector, delayed content, different responsive layout, failed script, or selector evaluated in a different frame | Check the selector in the same viewport, verify the page’s DOM and frame behavior, and inspect navigation errors |
| Wait timeout returns an error | The required condition did not occur within the configured bound | Confirm the condition is reachable; increase the bound modestly if the site is legitimately slow, or use a better readiness signal |
| Image is blank or shows a bot check | The destination served a challenge, denied automation, or failed before content loaded | Inspect the captured page state and verdict; do not interpret a challenge page as a rendering-wait failure |
| Full-page capture misses images or lower content | Lazy-loaded content may only load when scrolled into view | Use an API’s full-page and lazy-load support, or capture the required element after it has been brought into view |
| Capture changes between runs | Animation, rotating content, current timestamps, ads, or live data changed the rendered state | Reduce motion where supported, use a stable test URL or state, and keep the capture environment consistent |
| Download is an error document instead of an image | The request returned an HTTP error or JSON/text error body | Check status and content type before treating the response body as an image; retain provider error details for diagnosis |
7. Performance, reliability, and cost considerations
- Wait only for what you need. A global network-idle condition can delay a page with persistent background traffic. A precise selector can finish sooner when it genuinely represents the required content, but no cross-provider timing claim follows from the docs.
- Avoid arbitrary long delays. A fixed delay spends time on every capture and still can be too short under load. Prefer an explicit readiness condition, then add only a small delay if a transition or animation needs settling.
- Make failures visible. Check HTTP status and response type. Record the requested URL, wait condition, timeout, viewport, and any page-verdict or billing headers your provider returns. Retry only failures likely to be transient; repeated retries do not correct a permanently wrong selector.
- Stabilize the output. Set viewport, device scale, color scheme, and capture scope consistently. Dynamic page state can still vary even with the same wait condition.
- Budget for total capture time. Navigation, readiness wait, full-page scrolling, and image encoding all contribute to a request. Use asynchronous or bulk features when appropriate and available. ScreenshotNeo supports async jobs, signed webhooks, bulk capture up to 100 URLs per call, and selectable cache TTL.
- Compare actual billing rules. The dossier does not verify current pricing or quotas for ScreenshotOne, Browserless, or Urlbox, so check each provider’s current pricing and failure policy before estimating production cost. ScreenshotNeo’s supplied pricing is Free for 1,000 shots monthly with no card; 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. Only clean shots are billed under the supplied product facts; cache hits are not billed.
8. Frequently asked questions
Is network idle always the best wait for JavaScript pages?
No. It measures network activity, not whether the specific element you need is visible and complete. A selector or application-specific function may be a better signal.
Does waiting for a selector mean the screenshot will include it visibly?
Not necessarily. Some APIs wait only for DOM presence. Use a documented visibility condition or a selector that marks the final rendered state.
Should I use a fixed delay or a selector?
Use a selector when a stable page element marks readiness. Use a delay for a known time-based transition when no better signal is available, and keep it bounded.
Can a screenshot API guarantee identical screenshots every time?
No readiness wait guarantees visual stability. Live content, animation, canvas rendering, and third-party resources may produce different captures.
Which API supports an application-specific JavaScript function wait?
Browserless documents a function precondition in shared request configuration. Confirm its current endpoint configuration and timeout behavior before relying on it.
Can I capture just the rendered component rather than the entire page?
Several providers document selector or element capture. Treat element capture as separate from readiness waiting and check whether the API can use the same selector for both roles.
