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 →Use Selenium’s By locator with findElement when you need one match, or findElements when zero or more matches are valid. In Selenium 3, the practical order is usually a unique ID, a concise CSS selector, a stable name or class, link text, tag name, and finally XPath for relationships or conditions CSS cannot express.
The examples below show the legacy PhantomJS 2.1.1 integration in JavaScript and Python, explain why elements are missed, and show how to migrate the same locators to a maintained headless browser.
What “finding an element” means in Selenium 3
Selenium does not search for visible text in the abstract. It queries the page’s DOM using a locator strategy. The By object describes that strategy, and the WebDriver call performs the search.
findElementreturns the first matching element. If nothing matches, Selenium raises a no-such-element error.findElementsreturns a collection. If nothing matches, the collection is empty, which is useful when zero matches is a normal result.
Finding and interacting are separate operations. An element can exist in the DOM yet be hidden, covered by another element, disabled, or outside the current iframe, so a successful lookup does not guarantee that a click or keystroke will work.
#1 Best Overall
Legacy setup: Selenium 3 with PhantomJS 2.1.1
PhantomJS 2.1.1 is a headless browser built on an older WebKit engine. Its embedded GhostDriver can expose a WebDriver endpoint with:
phantomjs --webdriver=8910
The documented default endpoint is 127.0.0.1:8910. PhantomJS 2.1 was released on January 23, 2016. Selenium later deprecated and removed native PhantomJS support because its WebDriver implementation was no longer actively developed. Treat the recipes here as maintenance guidance for an existing Selenium 3 suite, not a good foundation for a new test project.
For a new suite, use a maintained headless Chrome or Firefox driver. The locator concepts—By, findElement, and findElements—transfer directly, so migration normally changes browser setup rather than every selector.
JavaScript: locate IDs, CSS selectors, and collections
This Selenium 3-style example opens a page, finds a username by ID, finds a password field with CSS, and collects every result element.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsconst {Builder, By} = require('selenium-webdriver');
(async function () {
const driver = await new Builder().forBrowser('phantomjs').build();
try {
await driver.get('https://example.test/login');
const username = await driver.findElement(By.id('username'));
const password = await driver.findElement(By.css('input[name="password"]'));
const results = await driver.findElements(By.css('.result'));
await username.sendKeys('alice');
await password.sendKeys('secret');
console.log(`Found ${results.length} result elements`);
} finally {
await driver.quit();
}
})();
The phantomjs browser target depends on a Selenium 3-era JavaScript binding and a compatible PhantomJS installation. A current Selenium package may no longer include this target, so pin the versions required by the legacy project instead of assuming that the newest package will work.
Python: the Selenium 3 PhantomJS binding
In Python environments that still expose the PhantomJS binding, use the By-based API:
from selenium import webdriver
from selenium.webdriver.common.by import By
# In Selenium 3 environments that still expose the PhantomJS binding:
driver = webdriver.PhantomJS(executable_path='/path/to/phantomjs')
try:
driver.get('https://example.test/login')
username = driver.find_element(By.ID, 'username')
password = driver.find_element(By.CSS_SELECTOR, 'input[name="password"]')
results = driver.find_elements(By.CSS_SELECTOR, '.result')
print(len(results))
finally:
driver.quit()
Use the current project’s exact Selenium 3-compatible package and binary versions. If webdriver.PhantomJS is missing, that is a support-status problem rather than a selector problem; migrate the driver rather than trying random locator changes.
Rank #2
Choose the locator that will survive page changes
| Strategy | JavaScript | Python | Best use | Maintenance notes |
|---|---|---|---|---|
| ID | By.id('username') |
By.ID, 'username' |
A unique, stable HTML id |
Preferred when the ID is genuinely unique and does not change on every render. |
| CSS selector | By.css('form input[name="email"]') |
By.CSS_SELECTOR, 'form input[name="email"]' |
Compact combinations of tags, classes, IDs, and attributes | Usually the best fallback; scope it to a stable container. |
| Name | By.name('email') |
By.NAME, 'email' |
Inputs with a stable name attribute |
Confirm that repeated names are intentional. |
| Class name | By.className('information') |
By.CLASS_NAME, 'information' |
One class token shared by a component | Do not pass a compound string such as "card primary" to the traditional class-name strategy; use CSS for multiple classes. |
| Link text | By.linkText('Sign in') |
By.LINK_TEXT, 'Sign in' |
An anchor whose complete rendered text is stable | Applies to links, and exact wording changes can break it. |
| Partial link text | By.partialLinkText('Sign') |
By.PARTIAL_LINK_TEXT, 'Sign' |
An anchor when only part of its text is stable | Broad matches can select the wrong link. |
| Tag name | By.tagName('button') |
By.TAG_NAME, 'button' |
Collecting a set of elements by semantic tag | Often matches many nodes; combine with a container or filter. |
| XPath | By.xpath('//form//input[@name="email"]') |
By.XPATH, '//form//input[@name="email"]' |
Relationships, conditions, and structures CSS cannot express | Powerful but generally harder to read and debug. |
Prefer a stable ID
A unique ID is direct and readable:
const save = await driver.findElement(By.id('save')); // JavaScript
# driver.find_element(By.ID, 'save') # Python
Do not choose an ID that is generated differently on every load. Inspect several real page loads before treating it as stable.
Use CSS for a precise fallback
CSS can express an element, an attribute, and its scope without a long DOM path:
By.css('#checkout button.submit')
By.css('[data-testid="save"]')
By.css('form input[name="email"]')
A selector anchored to a stable component is less fragile than a selector that walks through every intermediate div.
Use class names correctly
By.className accepts one class token. For an element carrying both card and primary, use By.css('.card.primary') rather than a space-separated class-name argument.
Reserve XPath for relationships
XPath is appropriate when you need an ancestor, sibling, or conditional relationship, for example:
By.xpath('//form//input[@name="email"]')
By.xpath('//label[normalize-space()="Email"]/following::input[1]')
Keep XPath short enough to explain. Absolute paths such as /html/body/div[2]/div[1]/form/input break whenever layout markup changes.
Waiting for elements inserted after navigation
driver.get() means navigation was requested and the document became available; it does not guarantee that an application has finished rendering an asynchronous component. If a result is inserted later, wait for a condition instead of adding an arbitrary long sleep.
Rank #3
In a Selenium 3 binding, use its explicit-wait facility to wait for presence or visibility. The exact helper names vary by language version, but the condition should describe the state you need:
- Presence: the node exists in the DOM and can be read.
- Visibility: the node is displayed and has usable dimensions.
- Element to be clickable: visibility plus an enabled state, where supported.
Keep the timeout finite and capture the page state when it expires. A wait cannot fix a selector that never matches.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Frames, shadow boundaries, and page context
Switch into an iframe
If the target is inside an iframe, searching from the top-level document will always fail. Locate the frame, switch into it, and then search:
const frame = await driver.findElement(By.css('iframe#payment'));
await driver.switchTo().frame(frame);
const cardNumber = await driver.findElement(By.name('cardnumber'));
await driver.switchTo().defaultContent();
Switch back to the default content before looking for elements outside that frame. A frame’s DOM is a separate document.
Account for older browser-engine behavior
PhantomJS uses an old WebKit engine. Modern JavaScript, layout, promises, and browser APIs may behave differently or fail before Selenium ever evaluates your locator. If the same page works in current Chrome but not PhantomJS, inspect console errors and the rendered DOM in PhantomJS before rewriting selectors.
Shadow DOM support and other modern platform features are another reason to prefer a maintained browser for new automation. If the application depends on features PhantomJS cannot represent, a locator change is not a reliable solution.
Recommended Free Tools
Why Selenium says it cannot find an element
The selector does not match the rendered DOM
Check spelling, quoting, case, attribute values, and whether the element is actually present after scripts run. Test the smallest stable selector first, then add scope.
Rank #4
The page has not finished rendering
Use an explicit wait for the relevant condition. Avoid making every test sleep for a fixed number of seconds: it slows fast runs and still fails on slower pages.
You are in the wrong document
Switch into the correct iframe before searching, and return to the default content afterward.
The element exists but is hidden
A hidden template node can be found but cannot be interacted with. Wait for visibility or the application’s displayed state. Also check whether a cookie banner, modal, or overlay is covering the control.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11The selector is too broad
By.tagName('button') can return many buttons, and partial link text can match an unintended anchor. Scope the search to a stable form, panel, or component.
The page is blocked or different in automation
Bot defenses, authentication redirects, user-agent checks, and network failures can produce a page that does not contain the expected markup. Save the current URL and page source on failure so you can distinguish a bad selector from a different response.
The PhantomJS driver itself is unsupported
Selenium removed native PhantomJS support because its implementation was no longer actively developed. Errors during session creation, missing browser targets, or incompatibilities with modern pages should trigger a migration to headless Chrome or Firefox, not an endless search for another locator syntax.
Debugging with findElements
When you are unsure whether a component is present, use a collection lookup and inspect its length:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
const matches = await driver.findElements(By.css('[data-testid="notice"]'));
if (matches.length === 0) {
console.log('Notice is not present on this response');
}
This avoids treating an expected absence as an exception. Use findElement when the element is required and its absence should fail the test immediately.
Best Value
Performance and reliability practices
- Prefer one stable locator over a long chain of DOM descendants.
- Search from a stable container when a page contains repeated components.
- Wait for a meaningful state rather than polling with multiple unrelated selectors.
- Use
findElementsfor optional collections and check the returned count. - Quit the driver in a
finallyblock so failed tests do not leave PhantomJS processes running. - Record the URL, selector, page source, and screenshot when a lookup fails.
- Run the legacy suite against a pinned browser binary; changing PhantomJS or Selenium versions can alter parsing and WebDriver behavior.
Migration path to headless Chrome or Firefox
Keep your locator calls and replace the browser construction with the maintained browser’s WebDriver setup. Start with a small representative test: navigation, an ID lookup, a CSS lookup, an iframe switch, and an asynchronous wait. Compare the resulting DOM and timing before migrating the full suite.
Expect some selectors to expose genuine browser differences. A current engine may execute scripts PhantomJS cannot, while an old page may rely on WebKit behavior that no longer exists. Treat those as application-compatibility findings and update the test intentionally.
Or skip the browser setup
If your actual goal is a clean image or PDF of a page rather than DOM interaction, ScreenshotNeo provides a single HTTP request. It accepts consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.
See the complete parameter reference in the ScreenshotNeo documentation. A cURL capture is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
The equivalent Python request is:
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)
And 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}`);
ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Other available controls include full-page screenshots with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper size and page ranges, custom CSS and JavaScript, clicks, selector waits, delays, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs also work to ease switching.
The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try it.
FAQ
Can PhantomJS run without a visible desktop?
Yes. PhantomJS is headless, and its embedded GhostDriver exposes WebDriver through the --webdriver option. The limitation is its age and discontinued Selenium integration, not the absence of headless operation.
Should I rewrite every locator when moving to Chrome or Firefox?
No. Keep the By, findElement, and findElements calls first. Change the driver setup, then investigate only selectors affected by genuine browser or application differences.
Frequently Asked Questions
Can PhantomJS run without a visible desktop?
Yes. PhantomJS is headless, and its embedded GhostDriver exposes WebDriver through the --webdriver option. The limitation is its age and discontinued Selenium integration, not the absence of headless operation.
Should I rewrite every locator when moving to Chrome or Firefox?
No. Keep the By, findElement, and findElements calls first. Change the driver setup, then investigate only selectors affected by genuine browser or application differences.
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.
Free tools Windows power users keep installed
One-click scans. No signup required.

