Free tools Windows power users keep installed
One-click scans. No signup required.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Use .// when searching for descendants from a Selenium WebElement, and use // when searching from the document. For example, parent.find_elements(By.XPATH, ".//a") returns matching links at any depth inside that parent. The explicit equivalent is parent.find_elements(By.XPATH, "./descendant::a").
Find descendants from the document or from a parent
Selenium accepts XPath locators through By.XPATH. Choose the search context first: use the driver to search the page, or a previously found WebElement to limit the search to a section of the page.
from selenium import webdriver
from selenium.webdriver.common.by import By
# Start a browser session and open a page.
driver = webdriver.Chrome()
driver.get("https://example.com")
# Search the whole document for links below the results section.
links = driver.find_elements(
By.XPATH,
"//section[@id='results']//a",
)
# Locate a parent, then search only within that element.
results = driver.find_element(By.ID, "results")
result_links = results.find_elements(By.XPATH, ".//a")
for link in result_links:
print(link.text, link.get_attribute("href"))
driver.quit()
The document-scoped expression begins at the page: //section[@id='results']//a finds a elements that descend from the matching section. The parent-scoped expression begins at the results element: .//a finds descendant links within that element, including links nested several levels down.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →In Selenium Python, find_elements returns a list. If nothing matches, the list is empty; it does not raise a no-such-element exception merely because the result is empty. Use find_element when you expect one match and want Selenium to return that element or raise an exception if it is absent.
#1 Best Overall
Understand //, .//, and the descendant axis
These expressions look similar but their starting context matters. The leading dot in .// makes the current element the XPath context, which is important when calling find_elements on a parent element.
| Expression | Meaning | Typical use |
|---|---|---|
//a |
Find matching links in the document, at any depth. | driver.find_elements(By.XPATH, "//a") |
.//a |
Find matching links at any descendant depth from the current context element. | parent.find_elements(By.XPATH, ".//a") |
./descendant::a |
Use the explicit descendant axis to find links below the current context element. | parent.find_elements(By.XPATH, "./descendant::a") |
./a |
Find only direct child links, not links nested deeper. | parent.find_elements(By.XPATH, "./a") |
descendant-or-self::* |
Match the context element itself as well as its descendants. | Use when the context node must be included in the matches. |
The XPath descendant axis includes children, grandchildren, and all deeper descendants. It does not include the context node itself; descendant-or-self does. For the common case of finding elements below a located parent, .//tag is compact and easy to scan, while ./descendant::tag spells out the axis.
Build a scoped locator
A robust descendant locator starts with a meaningful parent and narrows the target using stable attributes or text. This keeps the query focused and helps avoid accidentally collecting matching elements elsewhere on the page.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Filter by a semantic attribute
ready_rows = results.find_elements(
By.XPATH,
".//tr[@data-state='ready']",
)
The predicate [@data-state='ready'] keeps only descendant rows whose data-state attribute has that exact value.
Match a class as a token
HTML class attributes can contain multiple whitespace-separated class names, and their order can change. Avoid matching a whole class attribute with @class='card active' unless that exact string is truly stable. For a class token, use a whitespace-aware test:
Rank #2
cards = results.find_elements(
By.XPATH,
".//*[contains(concat(' ', normalize-space(@class), ' '), ' card ')]",
)
This checks for the token card even if the element has other classes or the class order changes. The normalize-space function collapses surrounding and repeated whitespace before the test.
Match visible text carefully
To find a descendant button whose normalized text is exactly Next, use:
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 minutenext_buttons = results.find_elements(
By.XPATH,
".//button[normalize-space(.)='Next']",
)
normalize-space(.) accounts for leading, trailing, or repeated whitespace in the element’s text. Exact text matching is still sensitive to wording, localization, and text changes, so prefer a stable attribute when one identifies the control reliably.
Choose between find_element and find_elements
- Use
find_elementfor one expected target, such as a single heading inside a panel:heading = panel.find_element(By.XPATH, ".//h2"). - Use
find_elementsfor zero or more matches, such as every ready row:rows = panel.find_elements(By.XPATH, ".//tr[@data-state='ready']"). - Iterate over the returned list when processing multiple descendants. An empty list is a valid result when there are no matches.
Do not use the singular method and assume it will return a collection. If the page may contain no match and that is an ordinary outcome, use the plural method and check whether the list is empty.
Wait for descendants on dynamically rendered pages
A locator can be correct and still find nothing if the page has not inserted the target into the DOM yet. For content rendered after navigation or an asynchronous request, wait for the parent to appear before searching inside it:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
driver = webdriver.Chrome()
driver.get("https://example.com")
wait = WebDriverWait(driver, 10)
results = wait.until(
EC.presence_of_element_located((By.ID, "results"))
)
ready_rows = results.find_elements(
By.XPATH,
".//tr[@data-state='ready']",
)
print(f"Found {len(ready_rows)} ready rows")
driver.quit()
In the example, the wait targets the parent. Once it is present, the descendant query runs in that parent’s context. If the parent appears before its children are populated, wait for a descendant condition instead:
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 problemswait.until(
EC.presence_of_element_located(
(By.XPATH, "//*[@id='results']//tr[@data-state='ready']")
)
)
results = driver.find_element(By.ID, "results")
ready_rows = results.find_elements(
By.XPATH,
".//tr[@data-state='ready']",
)
Presence means that an element is in the DOM; it does not guarantee that it is visible or interactable. Use a visibility or clickability condition when the next operation requires those properties. Waiting for the specific condition needed is more reliable than adding an arbitrary sleep.
When XPath is the right locator
If the target has a unique, stable ID, Selenium’s guidance generally favors using that ID directly: it is simpler to read and maintain. XPath is especially useful when the target is identified by its relationship to an ancestor or by text that has no suitable stable attribute. CSS selectors can also express many descendant relationships, but XPath supports axes and text predicates that CSS selectors do not provide in the same way.
| Locator approach | Good fit | Trade-off |
|---|---|---|
| Unique ID | A stable ID directly identifies the target. | Simple and readable, but depends on the page providing a useful ID. |
| CSS selector | Tag, class, ID, and attribute relationships are sufficient. | Often concise; less expressive for XPath-style relationship and text matching. |
| XPath | The target is recognized by ancestry, descendant structure, or text. | Flexible, but a long expression tied to incidental markup can be difficult to maintain. |
Avoid absolute paths such as /html/body/div[2]/div[1]/.... They encode the page’s precise nesting and position, so a wrapper or layout change can break them. Prefer a stable ancestor followed by a specific descendant condition. XPath selectors are typically slower than simpler locator choices, and browser vendors do not generally performance-test arbitrary XPath expressions as a product feature; scope the query and keep it specific, particularly on large pages.
Troubleshoot common descendant XPath problems
The parent search returns elements outside the parent
Check whether the expression begins with //. When searching from a WebElement, use .//tag or ./descendant::tag to make the relative context explicit. For example, use parent.find_elements(By.XPATH, ".//a"), not an unqualified document-wide expression.
The query finds direct children but misses nested elements
./button selects direct button children only. Change it to .//button or ./descendant::button if the button can be nested inside another element.
A class locator stops matching after a page change
Replace whole-string class equality with a token-aware predicate if you need a class match. Class ordering and additional class names can vary without changing the element’s meaning.
The result list is empty immediately after navigation
The content may not have been rendered yet. Wait for the parent or for a matching descendant with WebDriverWait, then run the scoped query. If the locator still returns nothing, inspect whether the target is actually in the current document and whether the attribute or text predicate matches its current value.
Selenium raises an invalid selector error
Review the XPath syntax, quotation marks, brackets, and parentheses. XPath requires balanced predicates and quoted string values; an invalid expression differs from a valid query that simply matches zero elements.
An element becomes stale after it was found
A re-render can replace a previously located element, leaving the old WebElement reference stale. Reacquire the parent after the page update, then find its descendants again. Do not assume that a stored element reference automatically tracks replacement DOM nodes.
Best Value
Or skip the browser setup
If your goal is to capture a rendered page rather than inspect DOM elements with Selenium, ScreenshotNeo is a website screenshot API and MCP server for developers. It does not replace XPath selection or return a Selenium element list; it is an alternative for producing a screenshot or PDF without configuring a browser session yourself.
For example, save a page screenshot with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for request options. The service accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.
Sign up free for 1,000 screenshots a month with no card.
Frequently Asked Questions
Does XPath .// include the parent element itself?
No. It selects descendants of the current context element. To include that context node as well, use an XPath expression based on descendant-or-self.
What happens if find_elements has no matches?
It returns an empty list, which you can test or iterate over without handling a no-such-element exception.
Can I use XPath to find a descendant in an iframe?
Only after switching WebDriver into that frame. A frame’s document is a separate browsing context; locate the frame, switch to it, and then run the descendant search there.
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.

