{"record":{"id":"feaf81970f41eb91","repo":"microsoft/playwright","slug":"can-not-capture-the-selector-before-diving-into-th","errorCode":null,"errorMessage":"Can not capture the selector before diving into the frame. Only use * after the last frame has been selected","messagePattern":"Can not capture the selector before diving into the frame\\. Only use \\* after the last frame has been selected","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/isomorphic/selectorParser.ts","lineNumber":143,"sourceCode":"      chunk = { parts: [] };\n      chunkStartIndex = i + 1;\n      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('..')))","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/isomorphic/selectorParser.ts#L125-L161","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hand-authoring capture modifiers across frame boundaries; misunderstanding that '*' must come after the last enter-frame; building selector strings programmatically and inserting '*' too early.","solutions":["Place the '*' capture on the final (innermost) selector only: 'iframe >> internal:control=enter-frame >> *button'.","Avoid manual '*' — use the locator API and resolve/extract elements through returned handles.","Re-read the message: it tells you the capture must be after the last frame has been selected."],"exampleFix":"// before\nawait page.locator('*iframe >> internal:control=enter-frame >> button').click();\n\n// after\nawait page.locator('iframe >> internal:control=enter-frame >> *button').click();","handlingStrategy":"validation","validationCode":"function captureIsInLastFrameChunk(sel: string): boolean {\n  const parts = sel.split('>>').map(p => p.trim());\n  const lastEnterFrame = Math.max(-1, ...parts.map((p,i)=>/enter-frame/.test(p)?i:-1));\n  const captureIdx = parts.findIndex(p => /^\\*/.test(p));\n  return captureIdx === -1 || captureIdx > lastEnterFrame;\n}","typeGuard":null,"tryCatchPattern":"try { await page.locator(sel).click(); }\ncatch (e) { if (isInvalidSelectorError(e) && /Can not capture the selector before diving/.test(e.message)) { /* move '*' to the last fragment */ } else throw e; }","preventionTips":["Place '*' capture only on the innermost (post-frame) selector.","Avoid manual '*' — resolve elements via the locator API.","When building strings, append '*' last."],"tags":["selector","frames","capture","invalid-selector"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}