puppeteer/puppeteer · error

Element is not a <select> element.

Error message

Element is not a <select> element.

What it means

Thrown by ElementHandle.select() when the resolved DOM node is not an HTMLSelectElement. The check runs inside the page via evaluate(), so the element's runtime type matters, not the TypeScript generic. Puppeteer requires a real <select> because it iterates element.options and mutates option.selected.

Source

Thrown at packages/puppeteer-core/src/api/ElementHandle.ts:985

   */
  @throwIfDisposed()
  @bindIsolatedHandle
  async select(...values: string[]): Promise<string[]> {
    for (const value of values) {
      assert(
        isString(value),
        'Values must be strings. Found value "' +
          value +
          '" of type "' +
          typeof value +
          '"',
      );
    }

    return await this.evaluate((element, vals): string[] => {
      const values = new Set(vals);
      if (!(element instanceof HTMLSelectElement)) {
        throw new Error('Element is not a <select> element.');
      }

      const selectedValues = new Set<string>();
      if (!element.multiple) {
        for (const option of element.options) {
          option.selected = false;
        }
        for (const option of element.options) {
          if (values.has(option.value)) {
            option.selected = true;
            selectedValues.add(option.value);
            break;
          }
        }
      } else {
        for (const option of element.options) {
          option.selected = values.has(option.value);
          if (option.selected) {

View on GitHub (pinned to d484e21c17)

Solutions

  1. Verify the handle points to the <select> element: await page.$eval('select#state', el => el.tagName) before calling select().
  2. If the dropdown is custom (non-native <select>), use click() on the trigger and then click() on the desired option instead of select().
  3. Narrow the selector to target the <select> directly, e.g. 'select#country' rather than a parent wrapper.
  4. Re-query the handle right before selecting if the page re-renders frequently.

Example fix

// before
const handle = await page.$('.dropdown');
await handle.select('blue');

// after
const handle = await page.$('select.dropdown');
await handle.select('blue');
Defensive patterns

Strategy: type-guard

Validate before calling

// Before calling select(), confirm the element is a <select>
const isSelect = await handle.evaluate(el => el instanceof HTMLSelectElement);
if (!isSelect) {
  throw new Error('Refusing to call select() on a non-<select> element');
}
await handle.select('blue');

Type guard

async function isSelectElement(handle: import('puppeteer').ElementHandle): Promise<boolean> {
  return handle.evaluate(el => el instanceof HTMLSelectElement);
}

Try / catch

try {
  await handle.select('blue');
} catch (e) {
  if (e.message.includes('not a <select>')) {
    // fall back to clicking option elements in a custom dropdown
  } else throw e;
}

Prevention

When it happens

Trigger: Calling handle.select('blue') on an ElementHandle that resolves to an <input>, <div>, or any non-<select> node. Common when a selector matches a wrapper <div> around a custom dropdown, or when the page swapped the <select> for a custom component between querySelector and select().

Common situations: Selecting from a custom dropdown widget that uses <ul>/<li> or <div> instead of a native <select>; matching a <label> or container element rather than the <select> itself; the element was re-rendered after the handle was acquired.

Related errors


AI-assisted analysis of puppeteer/puppeteer@d484e21c17 (2026-08-12). Data as JSON: /api/errors/89faec01fd0fd975. Report an issue: GitHub.