In Selenium, how does the Select class actually change which option of a dropdown is selected?
answer
- One private helper does all the work
- The wrapper injects no script
- It clicks the option element itself
- Already in that state means nothing happens
- Disabled option refused before any click
basics
~20 sIt 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 sEvery 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
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.
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.
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.
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