Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
If Selenium always opens or changes the first dropdown in a table, the usual cause is a page-wide locator. Locate the target tr first, then find and operate its descendant control. Also confirm whether the control is a native HTML <select>; Selenium’s Select helper does not operate custom dropdowns built from buttons, divs, or list items.
Why Selenium targets the first row
A call such as driver.find_element(By.CSS_SELECTOR, "select.status") searches the entire document and returns the first match. Repeating that call in a loop does not make it row-aware, so every iteration can modify the same control.
Tables also redraw after a selection, pagination, sorting, or an AJAX update. A previously stored row element may then be detached from the DOM, producing a stale-element error. Numeric row positions are similarly fragile: sorting or filtering can move a record to a different index.
The reliable mental model
- Identify the intended row with a stable key, such as
data-row-idor a unique cell value. - Find the dropdown relative to that row.
- Interact with the control according to its DOM type.
- Verify the changed value in that same row.
- After a redraw, locate the row again instead of reusing its old WebElement.
First determine whether it is a native select
Inspect the element in browser developer tools. A native control has a <select> element containing <option> elements. Selenium’s Select class only works with those HTML elements. A widget made from a button, input, div, or li is a JavaScript overlay and must be operated through ordinary element interactions.
#1 Best Overall
| DOM you find | Correct approach | Typical mistake |
|---|---|---|
<select><option> |
Use Select after scoping to the row |
Using a global selector and changing the first match |
| Button plus popup/listbox | Click the row’s trigger, then choose the visible option | Passing the button or div to Select |
| Input with autocomplete | Type, wait for suggestions, select the matching suggestion | Assuming the suggestions are descendants of the input’s row |
Fix a native select in each row (Python)
This example uses a stable row key and selects by visible text. Replace the selectors and values with those used by your page.
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select, WebDriverWait
wait = WebDriverWait(driver, 10)
for wanted_id in ("42", "57", "83"):
row = wait.until(lambda d: d.find_element(
By.CSS_SELECTOR, f"tr[data-row-id='{wanted_id}']"
))
select_el = row.find_element(By.CSS_SELECTOR, "select.status")
wait.until(lambda d: select_el.is_enabled())
Select(select_el).select_by_visible_text("Approved")
selected = Select(select_el).first_selected_option.text.strip()
assert selected == "Approved", (
f"Row {wanted_id} selected {selected!r}, not 'Approved'"
)
The important operation is row.find_element(...), not another call on driver. Selenium also supports selection by option value and zero-based index:
Select(select_el).select_by_value("approved")
Select(select_el).select_by_index(2)
Visible text is generally easiest to review, while a stable value is less affected by translated labels. Use an index only when the option order is a documented part of the page.
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 →Multiple-select controls
If the element has the HTML multiple attribute, it can contain several selected options. Inspect all selected options rather than only first_selected_option:
selected = [option.text.strip()
for option in Select(select_el).all_selected_options]
assert "Approved" in selected
The control’s size, placeholder behavior, and selected-option semantics come from HTML, so do not treat a multi-select as a single-choice menu.
Rank #2
Fix a custom JavaScript dropdown
Custom widgets often render their popup outside the table, commonly as a child of body. Locate the trigger in the intended row, wait until it is clickable, click it, and then locate an option in the active visible listbox.
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)
row_key = "42"
row = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, f"tr[data-row-id='{row_key}']")
))
trigger = row.find_element(
By.CSS_SELECTOR, "button[aria-haspopup='listbox']"
)
wait.until(EC.element_to_be_clickable(trigger)).click()
option = wait.until(EC.element_to_be_clickable((
By.XPATH,
"//div[@role='listbox' and not(contains(@style,'display: none'))]"
"//div[@role='option' and normalize-space()='Approved']"
)))
option.click()
Do not assume the nth option belongs to the nth row. If several listboxes exist, use an attribute that associates the open popup with the trigger (for example, an ARIA ownership or ID relationship), or restrict the XPath to a listbox that is actually visible. Some frameworks use li, button, or another role instead of div[role='option']; match the markup you inspected.
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 & 11Outdated 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 matchVerifying a custom selection
After the click, read the row’s displayed label, selected-value attribute, or accessible name and assert it changed. For a redraw-prone widget, wait for that row to show the expected label before continuing:
wait.until(lambda d: row.find_element(
By.CSS_SELECTOR, "button[aria-haspopup='listbox']"
).text.strip() == "Approved")
If the selection replaces the row node, this wait can itself become stale. In that case, re-find the row by its stable key inside the wait callback.
Process rows safely when the table refreshes
Keep each iteration independent. Find the row immediately before acting, perform the selection, wait for the result, and only then move to the next key.
Rank #3
row_ids = ["42", "57", "83"]
for row_id in row_ids:
def current_row(d):
return d.find_element(
By.CSS_SELECTOR, f"tr[data-row-id='{row_id}']"
)
row = wait.until(current_row)
select_el = row.find_element(By.CSS_SELECTOR, "select.status")
Select(select_el).select_by_value("approved")
wait.until(lambda d: d.find_element(
By.CSS_SELECTOR,
f"tr[data-row-id='{row_id}'] select.status option:checked"
).get_attribute("value") == "approved")
If the table paginates, first navigate to the page containing the key and wait for the rows to finish loading. If it sorts after an update, never infer the next target from the old row index.
Recommended Free Tools
Handling click interception and interactability
Selenium scrolls an element into view and checks whether it can receive the click. If another element covers the click center, the click can fail with an element-click-intercepted error. Common causes include a loading mask, cookie banner, sticky header, animation, or a row positioned partly outside the viewport.
- Wait for the page’s loading mask or overlay to become invisible.
- Wait for the row-local trigger to be clickable, not merely present.
- Scroll the row into a useful position before clicking.
- Close or accept an obstructing banner when that is part of the test’s intended behavior.
- Use a JavaScript click only as a last resort, after understanding the widget’s event handling; it bypasses normal hit testing and can hide a real layout defect.
from selenium.webdriver.common.action_chains import ActionChains
row = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, "tr[data-row-id='42']")
))
trigger = row.find_element(By.CSS_SELECTOR, "button.status-menu")
driver.execute_script(
"arguments[0].scrollIntoView({block: 'center'});", trigger
)
wait.until(EC.element_to_be_clickable(trigger))
ActionChains(driver).move_to_element(trigger).click().perform()
Locator and synchronization checklist
- Prefer
data-row-id, a record ID, or another stable key overtr:nth-child(3). - Scope every repeated control to its target row.
- Use explicit waits for presence, enabled state, visibility, and clickability.
- Wait for network-driven redraws or for the selected label/value to change.
- Re-locate rows after sorting, filtering, pagination, or AJAX replacement.
- Assert the final value in the same row that was edited.
- Capture the row key and HTML in failure logs so a wrong-row defect is diagnosable.
Common failures and their fixes
Every loop iteration changes row one
Cause: the dropdown is found from driver. Fix: find the target row first and call row.find_element.
UnexpectedTagNameException from Select
Cause: the widget is not a native select. Fix: inspect its trigger and popup, then use click-and-option interactions.
ElementClickInterceptedException
Cause: an overlay or another element covers the click point. Fix: wait for the obstruction to disappear, scroll into view, and use a normal clickable wait.
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 →StaleElementReferenceException
Cause: the table replaced the row after selection. Fix: discard the old WebElement and re-find the row by its stable key.
Rank #4
The menu opens but the wrong option is chosen
Cause: options are rendered globally and the test selects an arbitrary matching or nth option. Fix: target the currently visible popup or its trigger-associated listbox, and match normalized text or a stable option value.
Timeout while waiting for a row
Cause: the row is on another page, filtered out, or still loading. Fix: navigate or adjust filters first, then wait for the specific key instead of increasing the timeout blindly.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Performance and reliability practices
Use one driver session and explicit waits rather than fixed sleeps. Stable keys prevent retries caused by row reordering. For large tables, process only the required records and avoid collecting every row repeatedly; locate the key directly when the page provides an ID selector. If a selection triggers a request, wait for a deterministic UI change rather than sleeping for an assumed network duration.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsKeep tests deterministic by resetting filters and sort order, and record the requested row key, desired value, actual value, and visible errors on failure. This distinguishes a locator bug from an application defect.
Or skip the browser setup
If your goal is a clean image or PDF of a page rather than interactive form testing, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.
One request is enough:
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 API documentation for all options, including row-relevant custom JavaScript, CSS selectors, waits, cookies, headers, device presets, full-page capture, and PDF output. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Best Value
FAQ
Should I use a row index if the table is static?
Only when the markup and ordering are guaranteed by the application. A stable record key remains safer and continues to work after sorting, filtering, or pagination.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Can I select an option by visible text in a custom widget?
Yes, provided the lookup is limited to the active popup and the text is normalized. A page-wide text match can still select an option from another open or hidden widget.
Why does a JavaScript click sometimes pass while Selenium click fails?
JavaScript bypasses Selenium’s hit-testing and therefore can ignore an overlay or layout problem. Prefer fixing the obstruction; reserve JavaScript for a widget whose event model you understand.
How can I prove the test changed the intended row?
Read back the selected value or label from the same stable-keyed row and assert it. Include that key and both values in failure output.
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.

