What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Short answer: In current Selenium Python, replace the deprecated find_elements_by_* call with driver.find_elements(By.STRATEGY, value). An empty list still has several possible causes: an invalid or changed selector, the wrong page or browsing context, asynchronous content that has not entered the DOM, or a lookup made before a click or navigation finished. Fix the API first, then diagnose selector, timing, and context in that order.
Use the Selenium 4 finder API first
The old convenience methods such as find_elements_by_xpath() and find_elements_by_css_selector() are deprecated in current Python Selenium. Import By, pass the strategy as the first argument, and pass the locator value as the second:
from selenium.webdriver.common.by import By
elements = driver.find_elements(By.CSS_SELECTOR, '.result')
Equivalent strategies include By.ID, By.NAME, By.XPATH, By.CSS_SELECTOR, By.CLASS_NAME, By.TAG_NAME, By.LINK_TEXT, and By.PARTIAL_LINK_TEXT. The value must match the strategy. For example, a CSS selector belongs with By.CSS_SELECTOR; an XPath expression belongs with By.XPATH.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall| Legacy call | Current form |
|---|---|
driver.find_elements_by_id('login') |
driver.find_elements(By.ID, 'login') |
driver.find_elements_by_name('email') |
driver.find_elements(By.NAME, 'email') |
driver.find_elements_by_xpath('//button') |
driver.find_elements(By.XPATH, '//button') |
driver.find_elements_by_css_selector('.result') |
driver.find_elements(By.CSS_SELECTOR, '.result') |
driver.find_elements_by_class_name('card') |
driver.find_elements(By.CLASS_NAME, 'card') |
Migration alone does not make a wrong selector match. It only ensures that your code uses the supported finder interface.
#1 Best Overall
What an empty list actually means
find_elements is a collection query. It returns a list, including an empty list, when no matching nodes exist in the document and context searched at that instant. It does not tell you whether the selector is wrong, the page is still changing, or the elements are inside another context. A singular call behaves differently: find_element raises NoSuchElementException when no match exists. Invalid CSS or XPath syntax can raise an invalid-selector exception instead of returning an empty collection.
Use the observed result to choose the next diagnostic step rather than adding a random delay.
Diagnose the failure in the order Selenium uses the page
1. Confirm that navigation and the preceding action succeeded
Print the URL and title immediately before the lookup. A redirect, authentication page, consent page, failed form submission, or an exception in an earlier click can leave the driver on a different document than expected.
print('URL:', driver.current_url)
print('Title:', driver.title)
print('Matches:', driver.find_elements(By.CSS_SELECTOR, '.result'))
Check the rendered DOM with browser developer tools, not just the HTML you remember from an earlier version of the site. Test the selector in the Elements panel and verify that the expected node is in the current document.
2. Validate the selector and its strategy
Keep CSS and XPath syntax separate. These are valid examples:
Rank #2
cards = driver.find_elements(By.CSS_SELECTOR, 'article.card')
links = driver.find_elements(By.XPATH, "//a[contains(@class, 'result-link')]")
Common mistakes include passing .card with By.XPATH, using an XPath where a CSS selector is expected, looking for a class name containing spaces with By.CLASS_NAME, or relying on a generated class that changed after a deployment. Prefer stable IDs, names, data attributes, or a short relationship-based XPath. Avoid selectors tied to presentation-only classes when the application provides a durable attribute.
3. Wait for asynchronous content
Page-load completion and application readiness are different events. The browser can finish loading assets declared in the original HTML while JavaScript is still fetching data or rendering components after a click. A single-page application commonly returns an empty list when the lookup runs immediately after navigation or submission.
Use an explicit, condition-based wait. If DOM presence is sufficient, wait for presence; if the next operation requires a visible element, wait for visibility.
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
wait = WebDriverWait(driver, 10)
items = wait.until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.result'))
)
The timeout should reflect the application and environment. A timeout is useful evidence: it means the condition never became true within the selected interval. It does not prove that changing the find_elements spelling will help.
A fixed time.sleep() can be too short on a slow run and unnecessarily slow on a fast run. It may be useful while exploring a page, but a condition-based wait is the reliable final implementation. Do not combine implicit and explicit waits; their interaction can make polling take longer and produce unpredictable timing.
Rank #3
4. Wait for the event that creates the elements
If results appear only after a click, submit the action and then wait for the result locator. If a previous result list is replaced, wait for an old node to become stale or for a loading indicator to disappear before querying the new list. Re-query after the update instead of holding a list captured before the DOM changed.
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 errorssubmit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[type="submit"]')))
submit.click()
results = wait.until(
EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.result'))
)
5. Search the correct browsing context
Selenium does not automatically search inside an iframe. Switch to the frame first, locate its contents, then return to the top-level document when finished.
frame = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'iframe.payment')))
driver.switch_to.frame(frame)
fields = driver.find_elements(By.CSS_SELECTOR, 'input')
driver.switch_to.default_content()
For nested frames, switch through each level. A selector that works in the frame will return an empty list from the parent document.
Shadow DOM is another separate search boundary. Locate the host, obtain its shadow root, and search from that root:
host = driver.find_element(By.CSS_SELECTOR, 'user-card')
shadow = host.shadow_root
name = shadow.find_elements(By.CSS_SELECTOR, '.name')
Do not assume that a selector copied from the browser inspector can cross a shadow boundary or reach an iframe without an explicit context switch.
Rank #4
6. Check for markup changes and driver differences
If the URL, selector, wait, and context are correct, compare the rendered page and behavior in another supported browser. Some failures originate in the underlying browser driver rather than in the locator itself. Keep Selenium, the browser, and its driver aligned with the versions supported in your environment, and isolate a minimal reproduction before changing several variables at once.
A small diagnostic script
This example records the state that matters without hiding a failure behind a broad exception:
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
locator = (By.CSS_SELECTOR, 'article.result')
wait = WebDriverWait(driver, 10)
print('URL:', driver.current_url)
print('Title:', driver.title)
try:
results = wait.until(EC.presence_of_all_elements_located(locator))
except Exception as exc:
print('Lookup did not become true:', type(exc).__name__, str(exc))
print('Current URL:', driver.current_url)
raise
else:
print('Result count:', len(results))
Replace the locator with the one you tested in developer tools. During investigation, inspect driver.page_source or a screenshot to determine whether the expected markup exists at all, but do not treat page source as proof that a JavaScript-rendered node is ready for interaction.
Symptom-to-fix checklist
| Symptom | Likely cause | Action |
|---|---|---|
| Every locator returns an empty list | Wrong URL, redirect, failed prior action, or unexpected document | Print URL and title; verify navigation and authentication state. |
| One selector is empty while nearby elements work | Selector mismatch or changed markup | Test the exact selector in developer tools and choose a stable attribute. |
| It works with a long sleep but not without one | Asynchronous rendering | Replace the sleep with an explicit presence or visibility wait. |
| Top-level elements are found, frame elements are not | Search is occurring outside an iframe | Switch to the frame before locating and switch back afterward. |
| Light-DOM selectors cannot see a component’s children | Shadow DOM boundary | Locate the host and search from its shadow root. |
| An exception appears instead of an empty list | Invalid selector, stale context, or singular lookup behavior | Read the exception type and correct syntax or context before adjusting waits. |
| Behavior differs between browsers | Browser-driver implementation difference | Reduce to a minimal case and compare supported browser/driver combinations. |
Performance and reliability considerations
- Use the narrowest stable locator you can maintain. Broad selectors create extra matches and make later assertions ambiguous.
- Set explicit timeouts according to the page’s normal service time and your CI environment. A very short timeout creates false failures; an unlimited wait hides a real regression.
- Wait for a meaningful application condition, such as a result node, rather than a generic page-load event.
- After a framework rerender, locate elements again. Previously returned WebElement objects may no longer represent nodes in the current DOM.
- Keep frame switching scoped and restore the original context in cleanup code so a later test does not inherit the wrong document.
Or skip the browser setup
If your goal is a clean visual capture rather than interactive Selenium actions, ScreenshotNeo can return a screenshot or PDF from one request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing state in X-Page-Verdict and X-Billed headers.
Free tools Windows power users keep installed
One-click scans. No signup required.
See the ScreenshotNeo API documentation for all options. A cURL request 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 same request in 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)
And in 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 provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. It is a capture service, not a replacement for Selenium’s clicks, form handling, or frame interaction; use it when you need a repeatable page image or PDF while diagnosing what a rendered page looks like.
Best Value
The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Create a free ScreenshotNeo account to try it.
FAQ
Can an empty list be a successful result?
Yes. If your test is checking that no matching nodes exist, an empty list is the expected collection value. Make that intent explicit in the assertion and separately verify that the page and context are the ones you intended to test.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Why did a list become unusable after the page updated?
Frameworks can replace nodes during rerendering. A previously returned WebElement reference may then be stale. Wait for the update and perform a fresh lookup instead of reusing the old reference.
Should I switch to find_element to expose errors?
Use it when exactly one element is required and absence should fail immediately. Keep find_elements when zero, one, or many matches are valid outcomes; changing methods does not fix a selector, timing, or context problem.
Frequently Asked Questions
Can an empty list be a successful result?
Yes. If your test is checking that no matching nodes exist, an empty list is the expected collection value. Make that intent explicit in the assertion and separately verify that the page and context are the ones you intended to test.
Why did a list become unusable after the page updated?
Frameworks can replace nodes during rerendering. A previously returned WebElement reference may then be stale. Wait for the update and perform a fresh lookup instead of reusing the old reference.
Should I switch to find_element to expose errors?
Use it when exactly one element is required and absence should fail immediately. Keep find_elements when zero, one, or many matches are valid outcomes; changing methods does not fix a selector, timing, or context problem.
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.

