vitest-dev/vitest · error · Error

Element not found

Error message

Element not found: ${v.element}

What it means

selectOptions accepts either string values or `{ element: SerializedLocator }` objects for `<option>` elements. For the object form the command resolves each locator to a Playwright ElementHandle; if the locator cannot be attached to a real DOM node (elementHandle() returns null), Vitest throws with the serialized locator text. This guards against silently selecting a detached/stale option.

Solutions

  1. Ensure the `<option>` exists in the DOM at call time: `await expect(select).toBeVisible()` and wait for options first.
  2. Prefer passing option values as plain strings (`selectOptions(select, ['apple'])`) when possible; the string path does not require resolving an ElementHandle.
  3. If using locators, build them right before the call rather than capturing and reusing across async gaps.
  4. Scope the option locator to the same frame/container as the select element.

Example fix

// before
const opt = page.getByRole('option', { name: 'Apple' })
// ... async work that removes the option ...
await userEvent.selectOptions(select, [{ element: opt }])

// after
await userEvent.selectOptions(select, [{ element: page.getByRole('option', { name: 'Apple' }) }])
// or, simplest:
await userEvent.selectOptions(select, ['apple'])
Defensive patterns

Strategy: validation

Validate before calling

// ensure the option exists before passing it as { element }
const option = page.getByRole('option', { name: 'Apple' })
await expect(option).toBeVisible()
await userEvent.selectOptions(select, [{ element: option }])

Type guard

import type { SerializedLocator } from '@vitest/browser'

function isOptionObject(v: unknown): v is { element: SerializedLocator } {
  return typeof v === 'object' && v !== null && 'element' in v
}

Try / catch

try {
  await userEvent.selectOptions(select, [{ element: optionLocator }])
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Element not found')) {
    // re-query the option or fall back to a value string
    await userEvent.selectOptions(select, ['apple'])
  } else {
    throw err
  }
}

Prevention

When it happens

Trigger: Calling `userEvent.selectOptions(select, [{ element: page.getByRole('option', { name: 'X' }) }])` when the option is not present, has been removed since the locator was created, is inside a shadow root the locator cannot reach, or the select has not yet rendered the options (timing).

Common situations: Selecting from a lazy-loaded option list before options render; option removed by virtualization after the locator was captured; selecting inside an iframe without scoping the locator; passing a locator built against the wrong frame.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/8ab4a1c6ad192380. Report an issue: GitHub.

Appendix: source

Thrown at packages/browser-playwright/src/commands/select.ts:22

import type { UserEventCommand } from './utils'
import { getDescribedLocator } from './utils'

export const selectOptions: UserEventCommand<UserEvent['selectOptions']> = async (
  context,
  selector,
  userValues,
  options = {},
) => {
  const value = userValues as any as (string | { element: SerializedLocator })[]
  const selectElement = getDescribedLocator(context, selector)

  const values = await Promise.all(value.map(async (v) => {
    if (typeof v === 'string') {
      return v
    }
    const elementHandler = await getDescribedLocator(context, v.element).elementHandle()
    if (!elementHandler) {
      throw new Error(`Element not found: ${v.element}`)
    }
    return elementHandler
  })) as (readonly string[]) | (readonly ElementHandle[])

  await selectElement.selectOption(values, options)
}

View on GitHub (pinned to 1fa9837ec2)