Recommended Free Tools
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
If PhantomCSS saves ten screenshots but every file shows the first page, the loop is usually running synchronously inside one CasperJS callback while navigation and rendering are asynchronous. Queue one CasperJS step per iteration, change the page inside that step, wait for a page-specific ready condition, and then capture with a unique name. A fixed delay can mask the race, but a condition-based wait is safer.
Why every iteration captures the first page
PhantomCSS is a CasperJS module that takes screenshots and compares them with baseline images through Resemble.js. CasperJS executes its then callbacks in order, but JavaScript code inside one callback still runs immediately. A for loop can therefore issue ten page-change requests and ten capture calls before the browser has completed even the first transition.
The later captures then observe the same DOM state, or a partially updated state, instead of pages 2 through 10. This is an orchestration problem, not normally a PhantomCSS filename or comparison problem.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The reliable pattern: queue, change, wait, capture
Create a separate CasperJS step for each target page. In that step, trigger the application’s page change, wait until the application exposes proof that the requested page is ready, and only then call phantomcss.screenshot.
#1 Best Overall
var firstPage = 1;
var lastPage = 10;
for (var pageNo = firstPage; pageNo <= lastPage; pageNo++) {
(function (targetPage) {
casper.then(function () {
this.evaluate(function (page) {
moveNext(page); // application-specific page change
}, targetPage);
this.waitFor(function () {
return this.evaluate(function (page) {
var indicator = document.querySelector('#page-number');
return indicator &&
indicator.textContent.trim() === String(page);
}, targetPage);
}, function () {
phantomcss.screenshot('html', 'page-' + targetPage);
}, function () {
this.die('Timed out waiting for page ' + targetPage);
}, 10000);
});
}(pageNo));
}
casper.run();
moveNext and #page-number are placeholders. Replace them with the function and readiness marker used by your application. The immediately invoked function expression preserves targetPage for older JavaScript runtimes; without it, callbacks may all refer to the final loop value.
Use the application’s strongest readiness signal
- Page indicator: wait for a pager element to contain the requested number.
- Unique text: wait for a heading or record label that only exists on that page.
- Target element: wait for the selector that appears after the transition.
- Resource completion: wait for the request or asset that supplies the new content.
CasperJS wait methods are not generally chainable; place a wait inside a casper.then step when you need it to participate in the ordered queue. Always provide a timeout callback so a missing page fails loudly instead of producing a plausible but wrong image.
Adapt the loop to common pagination models
Clicking a Next button
for (var pageNo = 1; pageNo <= 10; pageNo++) {
(function (targetPage) {
casper.then(function () {
if (targetPage > 1) {
this.click('#next');
}
this.waitForSelector('#results[data-page="' + targetPage + '"]',
function () {
phantomcss.screenshot('#results', 'results-page-' + targetPage);
},
function () {
this.die('Results page ' + targetPage + ' did not load');
}, 10000);
});
}(pageNo));
}
Use an attribute, text node, or other marker that changes on every click. Waiting only for #results is insufficient if that element exists on page 1 and is merely repopulated.
Free tools Windows power users keep installed
One-click scans. No signup required.
Changing a URL or route
for (var pageNo = 1; pageNo <= 10; pageNo++) {
(function (targetPage) {
casper.thenOpen('https://example.test/list?page=' + targetPage);
casper.then(function () {
this.waitForSelector('#page-number', function () {
var actual = this.fetchText('#page-number').trim();
if (actual !== String(targetPage)) {
this.die('Expected page ' + targetPage + ', got ' + actual);
}
phantomcss.screenshot('html', 'page-' + targetPage);
}, function () {
this.die('Timed out on page ' + targetPage);
}, 10000);
});
}(pageNo));
}
For route navigation, the navigation step itself is asynchronous, so the capture still belongs in a later queued step after the readiness check.
Fixed delays versus condition-based waits
| Approach | When it helps | Risk |
|---|---|---|
| Fixed delay | A simple page with a known, stable render time | The delay may be too short on a slow run or waste time on a fast run; an eight-second value from one historical report is not a universal setting. |
| Condition-based wait | Pages exposing a page number, selector, text, or resource signal | Requires a marker that truly changes for each state; a weak marker can pass before rendering is complete. |
If no reliable marker exists, a short delay can be a fallback, but combine it with an assertion on the resulting state whenever possible. Do not copy a delay from another site or machine without measuring your own application.
Make each screenshot auditable
Pass an explicit name such as page-1, page-2, and page-10. Default generated names such as screenshot_0.png make it harder to match an image to an iteration and to select the correct baseline. Include other stable identifiers when useful, for example orders-page-3-dark.
During diagnosis, log the requested page and the observed marker immediately before capture:
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 errorsthis.echo('Expected ' + targetPage + ', observed ' +
this.fetchText('#page-number').trim());
If the log is correct but files still look identical, inspect the capture selector, CSS state, and baseline comparison. If the log is wrong, fix navigation or the wait condition first.
Keep visual regression runs deterministic
- Use static fixtures or fake data when records, ads, timestamps, or rotating recommendations can change between runs.
- Freeze the viewport, device scale, locale, timezone, and authentication state used by the baseline.
- Hide animation or wait until transitions finish before capturing.
- Ensure lazy-loaded images and fonts have completed before the readiness marker is considered valid.
- Run one page manually and verify the marker changes before scaling to ten or more iterations.
PhantomCSS comparisons are most useful when the input UI is predictable. Mutable content can create visual diffs even after the loop is correctly synchronized.
Diagnostic checklist for identical files
- Confirm the loop schedules steps. The
forloop should add callbacks; it should not perform all navigation and captures directly inside one callback. - Confirm the closure value. Log
targetPageinside the callback. Every step must print a different value. - Confirm the page-change operation. Check that the click, route assignment, or application function actually changes the page.
- Confirm the readiness marker. It must update for every target page, not merely exist from page 1.
- Confirm timeout handling. A timeout should stop the run with the page number, not continue to capture stale content.
- Confirm the screenshot name. Names must include the iteration or another unique state identifier.
- Confirm the capture target. Capturing
htmland capturing a component can expose different state or styling. - Confirm deterministic data. Remove timestamps, random ordering, and changing network content from regression fixtures.
Common failures and precise fixes
All callbacks use the last page number
Cause: the loop variable is closed over by callbacks in an older runtime. Fix: wrap each iteration in a function, as shown above, or otherwise create a per-iteration value.
The wait passes immediately
Cause: the selector or text already exists on the previous page. Fix: wait for a value containing the requested page, a changed data attribute, or a unique element; clear or invalidate stale content before navigation if necessary.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Intermittent timeouts
Cause: the timeout is shorter than occasional network or rendering time, or the readiness signal depends on a failed request. Fix: inspect network and console behavior, choose a signal tied to successful rendering, and set a timeout appropriate for the application. Do not simply increase the timeout indefinitely.
Correct pages, unstable diffs
Cause: mutable data, animations, delayed images, or environment differences. Fix: use fixtures, disable motion, wait for assets, and keep runtime settings constant.
The code works locally but not in CI
Cause: different PhantomJS/CasperJS versions, fonts, viewport dimensions, credentials, or network speed. Fix: pin the runtime where possible, record environment settings, and make waits state-based rather than tuned to a local delay. PhantomCSS and CasperJS are historical tools; verify that their versions run on your current environment before committing to a new test suite.
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 if you need rendered pages without maintaining a CasperJS/PhantomJS loop. A separate request can capture each URL or route after your application exposes it.
One GET request returns PNG, JPEG, WebP, or PDF. The API accepts options for full-page captures, lazy images, CSS selectors, device and viewport settings, custom JavaScript and CSS, waits for selectors, delays or network idle, cookies and headers, blocking resources, and caching. For an asynchronous batch, its bulk capture call accepts up to 100 URLs; signed webhooks can report completion.
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 ScreenshotNeo documentation for the complete parameter list and response headers. The same call in Python is:
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)
And in 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 failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and whether the request was billed.
- An MCP server exposes
take_screenshot,get_page_info, andcapture_pdfto Claude, Cursor, and other MCP clients. - The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan.
Sign up for ScreenshotNeo to try the free 1,000-shot plan without a card.
Rank #4
FAQ
Does PhantomCSS itself make the loop asynchronous?
No. PhantomCSS captures when its call runs; CasperJS must schedule the surrounding navigation, wait, and capture operations in its ordered step queue.
Can I capture an element instead of the whole page?
Yes. Pass the element selector to phantomcss.screenshot, but still wait for that element’s content and styling to represent the requested page.
How do I know whether the bug is navigation or capture?
Log the page marker immediately before each capture and open the generated files using their unique names. A wrong marker indicates navigation or waiting; a correct marker with identical crops points to the capture target or page styling.
Frequently Asked Questions
Does PhantomCSS itself make the loop asynchronous?
No. CasperJS must schedule navigation, waiting, and capture in ordered steps.
Can I capture an element instead of the whole page?
Yes; pass its selector, while waiting until its content and styling are ready.
How do I separate navigation errors from capture errors?
Log the page marker before capture and inspect uniquely named files; the marker identifies which stage is wrong.
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.

