didi/DoKit · error · Error

Can't select any node with this selector: ${selector(path)}

Error message

Can't select any node with this selector: ${selector(path)}

What it means

Thrown inside unique() when a candidate selector, built from a candidate path, matches zero nodes when run via rootDocument.querySelectorAll. The algorithm assumes a selector derived from a live element must match at least that element; matching nothing means the generated selector is invalid or the document being queried is not the document that contains the element.

Source

Thrown at Web/packages/core/src/common/js/finder/index.js:137

    if (node.level === level - 1) {
      query = `${path[i].name} > ${query}`;
    } else {
      query = `${path[i].name} ${query}`;
    }
    node = path[i];
  }
  return query;
}
function penalty(path) {
  return path.map(node => node.penalty).reduce((acc, i) => acc + i, 0);
}
function unique(path) {
  const s = selector(path)
  const len = rootDocument.querySelectorAll(s).length
  // console.log('[unique] selector:', s, 'len:', len)
  switch (len) {
    case 0:
      throw new Error(`Can't select any node with this selector: ${selector(path)}`);
    case 1:
      return true;
    default:
      return false;
  }
}
function id(input) {
  const elementId = input.getAttribute('id');
  if (elementId && config.idName(elementId)) {
    return {
      name: `#${cssesc(elementId, { isIdentifier: true })}`,
      penalty: 0,
      type: 'id',
    };
  }
  return null;
}
function attr(input) {

View on GitHub (pinned to 626827cddb)

Solutions

  1. Set options.root (and thus rootDocument) to a node inside the same document as the target element, e.g. the iframe's document.body
  2. Re-fetch the element and generate the selector synchronously in the same task, avoiding async gaps where the DOM can mutate
  3. Wrap the finder call in try-catch and retry once after a microtask/frame, or fall back to an XPath or index-based locator
  4. If running in a jsdom/test environment, ensure the element was created from the same jsdom document instance

Example fix

// before
const sel = finder(iframeEl, { root: document.body }); // wrong document

// after
const iframeDoc = iframeEl.ownerDocument;
const sel = finder(iframeEl, { root: iframeDoc.body });
Defensive patterns

Strategy: validation

Validate before calling

const root = el.ownerDocument.body;
if (root.contains(el)) { const sel = finder(el, { root }); }

Try / catch

try { sel = finder(el, opts); } catch (e) { if (/Can't select any node/.test(e.message)) { sel = buildManualLocator(el); /* e.g. data-* attribute path */ } else throw e; }

Prevention

When it happens

Trigger: rootDocument (resolved from options.root) differs from the element's actual ownerDocument — e.g. element lives in an iframe but root is the main document, or vice versa. Also triggered by selectors containing characters that cssesc escapes differently across contexts, or when the DOM mutates between path construction and the querySelectorAll validation call.

Common situations: Element-picker / recording tools that capture elements in iframes or dynamically re-rendered React/Vue trees. The default root is document.body of the main document, so any iframe content breaks the invariant. Rapid DOM mutation between capture and selector generation.

Related errors


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/b0e88174238959e0. Report an issue: GitHub.