{"record":{"id":"305325203032d5d6","repo":"microsoft/playwright","slug":"can-not-capture-inside-a-frame-piercing-selector","errorCode":null,"errorMessage":"Can not *-capture inside a frame-piercing selector, while parsing selector ${selectorText}","messagePattern":"Can not \\*-capture inside a frame-piercing selector, while parsing selector (.+?)","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/isomorphic/selectorParser.ts","lineNumber":145,"sourceCode":"      continue;\n    }\n    if (selector.capture === i)\n      chunk.capture = i - chunkStartIndex;\n    chunk.parts.push(part);\n  }\n  if (!chunk.parts.length) {\n    if (pierceToken)\n      throw new InvalidSelectorError(`Selector cannot be empty when piercing frames, while parsing selector ${selectorText}`);\n    throw new InvalidSelectorError(`Selector cannot end with entering frame, while parsing selector ${selectorText}`);\n  }\n  const lastPart = chunk.parts[chunk.parts.length - 1];\n  if (lastPart.name === 'internal:control' && lastPart.body === 'enter-frame')\n    throw new InvalidSelectorError(`Selector cannot end with entering frame, while parsing selector ${selectorText}`);\n  chunks.push(chunk);\n  if (typeof selector.capture === 'number' && typeof chunks[chunks.length - 1].capture !== 'number')\n    throw new InvalidSelectorError(`Can not capture the selector before diving into the frame. Only use * after the last frame has been selected`);\n  if (typeof selector.capture === 'number' && pierce)\n    throw new InvalidSelectorError(`Can not *-capture inside a frame-piercing selector, while parsing selector ${selectorText}`);\n  return { pierce, chunks };\n}\n\nfunction selectorPartsEqual(list1: ParsedSelectorPart[], list2: ParsedSelectorPart[]) {\n  return stringifySelector({ parts: list1 }) === stringifySelector({ parts: list2 });\n}\n\nexport function stringifySelector(selector: string | ParsedSelector, forceEngineName?: boolean): string {\n  if (typeof selector === 'string')\n    return selector;\n  return selector.parts.map((p, i) => {\n    let includeEngine = true;\n    if (!forceEngineName && i !== selector.capture) {\n      if (p.name === 'css')\n        includeEngine = false;\n      else if (p.name === 'xpath' && (p.source.startsWith('//') || p.source.startsWith('..')))\n        includeEngine = false;\n    }","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/isomorphic/selectorParser.ts#L127-L163","documentation":"splitSelectorByFrame rejects combining the '*' capture modifier with frame-piercing mode (pierce-frames). A pierce selector matches elements across many frames simultaneously, so singling out one captured index is ambiguous/unsupported.","triggerScenarios":"Authoring a selector that uses both 'internal:control=pierce-frames' and a '*' capture modifier anywhere, e.g. 'internal:control=pierce-frames >> *div'.","commonSituations":"Trying to capture a specific matched element while also piercing frames; converting a normal captured selector to pierce mode without dropping the '*'; dynamic selector builders that combine both features.","solutions":["Drop the '*' capture modifier when using pierce-frames.","If you need a specific element, switch to non-piercing frame-by-frame navigation with frameLocator and capture there.","Re-evaluate whether piercing is needed at all for the use case."],"exampleFix":"// before\nawait page.locator('internal:control=pierce-frames >> *div').click();\n\n// after\nawait page.locator('internal:control=pierce-frames >> div').click();","handlingStrategy":"validation","validationCode":"function notCapturingWhilePiercing(sel: string): boolean {\n  const pierces = /internal:control=pierce-frames/.test(sel);\n  const captures = /(^|>>)\\s*\\*/.test(sel);\n  return !(pierce && captures);\n}","typeGuard":null,"tryCatchPattern":"try { await page.locator(sel).click(); }\ncatch (e) { if (isInvalidSelectorError(e) && /frame-piercing/.test(e.message)) { sel = sel.replace(/(^|>>)\\s*\\*/g, '$1'); } else throw e; }","preventionTips":["Do not combine '*' capture with pierce-frames.","If a specific element is needed, navigate frame-by-frame with frameLocator.","Drop '*' when switching a selector to pierce mode."],"tags":["selector","frames","pierce","capture","invalid-selector"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}