Recommended Free Tools
When page.sendEvent('click', x, y) works for one link but not another, check two things first: whether x and y are viewport coordinates, and whether you capture the page before the second link has finished handling the event. Measure the target with getBoundingClientRect(), send the click at the rectangle’s center, and wait for a meaningful DOM, URL, load, or resource change before rendering.
Why one PhantomJS link appears to work
A PhantomJS click is an input event delivered at a coordinate. It is not a promise that the browser has completed the link’s JavaScript, navigation, animation, or network work. Two links on the same page can therefore behave differently even when the script sends the same kind of event.
- Different coordinate frame:
offsetLeftandoffsetTopare relative to an element’s offset parent.page.sendEventneeds the mouse position in the page viewport. - Different completion time: one link may change the DOM immediately while another starts asynchronous work. Rendering immediately after the event can show the old state.
- Page-specific behavior: an overlay, iframe, CSS transform, or event handler can affect the result. These are possibilities to inspect, not assumptions about your particular page.
Start with coordinates and timing because they are the two causes supported by the closest documented troubleshooting case. Then add instrumentation so the page itself tells you what happened.
Use viewport coordinates, not parent-relative offsets
Inspect the intended element inside page.evaluate and return only simple serializable data. PhantomJS’s evaluate boundary does not return DOM nodes, closures, or functions as usable values; return numbers and strings instead.
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 →#1 Best Overall
var target = page.evaluate(function () {
var el = document.querySelector('#products a.details');
if (!el) return { found: false };
var r = el.getBoundingClientRect();
return {
found: true,
left: r.left,
top: r.top,
width: r.width,
height: r.height,
right: r.right,
bottom: r.bottom,
href: el.getAttribute('href'),
text: el.textContent
};
});
if (!target.found) {
console.log('Target was not found');
phantom.exit(1);
}
console.log(JSON.stringify(target));
The rectangle is expressed in viewport coordinates. Click its center, which avoids relying on a corner that may be outside the clickable area:
var x = Math.round(target.left + target.width / 2);
var y = Math.round(target.top + target.height / 2);
page.sendEvent('click', x, y);
Before clicking, verify that the rectangle has a positive size and lies where you expect. A zero-sized rectangle, negative position, or unexpected dimensions usually means the element is hidden, not laid out yet, or a different matching element was selected.
| Measurement | Reference frame | Use for sendEvent? |
|---|---|---|
offsetLeft/offsetTop |
Element’s offset parent | No, unless you have converted them to viewport coordinates |
getBoundingClientRect().left/top |
Page viewport | Yes |
Rectangle center |
Viewport | Recommended click point |
A complete PhantomJS diagnostic script
This script loads a page, records the target rectangle, clicks the link, waits for either a URL change or a DOM-signature change, and renders only after the result is observable. Replace the URL and selector with values from your page.
var system = require('system');
var page = require('webpage').create();
var address = system.args[1] || 'https://example.com';
var selector = system.args[2] || '#products a.details';
var startUrl = '';
var beforeSignature = '';
var finished = false;
function finish(code) {
if (finished) return;
finished = true;
phantom.exit(code || 0);
}
page.onConsoleMessage = function (msg) {
console.log('[page console] ' + msg);
};
page.onError = function (msg, trace) {
console.log('[page error] ' + msg);
trace.forEach(function (t) { console.log(' ' + t.file + ':' + t.line); });
};
page.onResourceError = function (err) {
console.log('[resource error] ' + err.url + ' - ' + err.errorString);
};
page.onUrlChanged = function (url) {
console.log('[url changed] ' + url);
};
page.open(address, function (status) {
if (status !== 'success') {
console.log('Initial load failed: ' + status);
finish(1);
return;
}
startUrl = page.url;
var info = page.evaluate(function (sel) {
var el = document.querySelector(sel);
if (!el) return { found: false };
var r = el.getBoundingClientRect();
return {
found: true,
left: r.left, top: r.top, width: r.width, height: r.height,
href: el.getAttribute('href'),
signature: document.body.innerText.slice(0, 500)
};
}, selector);
if (!info.found || info.width <= 0 || info.height <= 0) {
console.log('Target is missing or has no visible size: ' + JSON.stringify(info));
finish(1);
return;
}
beforeSignature = info.signature;
var x = Math.round(info.left + info.width / 2);
var y = Math.round(info.top + info.height / 2);
console.log('Clicking ' + selector + ' at ' + x + ',' + y);
page.sendEvent('click', x, y);
var deadline = Date.now() + 5000;
var poll = window.setInterval(function () {
var state = page.evaluate(function () {
var el = document.querySelector('body');
return {
url: location.href,
signature: el ? el.innerText.slice(0, 500) : ''
};
});
if (state.url !== startUrl || state.signature !== beforeSignature) {
window.clearInterval(poll);
console.log('Observable result received');
page.render('after-click.png');
finish(0);
} else if (Date.now() >= deadline) {
window.clearInterval(poll);
console.log('No observable result within 5 seconds');
page.render('after-click-timeout.png');
finish(2);
}
}, 100);
});
Run it with the PhantomJS executable and pass the URL and CSS selector as arguments. The timeout is deliberately a diagnostic fallback. A production script should wait on the page’s actual completion signal when one exists.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Wait for the result your link is supposed to produce
DOM update
For dropdowns, tabs, accordions, and menus, poll a state that proves the action happened: a class, attribute, text value, or visible panel. A body-text signature is only a broad diagnostic. Prefer a specific selector when you know it.
var open = page.evaluate(function () {
var menu = document.querySelector('#menu');
return menu && menu.className.indexOf('open') !== -1;
});
Navigation
Record page.url before the event and use page.onUrlChanged or a load callback to detect navigation. Do not render merely because sendEvent returned; that method reports that the event was sent, not that the destination finished loading.
Network or resource activity
Use resource and navigation callbacks when the click starts an asynchronous request. Log request and error callbacks to learn whether the expected asset was requested or failed. If the page exposes no reliable signal, use a bounded polling loop and report a timeout rather than silently treating the click as successful.
Fixed delay
A short delay can confirm that timing is involved, but it is not a universal fix. A delay that is too short still captures stale content; one that is too long wastes time and can hide intermittent failures. Replace it with a state-based wait as soon as you identify the state.
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 errorsRank #3
Inspect the page before changing the click
- Confirm the load: check the return status from
page.openand logpage.url. - Record the executable version: the PhantomJS troubleshooting guidance recommends checking the version, especially when more than one installation may be present. Confirm which executable your job actually launches.
- Verify the selector: return the element’s text, link target, rectangle, and relevant attributes from
evaluate. - Check the viewport: set
page.viewportSizedeliberately if responsive layout could move or hide the link. - Capture errors: connect
page.onError,page.onConsoleMessage, andpage.onResourceError. Console messages inside the page are not displayed by default. - Compare links: inspect the working and failing elements with the same routine. Differences in rectangle, href, classes, or surrounding markup often reveal whether you selected the wrong node or clicked before layout settled.
Cases that need separate inspection
Overlays and covered targets
A consent panel, modal, or loading layer can occupy the target’s coordinates. Log visible overlays and temporarily hide them only for diagnosis; do not make that a production workaround unless it reflects the behavior you intend to automate.
CSS transforms and scrolling
Transforms and scrolling can make hand-calculated offsets unreliable. getBoundingClientRect() measures the rendered position after layout. Re-measure immediately before the click if an animation or lazy layout can move the element.
Frames
If the link is inside an iframe, the element’s rectangle belongs to that frame’s viewport. The available case does not establish iframe behavior as the cause, so treat it as a branch to investigate: identify the frame, switch your inspection strategy to its document, and verify coordinates in the context where the event is delivered.
Event-specific handlers
Some controls react to a sequence such as mouseover, mousedown, mouseup, and click, or use delegated handlers on an ancestor. Compare the working element’s event behavior with the failing one and watch page errors. Do not assume that a successful click on one anchor proves every other control uses the same handler.
Free tools Windows power users keep installed
One-click scans. No signup required.
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
Common symptoms, causes, and fixes
| Symptom | Likely explanation | Action |
|---|---|---|
| Screenshot is unchanged immediately after click | Rendering occurred before asynchronous work completed | Wait for a DOM, URL, load, or resource signal |
| Click lands beside the link | Parent-relative offsets were passed as viewport coordinates | Use getBoundingClientRect() and click the center |
| Selector returns an element with zero dimensions | Hidden, collapsed, or not-yet-laid-out target | Wait for visibility and re-measure |
| Script reports success but destination does not load | Navigation or resource failure | Log URL, navigation, resource, and page errors |
| Only one installed PhantomJS behaves differently | Different executable or version is running | Print the version and absolute executable path |
| Page logs errors you cannot see | Console output is not wired to the outer script | Attach page.onConsoleMessage and page.onError |
Make the script reliable in repeated runs
- Use stable selectors rather than positional selectors such as “the second link.”
- Set viewport dimensions, user agent, cookies, and other inputs explicitly when they affect layout or authentication.
- Keep waits bounded and return a nonzero exit code on timeout so automation can distinguish failure from a successful capture.
- Save the pre-click rectangle, URL, console output, page errors, and post-click state with the screenshot. This makes intermittent failures comparable.
- Re-measure after any action that can scroll, expand content, load images, or change responsive layout.
- Do not interpret a rendered image alone as proof of failure; pair it with URL and DOM evidence.
Or skip the browser setup
If your goal is a clean screenshot rather than debugging PhantomJS input, ScreenshotNeo provides a website screenshot API and MCP server. It accepts cookie and consent banners before capture, removes more than 60 known consent platforms, newsletter popups, and chat widgets, and lets you turn each cleanup step off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are free, with X-Page-Verdict and X-Billed headers explaining the result.
One GET request returns PNG, JPEG, WebP, or PDF. The parameter names used by other screenshot APIs also work, which can simplify a migration. See the ScreenshotNeo documentation for all options.
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}`);
It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients; full-page and element captures, dark mode, device presets, custom CSS and JavaScript, waits, request blocking, cookies and headers, geolocation, signed links, async webhooks, bulk capture of up to 100 URLs per call, and a usage API. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for the free plan.
FAQ
Does sendEvent click by CSS selector?
No. It sends a mouse event at the supplied coordinates. Find the element in the page, convert its rendered rectangle to a viewport point, and pass that point.
Should I always add a five-second sleep?
No. Use the page’s observable completion condition when possible. A bounded delay is useful for diagnosis, but it cannot account for every network or application state.
Best Value
What should I log when the coordinate fix does not help?
Log the PhantomJS version and executable, selector result, rectangle, URL changes, resource errors, page errors, and console messages. Then compare those records with the link that works.
Can this evidence identify the exact cause on my page?
No. Without the page and script, coordinate frame and timing are the first checks, not a universal diagnosis. Frame context, overlays, transforms, and page-specific event code require inspection of the actual DOM and behavior.
Frequently Asked Questions
Does sendEvent click by CSS selector?
No. It sends a mouse event at the supplied coordinates. Find the element in the page, convert its rendered rectangle to a viewport point, and pass that point.
Should I always add a five-second sleep?
No. Use the page’s observable completion condition when possible. A bounded delay is useful for diagnosis, but it cannot account for every network or application state.
What should I log when the coordinate fix does not help?
Log the PhantomJS version and executable, selector result, rectangle, URL changes, resource errors, page errors, and console messages. Then compare those records with the link that works.
Can this evidence identify the exact cause on my page?
No. Without the page and script, coordinate frame and timing are the first checks, not a universal diagnosis. Frame context, overlays, transforms, and page-specific event code require inspection of the actual DOM and behavior.
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.

