skip to content

Why does Selenium's Select class throw UnexpectedTagNameException on a dropdown built from div elements?

level: middleimportance: must knowfreq 68%

answer

  1. The constructor checks one thing first
  2. Not every dropdown is a real control
  3. A tag name comparison, case-insensitive
  4. The message names expected and actual tag
  5. It extends WebDriverException, so unchecked

basics

~20 s

Selenium's Select constructor reads the element's tag name and accepts only a select tag. A styled listbox made of div and span elements has no option children and no multiple attribute, so the wrapper rejects it at construction.

solid answer

~40 s

The `Select` constructor's first act is `element.getTagName()`, compared case-insensitively with `select`; anything else raises `UnexpectedTagNameException`, whose message reads `Element should have been "select" but was "div"`. It extends `WebDriverException`, so it is unchecked. The check exists because every method on the class is defined over things only a real dropdown has: `option` descendants, the `multiple` attribute, and the selectedness the protocol reports per option. A scripted listbox has class names and ARIA state instead, none of which the wrapper can read. There is no flag or subclass that changes this: a custom widget is a set of ordinary elements and has to be driven with the ordinary element commands.

go deeper

for a junior

Recall that Select only accepts a real select element and that the constructor is where it complains. Recognise the exception name so you do not mistake it for a locator that found nothing.

for a middle

Explain the constructor's tag-name check and why the rest of the class depends on option children, the multiple attribute and per-option selectedness, none of which a scripted widget provides.

for a senior

Diagnose from the exception alone which of two very different faults you have, and be clear that a custom widget is driven with ordinary element commands rather than by forcing the wrapper onto it.

for a principal

Own the consequence for the wider suite: a design that ships custom listboxes changes what automation costs, and that is a conversation to have with the people choosing the component library.

## The check the constructor performs `Select` decides whether it will work with your element in its very first two statements: ```java String tagName = element.getTagName(); if (!"select".equalsIgnoreCase(tagName)) { throw new UnexpectedTagNameException("select", tagName); } ``` That comparison is the whole gate. It is **case-insensitive**, it runs before any other work, and nothing else about the element is considered: not its CSS classes, not its ARIA role, not whether it visually behaves like a dropdown when the applicant clicks it. Construction either succeeds or fails, so you never end up holding a half-working wrapper. ## What the exception carries `UnexpectedTagNameException` lives in the same `org.openqa.selenium.support.ui` package and extends `WebDriverException`, so it is **unchecked** — nothing forces you to catch it, and it surfaces as an ordinary test failure. Its message is built from the two tag names, giving output such as `Element should have been "select" but was "div"`. That single line tells you the control on the parking-permit renewal form is not a browser dropdown at all, which is usually more valuable than the failure it prevented. ## Why the class cannot be taught to drive a scripted listbox Every method on `Select` is defined in terms of things only a real `<select>` has: - `getOptions()` is literally a search for `option` descendants of the wrapped element; a panel of `div` rows has none. - The constructor reads the `multiple` content attribute to answer `isMultiple()`; a scripted widget has no such attribute. - `getAllSelectedOptions()` and `getFirstSelectedOption()` filter on **selectedness**, the state the WebDriver protocol reports for an option element. A `div` has no selectedness — at best it has a class name or an `aria-selected` value, which is a different thing that the protocol does not report as selected. - Changing the choice relies on the protocol's dedicated option branch of the click algorithm, which scrolls the containing `<select>` into view and updates the option's selectedness. A `div` takes the ordinary pointer path instead. | What `Select` needs | Real `<select>` | Scripted listbox | |---|---|---| | `option` descendants | present | absent — `div`, `li` or `span` rows | | `multiple` attribute | present or absent, readable | not defined | | reported selectedness | yes, per option | no, only page-owned styling or state | | option-specific click handling | defined by the protocol | ordinary element click path | ## Telling the two apart on the renewal form 1. Inspect the permit-type control in the browser's element inspector. A real one is a `<select>` element with `<option>` children that exist even while the list is closed. 2. A scripted one is usually a button or text box plus a panel of rows that is inserted into the DOM only while the list is open. 3. A real `<select>` opens the operating system's own popup, which the page's CSS cannot restyle; a scripted one is styled entirely by the page. 4. The tag check itself is the cheapest test of all. Constructing a `Select` around the control either succeeds or tells you, in one line, which tag you actually have. ## What you do instead There is no configuration, subclass or flag that makes `Select` accept a scripted widget, and there is no point trying: a wrapper whose every method is defined over `<option>` elements has nothing to work with. A scripted listbox is a collection of ordinary elements and is driven with the ordinary element commands your suite already uses for ordinary elements. Treat `UnexpectedTagNameException` as **information** rather than an obstacle — it is the fastest possible signal that you are looking at a custom widget. The one thing not to do is to reach for a workaround that fakes the outcome. Setting an `aria-selected` value or a class name on a row makes the page *look* chosen without the widget's own code ever running, so the renewal form's state, its hidden input and its validation are all left behind. The check exists to stop a wrapper producing that kind of half-truth, and the same reasoning applies to whatever you write in its place. ## The case that is often confused with it A different failure looks similar and is not the same problem. Some designs keep a real `<select>` in the DOM but hide it behind a styled overlay. There the tag name is right, so `new Select(...)` **succeeds**; the failure arrives later, when `selectByVisibleText` refuses with `UnsupportedOperationException("You may not select an option in invisible select")`. The two messages say different things: `UnexpectedTagNameException` means "this is the wrong kind of control", while the `UnsupportedOperationException` means "this is the right control, but it is not currently visible". Reading the exception type before reaching for a workaround saves the hour usually spent on the wrong one.

  • The element really is a select but selectByVisibleText still fails. What is the difference?
    A different exception with a different meaning. If the tag is `select`, construction succeeds; a later `UnsupportedOperationException("You may not select an option in invisible select")` means the control is present but hidden, typically behind a styled overlay. `UnexpectedTagNameException` says the control is the wrong kind; the `UnsupportedOperationException` says it is the right kind but not currently visible. Read the exception type before choosing a fix.
  • Can you subclass Select or pass a flag to make it work on a custom widget?
    No, and it would not help. The constructor's tag check is only the first obstacle: `getOptions` searches for `option` descendants, `isMultiple` reads the `multiple` attribute, and the selected-option methods filter on selectedness reported by the protocol. A div-based widget has none of these, so even bypassing the check would leave every method with nothing to read.

Select is a socket wired for one plug shape. It is not a general dropdown driver but a thin adapter over the browser's own control, so a widget that merely looks like one does not fit, however convincing the styling.

saying these in an interview costs you the question

  • Thinks Select works on any element that looks like a dropdown
  • Believes an aria-selected or role attribute is enough for Select
  • Assumes UnexpectedTagNameException means the element was not found
  • Tries to subclass Select so it can drive a div listbox
  • Confuses the wrong-tag failure with an invisible select element