What changes in Selenium's Select class when the wrapped select element carries the multiple attribute?
answer
- One attribute changes several behaviours
- Read once, at construction time
- Deselecting is not available everywhere
- Presence of the attribute is what counts
- Single-select clicks only turn selection on
basics
~20 sSelecting by text or value stops selecting every match instead of just the first, the whole deselect family becomes legal, and isMultiple reports true. Selenium's Select reads that attribute once, in the constructor, and caches the answer.
solid answer
~40 sThe constructor reads `getDomAttribute("multiple")` once and caches it. Because `multiple` is a boolean attribute, that command answers `"true"` whenever the attribute is present and `null` when it is absent, so `isMultiple()` really reports presence — even `multiple="false"` is multiple. Three behaviours turn on it. `selectByValue` and `selectByVisibleText` return after the first match on a single-selection dropdown but select **every** match on a multiple one. `deselectAll`, `deselectByValue`, `deselectByIndex` and `deselectByVisibleText` all throw `UnsupportedOperationException` unless the control is multiple, because in Selenium 4 the protocol's click on an option only toggles selectedness when the container has `multiple` and otherwise sets it to true. And `getAllSelectedOptions()` can legitimately be empty, so `getFirstSelectedOption()` throws `NoSuchElementException`. Because the flag is cached, re-wrap the element after the page swaps the control.
code
java · 25 linesimport org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.Select;
public class VehicleClassPicker {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
driver.get("https://permits.example.gov/renew");
WebElement list = driver.findElement(By.id("vehicle-class"));
Select vehicleClasses = new Select(list);
if (vehicleClasses.isMultiple()) {
vehicleClasses.deselectAll();
vehicleClasses.selectByValue("car");
vehicleClasses.selectByValue("motorcycle");
}
System.out.println(vehicleClasses.getAllSelectedOptions().size());
} finally {
driver.quit();
}
}
}go deeper
Recall that isMultiple tells you whether the control accepts more than one choice, and that the deselect methods only exist for that case. Check it before assuming which methods are available.
Explain how isMultiple is computed from the multiple attribute, why an empty attribute value counts as true, and why the select-by methods stop at the first match on one shape and not the other.
Diagnose the real failures: an UnsupportedOperationException about a multi-select, an unexpectedly multiplied selection after a text match, and a cached flag that no longer matches a re-rendered control.
Own the wider question of how a suite handles controls whose shape varies by permit type or feature flag, and what the team's convention should be for verifying the control before driving it.
## How isMultiple decides, and when The `multiple` attribute is read exactly once, in the constructor, and cached in a final field: ```java String value = element.getDomAttribute("multiple"); isMulti = (value != null && !"false".equals(value)); ``` `multiple` is a **boolean attribute**, and the WebDriver protocol's get-element-attribute command has a special rule for those: it returns the string `"true"` when the attribute is present on the element, and `null` when it is not. The specification is explicit that this deviates from the DOM's own attribute getter, which would hand back an empty string for a bare boolean attribute. So in Selenium 4 what reaches `getDomAttribute("multiple")` is one of exactly two values: | Markup | `getDomAttribute("multiple")` | `isMultiple()` | |---|---|---| | `<select>` | `null` | `false` | | `<select multiple>` | `"true"` | `true` | | `<select multiple="multiple">` | `"true"` | `true` | | `<select multiple="true">` | `"true"` | `true` | | `<select multiple="false">` | `"true"` | `true` | The last row is the one people get wrong. Writing `multiple="false"` does not turn the behaviour off: the attribute is present, so the protocol reports `"true"` and the wrapper agrees. In effect `isMultiple()` answers the question "is the `multiple` attribute present at all", and the comparison against the literal text `false` in the source is a defensive check that a browser driven through this command never triggers. Because the field is **final and set at construction**, a `Select` answers with the state of the element as it was when you wrapped it. If the parking-permit renewal form swaps its vehicle-class control for a multi-selection list after an update, a wrapper built before that change keeps answering `false`. Construct a new `Select` after the control changes rather than reusing the old one. ## Selecting: the first match versus every match `selectByValue` and `selectByVisibleText` both loop over the options they matched. On a single-selection dropdown they `return` as soon as the first one is selected. On a multiple-selection list there is nothing to return early for, so they keep going and **every matching option ends up selected**. - Two vehicle classes labelled "Van" in different groups: one `selectByVisibleText("Van")` call selects both. - `selectByContainsVisibleText("Resident")` on a multi-selection list can select "Resident annual", "Resident quarterly" and "Non-resident annual" together, because all three contain the substring. - On a single-selection dropdown neither situation arises, because the loop stops after one. - `selectByIndex` is unaffected either way: an index identifies exactly one option, so there is never more than one match to iterate. The practical consequence is that an ambiguous label is invisible on a single-selection control and very visible on a multiple-selection one. The same call that appeared to work on the permit-type dropdown selects three vehicle classes the moment the same helper is pointed at a list that allows several. ## Deselecting is a multiple-selection-only family Five methods refuse to run at all unless `isMultiple()` is true: - `deselectAll()` throws `UnsupportedOperationException("You may only deselect all options of a multi-select")`. - `deselectByValue`, `deselectByIndex`, `deselectByVisibleText` and `deSelectByContainsVisibleText` throw `UnsupportedOperationException("You may only deselect options of a multi-select")`. This is not a stylistic restriction. `Select` changes an option by clicking it, and the protocol's click algorithm toggles selectedness only when the option's container carries `multiple`; on an ordinary dropdown it sets selectedness to true and never to false. There is simply no operation that clears a single-selection dropdown's choice, so the wrapper refuses rather than pretending. `deselectAll()` itself is a plain walk: it visits every option and clicks the ones currently selected, leaving the others alone. That is why it is safe to call on an already-empty list — every option is skipped because its state already matches — and why its cost grows with the number of options rather than with the number selected. ## A vehicle-class list on the renewal form 1. Wrap the control and ask `isMultiple()` before assuming which family of methods is available; the same page can render either shape depending on the permit type. 2. If it is a multiple-selection list, `deselectAll()` first when you need a known starting point, because pre-selected classes carried over from the applicant's previous permit are otherwise added to, not replaced. 3. Then select each class you want with its own call. Repeating a call for an already-selected class does nothing, so the sequence is safe to run twice. 4. Read the result back before moving on, because a multiple-selection list gives you no other signal that a class you asked for was refused as disabled. ## Reading the result back On a multiple-selection list, "nothing chosen" is a real state. `getAllSelectedOptions()` legitimately returns an **empty list**, and `getFirstSelectedOption()` then throws `NoSuchElementException("No options are selected")` rather than returning null. On a single-selection dropdown that throw is rare, because the browser keeps one option selected by default, so a `NoSuchElementException` from that method is usually a signal that you are on a multiple-selection control after all. That asymmetry is worth stating plainly, because it decides which method a caller should reach for. On a permit-type dropdown `getFirstSelectedOption()` is the natural read and the exception is a genuine fault. On a vehicle-class list the same call is a liability, because "nothing chosen yet" is an ordinary state of the page rather than an error, and `getAllSelectedOptions()` returning an empty list expresses it without throwing.
- Why is deselectAll refused on an ordinary single-selection dropdown?Because the wrapper changes an option by clicking it, and the protocol's click on an option whose container has no `multiple` attribute sets selectedness to true and never to false. There is no operation that clears a single-selection dropdown's choice, so `deselectAll` raises `UnsupportedOperationException` rather than pretending to do something. Choosing a different option is the only way to change the value.
- The page turns a control into a multi-select after an update, but isMultiple still returns false. Why?The flag is read once, in the constructor, and stored in a final field, so a `Select` built before the change keeps answering with the state it saw. Construct a new `Select` around the element after the control changes. The same applies in reverse: a wrapper built while the control was multiple keeps offering the deselect family after it stops being multiple.
- What does selectByContainsVisibleText do differently on a multiple-selection list?It can select several options at once. The substring match may hit more than one option, and on a multiple-selection list the loop does not stop after the first, so "Resident" can select "Resident annual", "Resident quarterly" and "Non-resident annual" together. On a single-selection dropdown the loop returns after the first match, which hides the ambiguity rather than removing it.
saying these in an interview costs you the question
- Thinks deselectAll works on an ordinary single-selection dropdown
- Thinks writing multiple=false turns multiple selection back off
- Believes isMultiple is re-read from the DOM on every call
- Expects selectByVisibleText to stop at the first match on a multi-select
- Thinks clicking the chosen option clears a single-selection dropdown