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

  1. Use a Locator and waitFor() before evaluating so the element is present.
  2. Fix the selector (try getByRole/getByTestId) and verify with page.locator(sel).count().
  3. Ensure you are querying the right frame (use frameLocator for iframes).
  4. 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

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


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/76a2cf6b28c9f065. Report an issue: GitHub.