What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
If CasperJS returns null, throws an exception, or produces result links without titles, fix two separate JavaScript errors first: map over links, not the undefined link variable, and keep this.echo() outside evaluate(). Code executed by evaluate() runs in the page’s browser context, where this is the page window rather than the CasperJS object.
The corrected CasperJS extraction
This complete example searches Google, waits for result headings, extracts each result title and destination URL in the page context, then prints the returned data from the CasperJS context.
var casper = require('casper').create({
pageSettings: {
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120 Safari/537.36'
}
});
function getLinks() {
var links = document.querySelectorAll('h3.r a');
return Array.prototype.map.call(links, function (e) {
return {
title: e.textContent || e.innerText || '',
href: e.getAttribute('href')
};
});
}
casper.start('https://www.google.com/search?q=casperjs', function () {
this.echo('Search page opened');
});
casper.waitForSelector('h3', function () {
var results = this.evaluate(getLinks);
if (results === null) {
this.die('evaluate() failed; inspect the page and JavaScript errors.');
}
this.echo(JSON.stringify(results, null, 2));
}, function () {
this.die('No result heading appeared before the timeout.');
});
casper.run();
The important details are deliberately simple:
var linksis the same identifier passed toArray.prototype.map.call().- The function returns plain serializable objects rather than trying to print them.
this.echo()runs in the outer CasperJS step, wherethisis the CasperJS instance.
Why the original function returns null
1. The variable name is wrong
Declaring var links = document.querySelectorAll('h3.r a') and then calling Array.prototype.map.call(link, ...) references link, which was never declared. That raises a JavaScript error before the function can return an array. Change the argument to links.
// Wrong
return Array.prototype.map.call(link, function (e) { /* ... */ });
// Correct
return Array.prototype.map.call(links, function (e) { /* ... */ });
2. evaluate() changes execution context
CasperJS serializes a function and executes it inside the current page. Inside that function, this refers to the page’s window, not to CasperJS. The page window has no echo() method, so this.echo(...) throws a TypeError. An exception in the evaluated function prevents a normal return, which CasperJS exposes as null.
#1 Best Overall
Treat evaluate() as a boundary. Read the DOM, transform it into strings, numbers, booleans, arrays, or plain objects, and return that data. Perform logging, assertions, navigation, screenshots, and CasperJS control flow after evaluate() returns.
function readPage() {
return {
title: document.title,
links: Array.prototype.map.call(
document.querySelectorAll('a'),
function (a) {
return {
text: a.textContent || '',
href: a.href
};
}
)
};
}
casper.then(function () {
var pageData = this.evaluate(readPage);
this.echo(JSON.stringify(pageData));
});
Make the selector and wait condition reliable
Do not depend blindly on h3.r
The h3.r selector targets an older Google result structure. Google changes classes, wrappers, consent screens, and other markup. A script can therefore execute successfully and still return an empty array because no element matches that selector.
Wait for a node you actually intend to extract, then verify that the same node exists in the captured HTML. A broad h3 wait is useful for diagnosis, but production code should use the result selector that your target response contains.
casper.waitForSelector('h3', function () {
this.echo('A heading exists');
var html = this.getHTML();
this.echo(html.substring(0, 2000));
var results = this.evaluate(getLinks);
this.echo(JSON.stringify(results));
});
fetchText() is another option when you only need visible text and not a structured object. For example:
casper.waitForSelector('h3', function () {
this.echo(this.fetchText('h3'));
});
Use evaluate() when title and URL must remain paired. Use fetchText() for a quick text check or a diagnostic printout.
Rank #2
Diagnose an empty array versus a failed evaluation
| Observed result | Likely cause | Next check |
|---|---|---|
null |
An exception occurred inside evaluate(), such as the undefined link variable or this.echo(). |
Remove CasperJS calls from the evaluated function and correct the variable name. |
[] |
The function ran, but the selector matched no elements. | Print this.getHTML() and inspect the actual result markup. |
| Titles are empty but URLs exist | The selected anchor contains no direct text, or visible text is supplied by a nested node or alternate attribute. | Inspect the anchor’s HTML and use textContent, innerText, or a more specific descendant selector. |
| The wait times out | The response is a consent page, CAPTCHA, blocked request, alternate locale, or a page whose result nodes use different markup. | Capture and inspect the returned HTML and page URL before changing selectors. |
Check the page CasperJS actually received
A successful screenshot does not prove that your selector matches the DOM used by the extraction function. A screenshot shows rendered pixels; querySelectorAll() searches the current document tree. The two can differ when content is injected later, hidden behind a consent flow, or rendered by a different response.
- Wait for a node that should exist on the target response.
- Print
this.getCurrentUrl()and a useful slice ofthis.getHTML(). - Look for consent text, CAPTCHA wording, an interstitial, or a different Google result layout.
- In a browser developer console, test the exact selector against the same response.
- Only then update
getLinks().
Keep the extraction function free of CasperJS references so that failures remain local and understandable:
function getLinks() {
var nodes = document.querySelectorAll('h3 a');
var output = [];
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
output.push({
title: (node.textContent || node.innerText || '').replace(/^s+|s+$/g, ''),
href: node.getAttribute('href')
});
}
return output;
}
This version uses a neutral h3 a selector only as an example. Replace it with the selector confirmed in your captured response; do not assume that a historical class remains present.
What a “missing title” can mean
Your scraper’s title
The title property in the returned object is text read from the result anchor in Google’s current DOM. It can be empty if the chosen node has no text, if text is nested elsewhere, or if the response is not a normal results page.
Google’s title link
Google’s displayed title link is generated from several signals, including the destination page’s <title>, its main visual title and headings, og:title, prominent text, anchor text pointing to the page, and WebSite structured data. Google can replace the displayed wording when it considers a title inaccurate, obsolete, duplicated, or otherwise problematic. Therefore, changing your CasperJS selector cannot force Google to use a different title.
If you own the destination site
Use a descriptive, concise <title>, a distinctive main heading, and consistent language. After changes, allow time for Google to recrawl and reprocess the page; that can take days to weeks. Scraping the current result tells you what Google displayed, not which single source produced it.
Common errors and fixes
“ReferenceError: link is not defined”
Use the declared plural variable: map.call(links, ...).
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 errors“TypeError: this.echo is not a function”
Move the log statement outside evaluate(). Return the value, assign it to a variable in the CasperJS callback, and call this.echo() there.
evaluate() returns null after the spelling fix
There is still an exception in the page function, or the function is not returning a serializable value. Remove DOM methods one at a time, return a small object, and avoid returning DOM elements, functions, or circular objects.
An empty array persists
The selector probably does not match this response. Print the HTML after the wait and check for consent, CAPTCHA, redirects, and changed markup.
Rank #4
The script works intermittently
Waiting for a generic page event may race the result insertion. Wait for the result node, or use a bounded retry strategy that rechecks the selector. Keep the timeout finite so blocked responses fail visibly instead of hanging.
Recommended Free Tools
Or skip the browser setup
If your real requirement is a clean image or PDF of a URL rather than DOM-level Google extraction, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
See the full parameter reference in the ScreenshotNeo documentation. 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(`${res.status} ${res.statusText}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));
The service also supports full-page lazy-image capture, CSS-selector element capture, device presets and custom viewports, dark mode, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, pre-capture clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs are accepted to ease migration.
There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Create a free ScreenshotNeo account to try it.
Free tools Windows power users keep installed
One-click scans. No signup required.
Performance, reliability, and cost considerations
- Reuse one CasperJS process for a batch of URLs instead of starting a browser for every result.
- Wait for a specific, meaningful node; waiting for an arbitrary delay is slower and still unreliable.
- Store the captured HTML when debugging so selector changes can be compared with the response that failed.
- Treat CAPTCHA, consent, and redirect pages as distinct outcomes, not as “no titles.”
- For ScreenshotNeo, inspect
X-Page-VerdictandX-Billedheaders so your accounting distinguishes clean captures from non-billable failures and cache hits.
FAQ
Can I call CasperJS methods from any callback passed to evaluate()?
No. The evaluated function runs in the page context. Return data from it and call CasperJS methods in the surrounding CasperJS step.
Best Value
Should I use innerText or textContent?
Use both with a fallback, as in the example. textContent is broadly available; innerText more closely reflects rendered text but can depend on layout.
Why does Google show a title different from my page’s <title>?
Google composes title links from multiple page and link signals and may rewrite them when its systems detect a poor or outdated title.
Frequently Asked Questions
Does a screenshot prove that CasperJS can find the title node?
No. A screenshot shows rendered pixels, while the selector runs against the document tree received by CasperJS. Inspect the captured HTML and test the selector directly.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →What should my function return from evaluate()?
Return serializable primitives, arrays, or plain objects such as `{title: ‘…’, href: ‘…’}`. Do not return DOM nodes, functions, or circular objects.
The Bottom Line
Fix the plural variable, keep CasperJS logging outside evaluate(), and verify the selector against the actual response. Those three practices distinguish a JavaScript-context failure from a changed Google page.
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.




