jackwener/OpenCLI · error · Error

No element found matching selector: ${query}

Error message

No element found matching selector: ${query}

What it means

setFileInputFiles first probes the page with Runtime.evaluate to confirm the selector matches an element; if document.querySelector finds nothing, it throws 'No element found matching selector'. This fails fast before attempting the file-chooser interception dance required by chrome.debugger attachments.

Source

Thrown at extension/src/cdp.ts:362

export async function setFileInputFiles(
  tabId: number,
  files: string[],
  selector?: string,
): Promise<void> {
  await ensureAttached(tabId);

  // Enable DOM + Page domains. Page is needed for file-chooser interception.
  await sendDebuggerCommand({ tabId }, 'DOM.enable');
  await sendDebuggerCommand({ tabId }, 'Page.enable');

  // Find the file input element (used to trigger the chooser).
  const query = selector || 'input[type="file"]';
  const found = await sendDebuggerCommand({ tabId }, 'Runtime.evaluate', {
    expression: `!!document.querySelector(${JSON.stringify(query)})`,
    returnByValue: true,
  }) as { result?: { value?: boolean } };
  if (!found.result?.value) {
    throw new Error(`No element found matching selector: ${query}`);
  }

  // Chrome rejects DOM.setFileInputFiles with a plain nodeId/backendNodeId when
  // the debugger is attached via chrome.debugger (crbug 928255, "-32000 Not
  // allowed"). The only accepted path is file-chooser interception: enable it,
  // programmatically open the chooser, and use the backendNodeId that the
  // intercepted Page.fileChooserOpened event hands back. See issue #2108.
  await sendDebuggerCommand({ tabId }, 'Page.setInterceptFileChooserDialog', { enabled: true });
  try {
    const backendNodeId = await new Promise<number>((resolve, reject) => {
      const timer = setTimeout(() => {
        cleanup();
        reject(new Error('Page.fileChooserOpened not received within 5s — the input may not have opened a file chooser'));
      }, 5000);
      const listener = (source: chrome.debugger.Debuggee, method: string, params: unknown) => {
        if (source.tabId !== tabId || method !== 'Page.fileChooserOpened') return;
        // This is our chooser event — settle now either way, so a malformed
        // event rejects immediately instead of hanging until the 5s timeout.

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the selector matches in DevTools console: document.querySelector('…')
  2. Wait for the upload UI to render before calling setFileInputFiles
  3. Click the button that injects the file input first, then set the files
  4. Target the correct frame if the input lives in an iframe (the probe runs in the main frame)
  5. Use the default selector by passing an empty selector only when exactly one file input exists

Example fix

// before
await cdp.setFileInputFiles(tabId, files, 'input.upload');   // input not yet in DOM
// after
await cdp.evaluate(tabId, "!!document.querySelector('input.upload') || document.querySelector('#add')?.click()");
await new Promise(r => setTimeout(r, 500));   // let it render
await cdp.setFileInputFiles(tabId, files, 'input.upload');
Defensive patterns

Strategy: validation

Validate before calling

const found = await cdp.evaluate(tabId, `!!document.querySelector(${JSON.stringify(selector)})`);
if (found !== true) throw new Error(`pre-check failed: ${selector} not in DOM`);

Type guard

function selectorExists(v: unknown): v is true {
  return v === true;
}

Try / catch

try {
  await cdp.setFileInputFiles(tabId, files, selector);
} catch (e) {
  if ((e as Error).message.startsWith('No element found matching selector')) {
    await waitForSelector(tabId, selector);
    return retry();
  }
  throw e;
}

Prevention

When it happens

Trigger: handleSetFileInput is called with a selector (default 'input[type="file"]') that matches no element in the current DOM of the attached tab.

Common situations: The file input is inside a shadow DOM or iframe that document.querySelector can't see; the page hasn't rendered the uploader yet; selector typo; the input is only added after clicking an 'Upload' button.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/348adbcee246dbb0. Report an issue: GitHub.