microsoft/playwright · error · InvalidSelectorError

Can not capture the selector before diving into the frame. O

Error message

Can not capture the selector before diving into the frame. Only use * after the last frame has been selected

What it means

splitSelectorByFrame rejects a selector where the '*' capture modifier lands in any chunk other than the last (deepest) frame chunk. Capture must target an element in the final frame; capturing before diving into a frame is meaningless because the captured node would be abandoned when descending.

Source

Thrown at packages/isomorphic/selectorParser.ts:143

      chunk = { parts: [] };
      chunkStartIndex = i + 1;
      continue;
    }
    if (selector.capture === i)
      chunk.capture = i - chunkStartIndex;
    chunk.parts.push(part);
  }
  if (!chunk.parts.length) {
    if (pierceToken)
      throw new InvalidSelectorError(`Selector cannot be empty when piercing frames, while parsing selector ${selectorText}`);
    throw new InvalidSelectorError(`Selector cannot end with entering frame, while parsing selector ${selectorText}`);
  }
  const lastPart = chunk.parts[chunk.parts.length - 1];
  if (lastPart.name === 'internal:control' && lastPart.body === 'enter-frame')
    throw new InvalidSelectorError(`Selector cannot end with entering frame, while parsing selector ${selectorText}`);
  chunks.push(chunk);
  if (typeof selector.capture === 'number' && typeof chunks[chunks.length - 1].capture !== 'number')
    throw new InvalidSelectorError(`Can not capture the selector before diving into the frame. Only use * after the last frame has been selected`);
  if (typeof selector.capture === 'number' && pierce)
    throw new InvalidSelectorError(`Can not *-capture inside a frame-piercing selector, while parsing selector ${selectorText}`);
  return { pierce, chunks };
}

function selectorPartsEqual(list1: ParsedSelectorPart[], list2: ParsedSelectorPart[]) {
  return stringifySelector({ parts: list1 }) === stringifySelector({ parts: list2 });
}

export function stringifySelector(selector: string | ParsedSelector, forceEngineName?: boolean): string {
  if (typeof selector === 'string')
    return selector;
  return selector.parts.map((p, i) => {
    let includeEngine = true;
    if (!forceEngineName && i !== selector.capture) {
      if (p.name === 'css')
        includeEngine = false;
      else if (p.name === 'xpath' && (p.source.startsWith('//') || p.source.startsWith('..')))

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Place the '*' capture on the final (innermost) selector only: 'iframe >> internal:control=enter-frame >> *button'.
  2. Avoid manual '*' — use the locator API and resolve/extract elements through returned handles.
  3. Re-read the message: it tells you the capture must be after the last frame has been selected.

Example fix

// before
await page.locator('*iframe >> internal:control=enter-frame >> button').click();

// after
await page.locator('iframe >> internal:control=enter-frame >> *button').click();
Defensive patterns

Strategy: validation

Validate before calling

function captureIsInLastFrameChunk(sel: string): boolean {
  const parts = sel.split('>>').map(p => p.trim());
  const lastEnterFrame = Math.max(-1, ...parts.map((p,i)=>/enter-frame/.test(p)?i:-1));
  const captureIdx = parts.findIndex(p => /^\*/.test(p));
  return captureIdx === -1 || captureIdx > lastEnterFrame;
}

Try / catch

try { await page.locator(sel).click(); }
catch (e) { if (isInvalidSelectorError(e) && /Can not capture the selector before diving/.test(e.message)) { /* move '*' to the last fragment */ } else throw e; }

Prevention

When it happens

Trigger: Authoring a selector like '*iframe >> internal:control=enter-frame >> button' (capture on the iframe, before entering the frame), or any use of '*' that places the capture index in a non-final frame chunk.

Common situations: Hand-authoring capture modifiers across frame boundaries; misunderstanding that '*' must come after the last enter-frame; building selector strings programmatically and inserting '*' too early.

Related errors


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