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
- Verify the handle points to the <select> element: await page.$eval('select#state', el => el.tagName) before calling select().
- If the dropdown is custom (non-native <select>), use click() on the trigger and then click() on the desired option instead of select().
- Narrow the selector to target the <select> directly, e.g. 'select#country' rather than a parent wrapper.
- 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
- Use a selector that targets the <select> tag directly, not a wrapper.
- Assert element.tagName === 'SELECT' in a helper before select().
- Re-query the handle right before select() if the page re-renders.
- For custom dropdowns, click the trigger and options instead of select().
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
- Cannot focus non-HTMLElement
- Error: failed to find element matching selector "${selector}
- Element is not a(n) `${tagName}` element
- Node is either not clickable or not an Element
- Unsupported browser '${browser.name}'
AI-assisted analysis of puppeteer/puppeteer@d484e21c17 (2026-08-12).
Data as JSON: /api/errors/89faec01fd0fd975.
Report an issue: GitHub.