skip to content

In Selenium, how does the Select class actually change which option of a dropdown is selected?

level: middleimportance: should knowfreq 47%

answer

  1. One private helper does all the work
  2. The wrapper injects no script
  3. It clicks the option element itself
  4. Already in that state means nothing happens
  5. Disabled option refused before any click

basics

~20 s

It clicks the option element. Selenium's Select checks the option is enabled, compares its current selectedness with the state you asked for, and clicks only when they differ, so re-selecting an already-chosen option sends nothing at all.

solid answer

~40 s

Every select and deselect call in `Select` reaches one private helper that does three things: it refuses a disabled option with `UnsupportedOperationException("You may not select a disabled option")`, compares `isSelected()` with the state you asked for, and calls `click()` on the `<option>` only when they differ. No script is injected. In Selenium 4 the protocol's click algorithm has a dedicated branch for options: it scrolls the containing `<select>` into view, fires the pointer and focus events at that container, then toggles selectedness if the container has `multiple` or sets it to true otherwise, firing `change` at the container when the option was not previously selected. So the interactability checks apply to the select, not the option, and re-selecting the current choice is a genuine no-op.

go deeper

for a junior

Recall that the wrapper works by clicking the option element rather than setting a value directly, and that a disabled option produces an error rather than being quietly skipped.

for a middle

Explain the helper's three steps and the protocol's option-specific click behaviour, including why the checks and events land on the containing select and why an unchanged selection sends nothing.

for a senior

Use this to diagnose real failures: decide from the symptom whether the click never happened, happened but changed nothing, or happened and the page's own handler behaved differently than expected.

for a principal

Frame for the team when driving a control through its real events is worth the round trips, and what a suite gives up when it starts reaching around the protocol to set state directly.

## Every change funnels through one private method `Select` exposes a dozen public methods, but every one of them that changes the page ends up in the same three lines: ```java private void setSelected(WebElement option, boolean select) { assertOptionIsEnabled(option, select); if (option.isSelected() != select) { option.click(); } } ``` Three facts follow immediately, and between them they explain almost every surprise the class produces: - The change is a **real click on the `<option>` element**. The wrapper injects no script and sets no property directly. - If the option already has the selectedness you asked for, **nothing happens at all** — no command is sent, no event is fired. - A disabled option is rejected **before** the click, with `UnsupportedOperationException("You may not select a disabled option")`. ## What a click on an option means to the protocol The WebDriver protocol's element-click algorithm has a branch specifically for `option` elements, and it does not behave like a click on a button: 1. The option's **container** — the enclosing `<select>` — is scrolled into view, and the in-view and obscured checks are applied to that container, not to the option. 2. `mouseOver`, `mouseMove` and `mouseDown` are fired at the container, and the focusing steps run on the container. 3. If the option is not disabled, an `input` event is fired at the container. 4. If the container carries the `multiple` attribute, the option's selectedness is **toggled**; otherwise it is **set to true**. 5. A `change` event is fired at the container when the option's previous selectedness was false. 6. `mouseUp` and `click` are fired at the container. Two consequences matter in practice. The interactability checks are made against the `<select>`, so an option scrolled out of a long open list is not itself the problem. And on an ordinary single-selection dropdown a click can only ever turn selectedness **on**, never off — which is exactly why `Select` refuses the whole deselect family on a single select. | | `<select>` | `<select multiple>` | |---|---|---| | Effect on the clicked option | selectedness set to true | selectedness toggled | | Effect on the other options | the previous choice is replaced | left untouched | | Can one click clear a choice | no | yes | ## The guards that run before the click `Select` refuses several situations rather than sending a click that would fail or lie: - Every one of `selectByVisibleText`, `selectByValue` and `selectByIndex` first checks the `<select>` itself is enabled, raising `UnsupportedOperationException("You may not select an option in disabled select")`. - The visible-text path additionally checks the `<select>` is visible, raising `UnsupportedOperationException("You may not select an option in invisible select")`. - The visible-text path also rejects a matched option hidden by `visibility`, `display` or `opacity` with `NoSuchElementException("Invisible option with text: ...")`. - A disabled option is refused wherever it is reached. The protocol treats an option as disabled when the option itself, an enclosing `<optgroup>`, or the `<select>` is disabled, so a whole greyed-out group of permit tiers behaves consistently. - The disabled-option guard applies only when you are **selecting**. Deselecting a disabled option is not blocked by it, because the guard's purpose is to stop a choice being recorded that the applicant could not have made. - Every guard raises before any command that would change the page, so a refusal never leaves the renewal form half-updated. Either the option's selectedness changed or nothing at all happened. ## Why the silent no-op matters on a renewal form Suppose the parking-permit renewal form recalculates the fee from a listener on the permit-type dropdown, and a step re-selects "Resident annual" to force a refresh. Because the option is already selected, `setSelected` sends no click; no `input` and no `change` reach the `<select>`, and the fee is never recalculated. The step looks like it did something and did nothing. The same mechanism is a benefit when it is understood: running the same selection twice is safe and cannot double-fire the page's handlers. ## What this buys you Because the wrapper composes ordinary commands rather than reaching around them, the events the page sees are the ones the protocol defines for a user choosing an option, and the guards fail loudly when the control is not in a state a user could act on. When you need to know why a selection did not take, the question to ask is which of the three lines above stopped: a disabled option raised, the states already matched so no click was sent, or the click happened and the page's own handler did something else with it.

  • A step re-selects the already-chosen permit type to force a recalculation. Why does nothing happen?
    Because the helper compares the option's current selectedness with the state you asked for and skips the click when they already match. No click means no `input` and no `change` reaches the `<select>`, so a fee listener bound to that event never runs. The step is a genuine no-op; to re-trigger the page you have to change the selection to something else and back.
  • Which element do the interactability checks apply to when Select clicks an option?
    The containing `<select>`, not the option. The protocol's option branch scrolls the container into view and applies the in-view and obscured checks to it, then fires the pointer and focus events at the container as well. That is why an option far down a long list is not itself the source of an interactability failure.

saying these in an interview costs you the question

  • Believes Select injects JavaScript to set the option
  • Expects a change event when re-selecting the already-chosen option
  • Thinks a disabled option is skipped silently instead of raising
  • Assumes interactability is checked on the option, not the select
  • Assumes a click on an option always replaces the previous choice