Remove the extra call. Selenium’s find_element() method returns a WebElement, not another function. Call the lookup once, then call an element method such as click() or read a property such as text:
from selenium.webdriver.common.by import By
element = driver.find_element(By.XPATH, "//button[@type='submit']")
element.click()
If your traceback says TypeError: 'WebElement' object is not callable, inspect the exact line for a trailing () after the lookup result or after a variable that already contains the element.
What the error means
In Python, putting parentheses after an object means “call this object as a function.” A Selenium lookup does not return a function. The documented find_element method returns the first matching WebElement; find_elements returns a list of matching WebElement objects. Selenium describes a WebElement as representing a DOM element, with operations such as clicking, clearing, and sending keys rather than function-call behavior.
That is why these expressions fail:
# The lookup already ran; the final () tries to call the WebElement
driver.find_element(By.XPATH, "//button[@type='submit']")()
# element is a WebElement, so element() is also invalid
element = driver.find_element(By.XPATH, "//button[@type='submit']")
element()
The normal form is to make one lookup and then use the returned object according to its type:
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 →#1 Best Overall
from selenium.webdriver.common.by import By
element = driver.find_element(By.XPATH, "//button[@type='submit']")
element.click()
label = driver.find_element(By.XPATH, "//h1")
print(label.text)
The Selenium API documents the lookup signatures and return types in its WebDriver documentation. The WebElement API is described in the project’s WebElement documentation.
Correct XPath lookup syntax in Selenium 4
Use the Selenium 4 locator form: import By, pass By.XPATH, and provide the XPath string as the second argument.
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com")
heading = driver.find_element(By.XPATH, "//h1")
print(heading.text)
driver.quit()
Remove the accidental leading space before driver if you copy the example into a file; it is shown only to keep the lines visually grouped here. A clean runnable version is:
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
When writing your own script, keep statements at the same indentation level unless they are inside a function or control block:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
from selenium import webdriver
from selenium.webdriver.common.by import By
browser = webdriver.Chrome()
browser.get("https://example.com")
heading = browser.find_element(By.XPATH, "//h1")
print(heading.text)
browser.quit()
Selenium’s locator migration guidance recommends driver.find_element(By_object, "some_locator") instead of the older convenience methods such as find_element_by_xpath(...). See the Selenium project’s Python locator migration article and the current By documentation.
Rank #2
Fix the specific call site
- Read the complete traceback. Find the file name, line number, and expression Python identifies. The error is reported at the call site; changing unrelated XPath strings before examining that line can hide the real problem.
- Read the expression from left to right. Confirm that
find_elementhas its locator arguments once and that no second pair of parentheses follows the closing parenthesis of the lookup. - Assign the result to a descriptive variable. Names such as
submit_buttonorprofile_headingmake it clearer that the variable contains an element, not a function. - Use a WebElement method or property. Use
click(),clear(), orsend_keys(...)when you need an operation. Usetextwhen you need the element’s visible text. Do not writeelement(). - Run the smallest corrected example. Confirm that the lookup and the intended element operation work before adding waits, loops, or additional page actions.
Common before-and-after corrections
| Incorrect expression | Correct expression | Why |
|---|---|---|
driver.find_element(By.XPATH, "//input")() |
driver.find_element(By.XPATH, "//input").clear() |
The lookup returns an element; call the element method you need. |
button() |
button.click() |
button already refers to a WebElement. |
title() |
title.text |
text is a property, not a method. |
driver.find_elements(By.XPATH, "//a")() |
links = driver.find_elements(By.XPATH, "//a") |
The plural lookup returns a list; process that list explicitly. |
Choose find_element or find_elements
The two methods have different result shapes. Choose based on what the next line of your program should receive.
| Method | Return value | Use it when | Example |
|---|---|---|---|
find_element(By.XPATH, expression) |
The first matching WebElement |
One matching control or node is expected | button = driver.find_element(By.XPATH, "//button[1]") |
find_elements(By.XPATH, expression) |
A list of matching WebElement objects |
You need to inspect or process every match | rows = driver.find_elements(By.XPATH, "//table//tr") |
Neither return value is callable. For a list, iterate or index it deliberately:
rows = driver.find_elements(By.XPATH, "//table//tr")
for row in rows:
print(row.text)
if rows:
first_row = rows[0]
print(first_row.text)
If your code expects exactly one result but you use find_elements, you may later try to call the list or pass it where one element is expected. Conversely, switching to find_element does not make an XPath that matches nothing valid; it changes the return type and failure behavior. Keep the result-shape decision separate from XPath debugging.
Recommended Free Tools
Separate a call-site error from an XPath or page error
TypeError: 'WebElement' object is not callable means Python attempted to call a WebElement. It does not, by itself, prove that the XPath is malformed. After removing the extra call, other failures can still occur, but they belong to different categories:
- Invalid XPath: the XPath expression cannot be parsed. Check quotes, brackets, axes, and predicates.
- No matching element: the page does not contain a match at the moment of lookup. Check the URL, frame context, navigation state, and whether the element is inserted later.
- Element not usable yet: a match may exist but not be ready for the intended action. Use an appropriate explicit wait for the condition your action requires.
- Stale reference: the page replaced the node after you located it. Locate the element again after the update instead of calling the old reference.
- Wrong result shape: code treats a list from
find_elementsas a single element, or treats a single WebElement as a collection.
Fix or rule out the Python call-site problem first. Only then investigate page state and locator behavior; otherwise you may rewrite a correct XPath while leaving the extra parentheses in place.
Reliable patterns for real scripts
Keep lookup and action on separate lines
Separating the operations makes the return type visible and gives the traceback a precise location:
submit_button = driver.find_element(
By.XPATH,
"//button[@type='submit']"
)
submit_button.click()
Use an explicit wait when the page changes asynchronously
A wait addresses timing, not callability. For a clickable control, Selenium’s expected-conditions pattern can be used after the lookup syntax is correct:
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 problemsfrom selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
submit_button = wait.until(
EC.element_to_be_clickable((By.XPATH, "//button[@type='submit']"))
)
submit_button.click()
The object returned by until in this pattern is still used as an element; do not append another pair of parentheses after the result.
Check the type while diagnosing
When a variable’s origin is unclear, print its type before using it:
value = driver.find_element(By.XPATH, "//h1")
print(type(value))
print(value.text)
This confirms whether the variable contains a WebElement, a list, a string, or something produced by your own helper function. It is a diagnostic check, not a substitute for choosing the correct Selenium operation.
Wrap lookups in helpers without changing their return type accidentally
A helper can return the element directly:
def find_submit(driver):
return driver.find_element(By.XPATH, "//button[@type='submit']")
submit_button = find_submit(driver)
submit_button.click()
Do not write find_submit(driver)(); the helper has already returned the WebElement. If a helper is intended to perform an action, give it an action-oriented name and perform the action inside it instead.
Troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
WebElement object is not callable on a line ending in () |
An extra call operator was applied to a lookup result or element variable. | Remove the final () and replace it with the intended method, such as .click(), or property, such as .text. |
| The error appears after changing to a helper function | The helper returns a WebElement, but the caller treats it as a function. | Call the helper once, assign its result, and use a WebElement operation. |
| The code now raises a no-such-element exception | The callability issue is fixed, revealing a separate locator or page-state problem. | Verify the current URL, frame/window context, XPath, and element timing. |
| A loop fails when processing matches | The code expected one element but received a list, or indexed a result incorrectly. | Use find_element for one result, or iterate over the list from find_elements. |
Old find_element_by_xpath code is rejected |
The script uses the pre-Selenium-4 locator style. | Import By and use driver.find_element(By.XPATH, "..."). |
When a failure remains, preserve the full traceback and the exact source line. The error message identifies what Python tried to call; it does not identify the author’s intended XPath, so an exact diagnosis of the remaining problem requires those details.
Version context and documentation
The Selenium locator migration article was published in 2022 and explains the move from legacy Python locator methods to the find_element(By, locator) form. The current WebDriver and By API pages identified for this guide are Selenium 4.49.0 documentation. The WebElement page surfaced is version 4.33.0; for version-sensitive behavior, consult the documentation that matches the Selenium package installed in your environment. These version labels describe documentation editions, not a performance measurement or a claim that every installed package is that version.
Or skip the browser setup
If your goal is to obtain a clean image or PDF of a page rather than interact with controls, ScreenshotNeo makes a single HTTP request to capture it. It is not a replacement for Selenium when you need clicks, form submissions, or application-state testing, but it avoids maintaining a browser driver for a static capture.
cURL (the API documentation is at https://screenshotneo.com/docs/):
Best Value
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)
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}`);
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks or 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 provides
take_screenshot,get_page_info, andcapture_pdftools for 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 screenshots; Growth is $15 for 15,000, Pro is $39 for 60,000, Scale is $99 for 250,000, and Business is $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan.
Create a free ScreenshotNeo account to use the 1,000 monthly screenshots without entering a card.
Frequently asked questions
Frequently Asked Questions
Can I test whether a Selenium value is callable?
Yes. Python’s callable(value) reports whether an object can be called, and type(value) shows what your helper actually returned. These checks help locate an accidental return-type change.
Does adding parentheses inside the XPath make the WebElement callable?
No. Parentheses that are part of a valid XPath expression are unrelated to Python’s call operator. The problematic form is parentheses placed after the Python expression that already returned the WebElement.
Should I replace every XPath with a different locator?
No. XPath is a supported Selenium locator strategy. Change the locator only when the XPath is invalid, matches the wrong node, or is too unstable for the page; an extra Python call operator is a separate issue.
PC 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 & 11Crashes, 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 minuteQuick 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.

