DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
CasperJS

How to Fix CasperJS Google Search Results with Missing Link Titles

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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 links is the same identifier passed to Array.prototype.map.call().
  • The function returns plain serializable objects rather than trying to print them.
  • this.echo() runs in the outer CasperJS step, where this is 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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.

  1. Wait for a node that should exist on the target response.
  2. Print this.getCurrentUrl() and a useful slice of this.getHTML().
  3. Look for consent text, CAPTCHA wording, an interstitial, or a different Google result layout.
  4. In a browser developer console, test the exact selector against the same response.
  5. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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, ...).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

“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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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-Verdict and X-Billed headers 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.