{"record":{"id":"bf91442990992b6b","repo":"microsoft/playwright","slug":"selector-cannot-end-with-entering-frame-while-par","errorCode":null,"errorMessage":"Selector cannot end with entering frame, while parsing selector ${selectorText}","messagePattern":"Selector cannot end with entering frame, while parsing selector (.+?)","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/isomorphic/selectorParser.ts","lineNumber":140,"sourceCode":"        continue;\n      }\n      chunks.push(chunk);\n      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) {","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/isomorphic/selectorParser.ts#L122-L158","documentation":"splitSelectorByFrame throws this in two cases: (a) the trailing chunk is empty and no pierce token was used (the selector ended right after an enter-frame, with nothing inside the frame), or (b) the last processed part of the chunk is itself an enter-frame control token. Both mean the selector dives into a frame but never selects anything inside it.","triggerScenarios":"Selectors ending in enter-frame: 'iframe >> internal:control=enter-frame' (nothing selected inside the frame), or a trailing '>>' after enter-frame. The descent is started but no target is given.","commonSituations":"Truncating a frame selector string; dynamically concatenating and leaving the final fragment empty; copy-paste errors leaving a dangling enter-frame.","solutions":["Always follow enter-frame with a selector inside the frame: 'iframe >> internal:control=enter-frame >> button'.","Use frameLocator('iframe').locator('button') so the closing selector is enforced by the API.","Trim trailing '>>' and empty fragments from dynamically built selector strings."],"exampleFix":"// before\nawait page.locator('iframe >> internal:control=enter-frame').click();\n\n// after\nawait page.frameLocator('iframe').locator('button').click();","handlingStrategy":"validation","validationCode":"function doesNotEndOnEnterFrame(sel: string): boolean {\n  const parts = sel.split('>>').map(p => p.trim()).filter(Boolean);\n  return !/internal:control=enter-frame$/.test(parts[parts.length - 1] || '');\n}","typeGuard":null,"tryCatchPattern":"try { await page.locator(sel).click(); }\ncatch (e) { if (isInvalidSelectorError(e) && /cannot end with entering frame/.test(e.message)) { sel = sel.replace(/>>\\s*internal:control=enter-frame\\s*$/,''); } else throw e; }","preventionTips":["Always select something inside the frame after enter-frame.","Use frameLocator to enforce an inner locator.","Trim dangling enter-frame tokens from dynamic selectors."],"tags":["selector","frames","iframe","empty","invalid-selector"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}