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

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:

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

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

Fix the specific call site

  1. 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.
  2. Read the expression from left to right. Confirm that find_element has its locator arguments once and that no second pair of parentheses follows the closing parenthesis of the lookup.
  3. Assign the result to a descriptive variable. Names such as submit_button or profile_heading make it clearer that the variable contains an element, not a function.
  4. Use a WebElement method or property. Use click(), clear(), or send_keys(...) when you need an operation. Use text when you need the element’s visible text. Do not write element().
  5. 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.

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

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_elements as 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:

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

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

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.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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/):

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

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

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.