microsoft/playwright · error · Error
Failed to find element matching selector "${selector}"
Error message
Failed to find element matching selector "${selector}" What it means
evalOnSelector (the single-element variant behind page.$eval / locator.evaluate on the first match) requires exactly one element. After querying the selector with no match it throws 'Failed to find element matching selector "..."'.
Source
Thrown at packages/playwright-core/src/server/frames.ts:913
});
return scope ? scope._context.raceAgainstContextDestroyed(promise) : promise;
}
async dispatchEvent(progress: Progress, selector: string, type: string, eventInit: Object = {}, options: types.QueryOnSelectorOptions, scope?: dom.ElementHandle): Promise<void> {
await this._waitForFunctionOnSelector(progress, selector, (injectedScript, element, data) => {
injectedScript.dispatchEvent(element, data.type, data.eventInit);
return { result: undefined };
}, { type, eventInit }, { mainWorld: true, ...options }, scope);
}
async evalOnSelector(progress: Progress, selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any, scope?: dom.ElementHandle): Promise<any> {
return progress.race(this._evalOnSelector(selector, strict, expression, isFunction, arg, scope));
}
private async _evalOnSelector(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any, scope?: dom.ElementHandle): Promise<any> {
const handle = await this.selectors.query(selector, { strict }, scope);
if (!handle)
throw new Error(`Failed to find element matching selector "${selector}"`);
const result = await handle.internalEvaluateExpression(expression, { isFunction }, arg);
handle.dispose();
return result;
}
async evalOnSelectorAll(progress: Progress, selector: string, expression: string, isFunction: boolean | undefined, arg: any, scope?: dom.ElementHandle): Promise<any> {
return progress.race(this._evalOnSelectorAll(selector, expression, isFunction, arg, scope));
}
private async _evalOnSelectorAll(selector: string, expression: string, isFunction: boolean | undefined, arg: any, scope?: dom.ElementHandle): Promise<any> {
const arrayHandle = await this.selectors.queryArrayInMainWorld(selector, scope);
const result = await arrayHandle.internalEvaluateExpression(expression, { isFunction }, arg);
arrayHandle.dispose();
return result;
}
async querySelectorAll(progress: Progress, selector: string): Promise<dom.ElementHandle<Element>[]> {
return progress.race(this.selectors.queryAll(selector));View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Use a Locator and waitFor() before evaluating so the element is present.
- Fix the selector (try getByRole/getByTestId) and verify with page.locator(sel).count().
- Ensure you are querying the right frame (use frameLocator for iframes).
- Use \$$eval (all-matches) if you genuinely want to operate on zero-or-many.
Example fix
// before
await page.$eval('#missing', el => el.textContent); // throws
// after
const loc = page.locator('#x');
await loc.waitFor();
const text = await loc.evaluate(el => el.textContent); Defensive patterns
Strategy: validation
Validate before calling
// Ensure a match exists before the single-element eval
if (await page.locator(sel).count() === 0) throw new Error('no match: ' + sel);
await page.$eval(sel, fn); Type guard
async function hasMatch(page: any, sel: string): Promise<boolean> {
return (await page.locator(sel).count()) > 0;
} Try / catch
try {
await page.$eval(sel, fn);
} catch (e) {
if (e instanceof Error && /Failed to find element matching selector/.test(e.message)) { /* wait + retry */ }
else throw e;
} Prevention
- Prefer Locator.waitFor() before single-element evaluations.
- Verify selectors with .count() during authoring.
When it happens
Trigger: Calling page.\$eval / elementHandle.\$eval / locator-based single-element evaluation on a selector that matches zero elements at query time (within the timeout, since this internal path has no built-in wait).
Common situations: Selector typos; element not yet rendered; dynamic content that appears after an async delay; shadow DOM the selector does not pierce; iframe content where the selector is run in the wrong frame; strict vs non-strict mismatch.
Related errors
- No element matching ${selector}
- Unsupported token "${unsupportedToken.toSource()}" while par
- Error while parsing css selector "${selector}". Did you mean
- Conflicting exactness in internal:role selector: ${stringify
- Malformed selector: ${part.name}=${part.body}
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/76a2cf6b28c9f065.
Report an issue: GitHub.