microsoft/playwright · error · InvalidSelectorError
Selector cannot end with entering frame, while parsing selec
Error message
Selector cannot end with entering frame, while parsing selector ${selectorText} What it means
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.
Source
Thrown at packages/isomorphic/selectorParser.ts:140
continue;
}
chunks.push(chunk);
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) {View on GitHub (pinned to c8fc3bf8d3)
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.
Example fix
// before
await page.locator('iframe >> internal:control=enter-frame').click();
// after
await page.frameLocator('iframe').locator('button').click(); Defensive patterns
Strategy: validation
Validate before calling
function doesNotEndOnEnterFrame(sel: string): boolean {
const parts = sel.split('>>').map(p => p.trim()).filter(Boolean);
return !/internal:control=enter-frame$/.test(parts[parts.length - 1] || '');
} Try / catch
try { await page.locator(sel).click(); }
catch (e) { if (isInvalidSelectorError(e) && /cannot end with entering frame/.test(e.message)) { sel = sel.replace(/>>\s*internal:control=enter-frame\s*$/,''); } else throw e; } Prevention
- Always select something inside the frame after enter-frame.
- Use frameLocator to enforce an inner locator.
- Trim dangling enter-frame tokens from dynamic selectors.
When it happens
Trigger: 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.
Common situations: Truncating a frame selector string; dynamically concatenating and leaving the final fragment empty; copy-paste errors leaving a dangling enter-frame.
Related errors
- Selector cannot start with entering frame, select the iframe
- Selector cannot be empty when piercing frames, while parsing
- "${part.body}" is only allowed as the first selector token,
- Can not capture the selector before diving into the frame. O
- Can not *-capture inside a frame-piercing selector, while pa
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/bf91442990992b6b.
Report an issue: GitHub.