skip to content

In Playwright, which ways can locator.selectOption() identify the option it should select?

level: middleimportance: must knowfreq 66%

answer

  1. More than one way to name an option
  2. Strings are forgiving, objects are precise
  3. Stating two properties narrows the match
  4. Multiple selects take an array
  5. It returns the values it selected

basics

~20 s

A plain string matches an option by its value or its label. An object narrows by value, label or index, and every property you state must match. An array selects several options in a multiple select.

solid answer

~40 s

`locator.selectOption('high')` passed a string matches an option whose `value` **or** label is `high`. The object form is precise: `{ value: 'p1' }`, `{ label: 'High' }`, `{ index: 2 }`, and if you state several properties all of them must match. An array like `['bug', 'regression']` selects several options in a `<select multiple>`; on a plain `<select>` only the first match is taken, silently. `selectOption([])` deselects everything. The call returns the array of `value` attributes actually selected, and it dispatches `input` then `change`. It waits for the named option to appear, which is what makes it work against an assignee list that loads after render, and it throws `Element is not a <select> element` if you point it at a styled listbox rather than a real `<select>`.

code

typescript · 14 lines
typescript
import { test, expect } from '@playwright/test';

test('set priority and labels on an issue', async ({ page }) => {
  await page.goto('https://tracker.example.com/issues/DEV-114');

  const priority = page.getByLabel('Priority');
  await priority.selectOption('High');           // matches value or label
  await priority.selectOption({ value: 'p1' });  // by value only
  await priority.selectOption({ index: 2 });     // by position

  const labels = page.getByLabel('Labels');      // <select multiple>
  const chosen = await labels.selectOption(['bug', 'regression']);
  expect(chosen).toEqual(['bug', 'regression']);
});

go deeper

for a junior

Know that a select is set with selectOption rather than clicks, and that passing a plain string is the everyday form because it matches either the value or the visible label.

for a middle

Explain the object form and its and-semantics, the difference between a single and a multiple select, the array return value, and the input plus change events that follow.

for a senior

Show judgment about which matcher to use: label for anything a human reads, index only where no stable text exists, and know that the verb waits for late-arriving options.

for a principal

Own the convention across a suite, including when a product's styled listbox should stop pretending to be a select and be tested as the click-and-pick widget it really is.

## What selectOption accepts `locator.selectOption()` is the only way Playwright sets a `<select>`. It does not click the element open and hunt for an item in a rendered list — a native dropdown is drawn by the operating system and cannot be scripted that way. Instead it matches option elements and marks them selected. The values argument comes in several shapes: - **A string** — `selectOption('high')` matches an option whose `value` **or** whose label is `high`. String matching deliberately covers both, which is why it is the form you reach for first. - **An object** — `{ value: 'high' }`, `{ label: 'High' }` or `{ index: 2 }`. These are precise: an object with only `label` will not match on `value`. - **Several properties at once** — `{ label: 'High', index: 2 }` matches only an option for which *every* stated property holds. Adding a property narrows, it never widens. - **An array** — `['bug', 'regression']`, for a `<select multiple>`. - **An option element locator or handle**, when you already have the exact option in hand. - **An empty array** — `selectOption([])` deselects everything. ## The single-versus-multiple rule For a plain `<select>`, only the **first** option matching any of the values you passed is selected; the rest are ignored. For a `<select multiple>`, every matching option is selected. That asymmetry catches people out: passing four candidate values to a single select is not an error and does not warn, it just picks one. | Element | Values passed | Result | |---|---|---| | `<select>` | `'high'` | that one option selected | | `<select>` | `['low', 'high']` | first match only, silently | | `<select multiple>` | `['bug', 'regression']` | both selected | | either | `[]` | all options deselected | | either | value that matches nothing | waits, then times out | ## What it returns and what it fires `selectOption` returns an **array of the `value` attributes of the options it actually selected**, so a single select returns a one-element array and a deselect returns `[]`. That return value is a useful sanity check when the labels and values differ. After selecting, it dispatches `input` and then `change` on the `<select>`, which is what a page's own handler listens for. ## Waiting, and the errors you will see Three failure modes are worth recognising in an issue tracker where the assignee list loads from the API after the page renders: 1. **The option is not there yet.** `selectOption` keeps retrying until the option appears, so a list populated a moment later just works. If it never appears, the call fails on timeout and the call log shows what it was waiting for. 2. **The element is not a `<select>`.** You get `Element is not a <select> element`. A styled listbox built from `<div>`s is not a select at all — that is a click-and-pick widget, not this verb. 3. **The option exists but is disabled.** Playwright will not select a disabled option; it waits for it to become enabled and otherwise times out. A locator pointing at a `<label>` whose control is the `<select>` retargets to the control, so label-based lookup works without extra ceremony. ## Choosing which form to pass The string form is the right default because it reads well and tolerates a page where value and label happen to coincide. Switch to the object form when the two differ in a way that matters: an issue tracker whose priority options carry `value="p1"` but display `High` will match `'High'` by label and `'p1'` by value, and being explicit about which one you meant documents the intent. Prefer `{ label: ... }` over `{ index: ... }` for anything a human reads. Index matching is positional: it breaks the moment someone inserts a new priority into the middle of the list, and the resulting failure points at the wrong place. Reserve index for lists that genuinely have no stable text, such as a generated set of time slots.

  • What does selectOption return, and when is that return value worth reading?
    It returns an array of the `value` attributes of the options it actually selected — one element for a plain `<select>`, several for a multiple, and `[]` for a deselect. It is worth asserting on when labels and values differ, because it proves which option matched rather than which one you hoped for.
  • Why does selectOption fail on a dropdown built from divs?
    It throws `Element is not a <select> element`. The verb marks `<option>` elements selected; it has nothing to mark on a styled listbox. Such a widget is driven the ordinary way — click the trigger, then click the option — because its items are real DOM nodes.
  • An option is added to the select a second after page load. Does selectOption need an explicit wait?
    No. `selectOption` retries until the requested option is present in the `<select>`, so an assignee list populated from an API call after render resolves on its own. Only if the option never appears does the call fail on timeout, and the call log names what it waited for.

saying these in an interview costs you the question

  • selectOption clicks the dropdown open and picks a visible item
  • A string only matches the value attribute, never the label
  • Passing several values to a single select raises an error
  • It works on any custom dropdown, not just a real select
  • You must wait for the options to load before calling it
  • Matching by index is as safe as matching by label