Recommended Free Tools
Call page.open once, keep the same webpage object, change the page state with page.evaluate, wait for that change to finish, and call page.render with a new filename for every image. This avoids a second navigation while preserving the page instance between captures.
The single-load capture pattern
PhantomJS separates navigation from rendering. page.open loads a URL and reports success or fail in its callback. After a successful load, the same page can be manipulated and rendered repeatedly. A typical sequence is:
- Create one object with
require('webpage').create(). - Set the viewport before opening the page.
- Call
page.openonce and stop if the callback status is notsuccess. - For each desired state, run page-context JavaScript with
page.evaluate. - Wait for the state-specific update to settle.
- Call
page.renderusing a filename that has not been used before. - Repeat until all states are captured, then call
phantom.exit().
The state can be a DOM attribute, a selected tab, an expanded panel, a chart filter, or any other change that can be triggered in the page context. No second page.open call is needed unless you intentionally navigate to another document.
Complete PhantomJS example
Save this as multi-capture.js, replace the URL and state logic, and run it with the PhantomJS command-line executable.
#1 Best Overall
var page = require('webpage').create();
var target = 'https://example.com/';
var states = ['first', 'second', 'third'];
var step = 0;
page.viewportSize = {
width: 1024,
height: 768
};
page.open(target, function (status) {
if (status !== 'success') {
console.log('Unable to load the URL: ' + status);
phantom.exit(1);
return;
}
captureNext();
});
function captureNext() {
if (step >= states.length) {
phantom.exit();
return;
}
var state = states[step];
page.evaluate(function (value) {
// Replace this with a page-specific action or DOM change.
document.body.setAttribute('data-capture-state', value);
}, state);
// Replace this delay with a page-specific readiness check when needed.
window.setTimeout(function () {
page.render('capture-' + (step + 1) + '.png');
step += 1;
captureNext();
}, 100);
}
The 1024×768 viewport is only an example. Set dimensions that match the layout you need to test. Each render receives a different filename, so an earlier image is not overwritten.
Replacing the illustrative state mutation
The sample changes an attribute only to make the workflow visible. Real pages usually need an interaction. For example, a tab switch could be performed inside evaluate:
page.evaluate(function () {
var tab = document.querySelector('[data-tab="reports"]');
if (tab) {
tab.click();
}
});
You can also set a form value, add a class, expand an accordion, or invoke a page-defined function. Keep the argument and return value JSON-serializable. PhantomJS documentation states: “As of PhantomJS 1.6, JSON-serializable arguments can be passed to the function.” Do not pass a DOM node, function, or other value that cannot be represented as JSON; pass a selector or plain data instead.
Waiting for each state to be ready
A fixed 100-millisecond delay is useful for demonstrating control flow, not for guaranteeing that an application has finished updating. A chart, request-driven table, or animation may need more time. Wait for an observable condition belonging to that state.
function waitForReport(done, attempts) {
attempts = attempts || 0;
var ready = page.evaluate(function () {
return !!document.querySelector('.report-rendered');
});
if (ready) {
done();
return;
}
if (attempts >= 40) {
console.log('Timed out waiting for the report state');
done();
return;
}
window.setTimeout(function () {
waitForReport(done, attempts + 1);
}, 250);
}
To use it, invoke waitForReport after your state-changing evaluate call and put page.render inside its callback. Replace .report-rendered with a selector, flag, or other condition that the target page actually sets. If the page exposes no reliable signal, choose a conservative delay and document that it is an approximation; there is no universal PhantomJS wait value for every site.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Viewport size versus clip rectangle
| Setting | What it controls | Typical use |
|---|---|---|
page.viewportSize |
The browser area in which the page lays out and paints. | Reproduce a desktop, tablet, or mobile-like layout. |
page.clipRect |
The rectangular portion included in the rendered output. | Capture one panel or crop a known region without changing layout. |
These settings work together: the viewport affects responsive breakpoints, while the clip rectangle selects the pixels saved. The documented 1024×768 values are examples, not required dimensions. Set clipRect before the render that needs cropping, then change or clear it before the next capture if the regions differ.
Handling several states safely
Use deterministic names
Include a sequence number and, when useful, a state label such as capture-02-reports.png. Never reuse the same path unless overwriting is intentional. If a run can be restarted, write to a run-specific directory or add a timestamp generated by the outer script.
Keep navigation out of the loop
Putting page.open inside the state loop reloads the document and defeats the purpose of this technique. Perform only in-page actions between renders. If an action genuinely navigates, wait for that navigation to complete before rendering; it is still the same page object, but it is no longer the original document state.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
End the process on every terminal path
Call phantom.exit(1) for an initial load failure and phantom.exit() after the final image. A missing exit call can leave an automation job running after the files have been written.
Troubleshooting
The callback reports fail
Do not render after a failed open. Check the URL, DNS or network access available to the PhantomJS process, and print the status before exiting. A successful callback means the load completed according to PhantomJS; it does not prove that every application request or widget finished.
Rank #3
Every image looks identical
Confirm that the selector exists and that the action actually changes the DOM. Log the state value, return a simple boolean from evaluate, and inspect the page for a state marker. If a framework updates asynchronously, replace the short delay with a selector or flag that appears only after the update.
The next capture starts too soon
Do not guess with an ever-smaller delay. Poll a page-specific readiness condition, increase the maximum wait attempts, or have the application set a completion marker after its request and rendering work finish.
A file is missing or has been overwritten
Check the working directory from which PhantomJS was launched and print the exact output path. Ensure the filename includes the incrementing step and that the process has permission to write there.
An evaluate argument causes an error
Pass strings, numbers, booleans, arrays, or plain objects that can be serialized as JSON. Pass a CSS selector instead of a live element and locate the element inside the evaluated function.
The crop is wrong
Inspect the viewport and clip coordinates. The clip rectangle is measured in page pixels; it does not resize the layout. First verify the full viewport capture, then add a clip rectangle with coordinates inside that viewport.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
The process waits forever
Bound every custom wait with a maximum number of attempts. On timeout, record the state and either render a diagnostic image or exit with a failure code, depending on whether incomplete captures are acceptable for your job.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsPerformance and reliability considerations
One navigation removes repeated network and page-startup work, so a sequence of renders is generally more efficient than opening the URL for every image. Rendering still consumes CPU and memory, especially for large viewports or many captures. Process states in a predictable order, release the process when finished, and keep output files separate so a later failure does not destroy earlier evidence.
PhantomJS documentation is legacy documentation. Current maintenance status and compatibility with present-day websites are not established here, so test the exact pages, JavaScript features, authentication flow, and rendering requirements you depend on before adopting this workflow for production. A page that loads successfully can still rely on browser capabilities that an older engine does not implement.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server when you prefer an HTTP call over maintaining a PhantomJS process. A request captures a URL as PNG, JPEG, WebP, or PDF. For a single capture, the minimal cURL call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/ -o shot.webp
See the ScreenshotNeo API documentation for authentication, output formats, and options. Equivalent Python and Node.js requests are:
Best Value
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);
For several visual states, make separate requests with state-specific custom JavaScript or click instructions, or submit asynchronous jobs when your integration supports them. ScreenshotNeo is useful when you need browser setup handled remotely and want a response that identifies the result with X-Page-Verdict and X-Billed headers.
Options relevant to repeated or controlled captures
- Full-page screenshots can load lazy images; element capture accepts a CSS selector.
- Choose dark mode, one of 12 device presets, any viewport, and a retina scale.
- Generate PDFs with paper size, margins, landscape mode, and page ranges.
- Run custom CSS or JavaScript, click an element before capture, hide selectors, and wait for a selector, delay, or network idle.
- Block ads, trackers, selected requests, or resource types to make captures more deterministic.
- Supply custom headers, cookies, user agents, Authorization values, timezone, and geolocation.
- Use transparent backgrounds, image resizing, and a cache with a TTL you choose.
- Create signed links for public image tags, asynchronous jobs with signed webhooks, and bulk captures of up to 100 URLs per call.
- Check usage through the usage API, retrieve the OpenAPI specification, and use parameter names accepted by other screenshot APIs to ease migration.
Before the capture, ScreenshotNeo accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports what happened. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Plans and billing
| Plan | Included screenshots per month | Price |
|---|---|---|
| Free | 1,000 | $0, no card |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Every feature is available on every plan. Yearly billing gives two months free. The free tier includes 1,000 screenshots each month without a card, and paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account to try it.
Frequently Asked Questions
Can page.evaluate return a DOM element for the next step?
Return a selector, boolean, string, number, or plain object instead. DOM elements and functions are not JSON-serializable values for the evaluate boundary.
Free tools Windows power users keep installed
One-click scans. No signup required.
Does one PhantomJS render call create multiple image files?
No. Each call writes one output, so a loop must invoke page.render once per state and provide a distinct filename.
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.

