How to Capture a Website After JavaScript Finishes Loading with ScreenshotOne
Wait for the rendered content you need—not just a browser navigation event. Compare ScreenshotOne’s wait options, runnable examples, and fixes for common capture failures.
A browser’s load event does not guarantee that a JavaScript application has finished fetching and displaying its report, chart, or table. With ScreenshotOne, use wait_until for browser navigation events, wait_for_selector for a known element that appears with the needed content, and delay only when neither gives a reliable readiness signal. For dynamic pages, a content-specific selector is usually the clearest signal. See ScreenshotOne’s options reference.
The examples below capture a page after its results container appears. Replace the example URL and #report-ready selector with values from your own site. The selector must identify an element that appears when the content you need is ready.
Choose a readiness signal
These options answer different questions. Navigation events describe browser lifecycle; a selector describes a page element; a delay describes elapsed time. A page can satisfy a navigation event while its application is still loading data.
| Method | What it waits for | Best fit | Tradeoff |
|---|---|---|---|
wait_until |
One or more navigation events | A general page lifecycle milestone | Does not prove application data is ready |
wait_for_selector |
An element appearing in the DOM | A known result, chart, or ready marker | DOM presence does not necessarily mean visible or complete |
delay |
A fixed number of seconds | A fallback when no dependable event or selector exists | Can wait too long on fast pages and too briefly on slow ones |
Navigation events with wait_until
ScreenshotOne documents load as the default. The other accepted values are domcontentloaded, networkidle0, and networkidle2. The network-idle choices mean no more than zero or two open network connections, respectively, for at least 500 ms. You can supply multiple values; the capture waits for all selected events. These events can still occur before a single-page app has filled in its data. See the option definitions and examples.
Application content with wait_for_selector
Use a selector tied to the content being captured, such as #report-ready or .results-table. The option waits for the element to appear in the DOM; it does not promise the element is visible. A robust application can expose a stable readiness marker only after its data and layout are ready, for example a data-ready="true" attribute. That marker is your page’s convention; pass its CSS selector to ScreenshotOne’s wait_for_selector option.
For a comma-separated selector list, the default wait_for_selector_algorithm=at_least_one allows the wait to end when one selector matches. The alternative at_least_by_count counts matched elements; it does not guarantee that each selector in the list matched. Also, if you use selector to capture an element, waiting for that same selector is ineffective. The options reference covers these selector behaviors.
Fixed time with delay
delay adds a number of seconds before capture; its default is zero. Use it when the page has no dependable readiness event or selector. It is a compromise: a fixed interval can be wasteful on a fast response and insufficient under a slow one. ScreenshotOne’s full-page guide suggests trying 5–10 seconds on difficult pages, but that is a tuning suggestion, not a universal setting. Full-page screenshot guidance.
Runnable ScreenshotOne examples
These examples make a GET request to ScreenshotOne’s /take endpoint and save the returned PNG. Create an access key in your ScreenshotOne account and keep it out of browser-side code, public repositories, and shared URLs. ScreenshotOne documents both GET options and POST JSON requests in its getting started guide.
cURL
export SCREENSHOTONE_ACCESS_KEY="YOUR_ACCESS_KEY"
curl -G "https://api.screenshotone.com/take" \
-H "X-Access-Key: ${SCREENSHOTONE_ACCESS_KEY}" \
--data-urlencode "url=https://example.com/dashboard" \
--data-urlencode "format=png" \
--data-urlencode "wait_for_selector=#report-ready" \
-o dashboard.png
To try the navigation event first, replace the wait_for_selector line with --data-urlencode "wait_until=load". To require more than one navigation event, repeat the parameter, for example --data-urlencode "wait_until=domcontentloaded" --data-urlencode "wait_until=networkidle2". Add --data-urlencode "delay=5" only when a selector or event does not reliably indicate readiness.
Python
Install the dependency with python -m pip install requests, set the access key in the environment, and save this as capture.py.
import os
import requests
response = requests.get(
"https://api.screenshotone.com/take",
headers={"X-Access-Key": os.environ["SCREENSHOTONE_ACCESS_KEY"]},
params={
"url": "https://example.com/dashboard",
"format": "png",
"wait_for_selector": "#report-ready",
},
timeout=100,
)
response.raise_for_status()
with open("dashboard.png", "wb") as image:
image.write(response.content)
The client timeout is 100 seconds so the caller does not abandon a request before ScreenshotOne’s documented maximum timeout of 90 seconds. The API-side timeout is separate: if needed, set "timeout": 90 in params. ScreenshotOne’s API also has a separate navigation_timeout option.
Node.js
This example uses the built-in fetch available in modern Node.js releases. Set SCREENSHOTONE_ACCESS_KEY, then run the file with node capture.mjs.
import { writeFile } from "node:fs/promises";
const accessKey = process.env.SCREENSHOTONE_ACCESS_KEY;
if (!accessKey) throw new Error("Set SCREENSHOTONE_ACCESS_KEY first");
const params = new URLSearchParams({
url: "https://example.com/dashboard",
format: "png",
wait_for_selector: "#report-ready",
});
const response = await fetch(
`https://api.screenshotone.com/take?${params}`,
{
headers: { "X-Access-Key": accessKey },
signal: AbortSignal.timeout(100_000),
},
);
if (!response.ok) {
const details = await response.text();
throw new Error(`ScreenshotOne returned HTTP ${response.status}: ${details}`);
}
await writeFile("dashboard.png", Buffer.from(await response.arrayBuffer()));
Practical tuning steps
- Start with the least waiting that gives the right image. ScreenshotOne’s performance guide recommends trying
wait_until=loadexplicitly first. If the page already contains the needed content at that point, extra waits only add latency. Rendering performance guidance. - If app data arrives later, wait for the content. Add
wait_for_selectorfor the results element or a readiness marker. Verify that the marker only appears after the relevant data has rendered. - Check visibility and layout separately. A matched DOM node can be hidden, empty, or zero-height. If your capture depends on a visible chart or table, make the marker reflect that state, or confirm the output image during development.
- Use a delay only as a fallback. Add a small
delayand increase it only as required. Remember it consumes time within the request’s timeout budget. - For full-page captures, account for lazy loading. Scrolling can trigger images and other below-the-fold content. ScreenshotOne’s full-page options include scrolling behavior and delay controls; a capture may need the page to scroll before all lazy content is available. Full-page screenshot guide.
- Account for scripts that navigate. If your injected script triggers a navigation or reload, configure
scripts_wait_untilfor the relevant navigation event or provide an appropriate delay. The default forscripts_wait_untilis an empty list, meaning no wait. Custom scripts guide.
Other capture options that affect the result
Waiting solves timing; it does not define what gets captured. ScreenshotOne exposes related options for the output and page state. Consult the full options reference for exact parameter details.
- Viewport versus full page: use
full_page=truefor the entire page. Long pages may need scrolling so lazy content loads; consider a maximum height for exceptionally tall pages. - One element: use
selectorto capture a specific element. If you need that element to exist before capture, wait for a different readiness marker; waiting on the same selector used for capture is ineffective. - Format: choose
format=png,jpeg/jpg,webp, orpdfas appropriate. ScreenshotOne documents JPG as the default. Binary responses can be saved directly to a file. - Viewport and device scale: choose viewport width and height, a device preset, and a device scale factor to match the layout you need. Responsive breakpoints can change which content is rendered.
- Motion: reduced-motion settings may help stabilize some pages, but they are best effort. Custom JavaScript animation, canvas drawing, and animated images can still change during a capture; do not expect pixel-identical output from a moving page.
- Timeouts: the documented
timeoutdefault is 60 seconds and maximum is 90 seconds.navigation_timeoutdefaults to 30 seconds and has a maximum of 30 seconds. These are separate from your HTTP client timeout. Timeout options. - Async rendering: for jobs that should continue without holding an immediate request open, ScreenshotOne supports asynchronous rendering and webhooks. Async and webhooks documentation.
- Sharing requests: do not expose an access key in a client-side app or a URL that others can inspect. ScreenshotOne’s Node SDK documentation describes signed take URLs for sharing a request without exposing the key. JavaScript and TypeScript SDK documentation.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot has the app shell but no report data | load completed before the app’s data request and rendering finished |
Wait for a result-specific selector or explicit readiness marker. Use a fixed delay only if the page has no dependable marker. |
| Selector wait ends but the target looks blank | The node exists in the DOM but is hidden, empty, or not yet populated | Use a marker that becomes available after the content is populated; inspect whether the selector points to the visible element. |
| Selector not found error | Selector typo, delayed rendering, missing content, or an element that is not visible | Check the selector against the rendered page, confirm the element is present and visible, and allow enough time. See ScreenshotOne’s selector error guide. |
networkidle0 never completes or takes too long |
The page keeps connections open, such as polling or streaming | Prefer a content selector or try networkidle2. Avoid requiring a network-idle state when the app intentionally maintains background connections. |
| Capture times out | Slow navigation, an overly long wait, or too much work for the configured timeout | Reduce unnecessary delay, check the target URL’s response, and adjust timeout or navigation_timeout within documented limits. For work that can complete asynchronously, consider async rendering. See timeout errors. |
| Full-page image misses lower-page images | Images load lazily only when scrolled into view | Enable or tune full-page scrolling and its delay settings, then inspect the resulting capture. See the full-page guide. |
| Image differs between captures | Animation, changing data, rotating content, or responsive layout | Capture a stable state where possible, select the same viewport, and use reduced motion when appropriate. Treat motion reduction as best effort. |
| Saved file is an error message, not an image | The request returned an API error body and the client saved it without checking status | Check HTTP status before writing the response body; log the error response for diagnosis. |
Performance, reliability, and cost
Waiting longer generally increases response latency and can reduce throughput in a synchronous workflow. A selector that represents actual readiness usually avoids waiting for unrelated activity to stop. Choose the shortest readiness rule that consistently produces the required result, and measure it on pages with both fast and slow data responses.
Neither navigation completion nor a fixed delay guarantees a correct capture if the page errors, the selector is wrong, or the content changes while rendering. For higher reliability, use a stable readiness marker, validate HTTP errors and the output, and set both the API timeout and client timeout deliberately. Retries can help with transient failures, but use bounded retries and avoid retrying permanent selector or authentication errors indefinitely.
ScreenshotOne’s documentation gives a 60-second default and 90-second maximum for the API’s timeout, with a 30-second default and maximum for navigation_timeout. Delay values beyond 30 seconds require special async configuration according to its options reference. Do not set a long fixed delay by default: it adds waiting to every request and still cannot prove the app reached the intended state. See the documented limits.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It accepts one GET request for an image or PDF; its options include waiting for a selector, delay, network idle, full-page capture, and custom JavaScript. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
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 and get 1,000 screenshots a month with no card.
FAQ
Does JavaScript need to finish every task before a screenshot?
No. Wait for the specific content required in the image. Background analytics or polling may never stop, so waiting for every network request can be counterproductive.
Can I wait for multiple selectors?
Yes. Comma-separated selectors use the configured selector algorithm. The default accepts at least one match; the count-based alternative checks a number of matches but does not guarantee that every selector matched.
Will waiting make an animated page identical every time?
No. Motion reduction can help in some cases, but custom animation, canvas output, and animated assets may still vary between captures.
Where can I find every ScreenshotOne parameter?
Use the ScreenshotOne options reference for the current request options and the getting started guide for authentication and request patterns.


