Free tools Windows power users keep installed
One-click scans. No signup required.
Short answer: native PhantomJS does not have a documented saveScreenshot() method. Its screenshot API is page.render(filename), which returns void. Wait for page.open() to report success, wait for the page’s own asynchronous content to be ready, call page.render(), and only then exit PhantomJS. If your code uses WebDriverJS, keep saveScreenshot() in the command chain and call the test callback after the chain completes.
First identify which API you are using
The name saveScreenshot() normally comes from a WebDriverJS-style wrapper, not from PhantomJS itself. In a native PhantomJS script, create a WebPage object and call page.render('file.png'). The documented signature has no completion callback or promise: it renders the page to an image buffer and saves the specified filename, then returns void.
That distinction determines what “wait” can mean:
- Native PhantomJS: wait for navigation and application readiness before rendering, then delay process exit if your environment can terminate the process before the file flushes.
- WebDriverJS: leave
saveScreenshot()in the client’s chain and invoke the test’sdonecallback only in a later chained command.
Native PhantomJS: render only after the page is ready
Minimal load-aware script
This is a complete native pattern. It checks the navigation result, waits briefly for post-load work, renders the image, and exits afterward.
#1 Best Overall
var page = require('webpage').create();
page.open('https://example.com', function (status) {
if (status !== 'success') {
console.log('Page load failed: ' + status);
phantom.exit(1);
return;
}
// Replace this with a real readiness condition for your page.
setTimeout(function () {
page.render('screenshot.png');
// Keep the process alive briefly if your environment exits before the file flushes.
setTimeout(function () {
phantom.exit();
}, 100);
}, 200);
});
page.open() calls its callback after loading and supplies a success or failure status. A successful callback means navigation completed; it does not prove that AJAX responses, timers, web fonts, lazy images, or client-side rendering have finished. The 200-millisecond readiness delay and 100-millisecond post-render delay are illustrative safeguards, not guarantees.
Why page.render() has no “finished” callback
Because the native method returns void, there is no PhantomJS render promise to await. The reliable sequence is therefore procedural: establish readiness, call render, keep the process alive long enough for your runtime and filesystem to finish the write, and exit. Adding a callback argument to page.render() will not turn it into an asynchronous API.
Replace a fixed delay with a deterministic readiness signal
A timer is useful as a fallback, but it is inherently approximate: a fast page wastes time, while a slow API response can still be missing from the capture. Prefer a signal emitted by the page you control, and cap the wait so a broken page cannot hang the job indefinitely.
DOM marker set by application code
Have the application add a marker such as window.__SCREENSHOT_READY__ = true or populate an element such as #ready after its data and visual state are complete. Poll that condition from PhantomJS with a bounded deadline.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #2
var page = require('webpage').create();
var deadline = Date.now() + 10000;
function finishOrRetry() {
var ready = page.evaluate(function () {
return window.__SCREENSHOT_READY__ === true ||
!!document.querySelector('#ready');
});
if (ready) {
page.render('screenshot.png');
setTimeout(function () { phantom.exit(0); }, 100);
return;
}
if (Date.now() >= deadline) {
console.log('Timed out waiting for application readiness');
phantom.exit(2);
return;
}
setTimeout(finishOrRetry, 100);
}
page.open('https://example.com', function (status) {
if (status !== 'success') {
console.log('Page load failed: ' + status);
phantom.exit(1);
return;
}
finishOrRetry();
});
Use a marker that represents the actual content required in the image, not merely a generic page-load event. If you cannot modify the page, poll for a selector whose presence proves the relevant content exists, or use a short bounded delay as a last resort.
Images, fonts and lazy content
Pages often continue work after the load callback. A chart may be drawn by a timer, an image may be inserted after an API response, and a font may change layout after the first paint. Make the readiness marker occur after those operations. For third-party pages where no marker is available, choose a conservative maximum wait and accept that a timeout remains possible; do not let an unbounded polling loop keep every job alive forever.
WebDriverJS: wait for the chained screenshot command
If your test uses a WebDriverJS client that provides saveScreenshot(), do not place the call inside a separate waitFor() callback or invoke done immediately afterward. The screenshot operation is itself queued. Put it directly in the chain and call done only in a subsequent command.
it('captures the page', function (done) {
client.url('https://example.com')
.waitFor('#ready', 7000)
.saveScreenshot('./ExtractScreen.png')
.call(done);
});
Here .waitFor('#ready', 7000) supplies an application-level readiness condition, .saveScreenshot() runs after it, and .call(done) is reached only when the preceding commands have completed. Client libraries differ, so confirm the exact chaining and timeout behavior for the version you run.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsRank #3
Navigation completion is not application completion
These are separate checkpoints:
- Navigation:
page.open()reports whether the URL loaded successfully. - Application readiness: your page has fetched data, completed client-side rendering, loaded required images or fonts, and reached the state you want to archive.
- Capture and shutdown:
page.render()is called, the output is allowed to flush, and only then does PhantomJS exit.
Confusing the first checkpoint with the second is the usual reason for an apparently successful script that produces an incomplete screenshot.
Troubleshooting incomplete or missing files
The file is missing
- Cause: the script exits before
page.render()runs, often becausepage.open()failed or an exception bypassed the render call. - Fix: log the status, return after
phantom.exit(1)on failure, and put rendering inside the successful callback or readiness branch.
The file exists but dynamic content is absent
- Cause: navigation finished before AJAX, timers, lazy loading or client-side rendering.
- Fix: wait for a page-specific DOM marker or JavaScript flag. Use a bounded delay only when no deterministic signal is available.
The image is truncated or intermittently corrupt
- Cause: the surrounding process terminates immediately after the render call and cuts off file flushing.
- Fix: keep PhantomJS alive briefly after
page.render(), as in the 100-millisecond safeguard, and tune the delay for your execution environment.
A WebDriverJS test finishes too early
- Cause:
done()is called before the queued screenshot command has completed, or the screenshot is hidden inside a callback that the client does not await. - Fix: chain
saveScreenshot()directly and place.call(done)after it.
The readiness wait never ends
- Cause: the selector or flag is never created because the page returned an error, the application changed its markup, or a dependency is blocked.
- Fix: use a deadline, log a useful timeout error, and exit with a nonzero status. Verify the marker in the same browser/runtime used by the capture.
Operational guidance for reliable jobs
Use explicit exit codes
Return a distinct nonzero code for navigation failure and readiness timeout. A scheduler can then distinguish a bad URL from a page that loaded but never reached its expected state.
Keep waits bounded
A readiness condition should have both a success path and a timeout path. This protects queues from one page whose JavaScript never settles.
Make the capture state reproducible
Use a stable URL, a known viewport and a marker that is tied to the exact data shown in the screenshot. If the page has animations, disable them or wait for a stable state before rendering; otherwise two otherwise identical runs can differ.
Plan for maintenance
The PhantomJS project states that development is suspended until further notice. That makes it a legacy choice for new automation. Keep existing jobs bounded and observable, and plan a migration when the surrounding application or browser requirements change.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If you only need a dependable URL-to-image request, ScreenshotNeo provides a website screenshot API and MCP server. It accepts the page URL, handles the browser session, and returns PNG, JPEG or WebP (or a PDF) from one request. Its cleanup steps can accept consent banners and remove more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be turned off.
Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.
One-call examples
See the parameter reference and complete option list in the ScreenshotNeo documentation.
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}`);
ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, pre-capture clicks, selector hiding, waits for selectors, delays or network idle, request and resource blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which eases migration.
An MCP server supplies take_screenshot, get_page_info and capture_pdf tools to Claude, Cursor and other MCP clients, so an AI agent can perform the capture without a custom PhantomJS wrapper.
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try the 1,000-shot allowance without adding a card.
Decision checklist
- Using native PhantomJS? Replace
saveScreenshot()withpage.render(). - Check
page.open()status before doing anything else. - Wait for an application-specific marker whenever possible.
- Use a finite timeout and a nonzero exit code for failure.
- Keep the process alive briefly after rendering if file flushes are unreliable.
- Using WebDriverJS? Chain
saveScreenshot()and calldoneafterward.
Frequently Asked Questions
Does native PhantomJS support a callback on page.render()?
No. The documented native method returns void, so sequence readiness, rendering and process exit explicitly.
Can a 200 ms delay guarantee that AJAX content is present?
No. It is only an illustrative fallback. A page-specific selector or JavaScript readiness flag is more reliable, with a bounded timeout.
Why does saveScreenshot() work in one project but not another?
It is commonly supplied by a WebDriverJS wrapper. Native PhantomJS exposes page.render(), so the correct waiting pattern depends on the library layer.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

