microsoft/playwright · error · InvalidSelectorError
Selector cannot start with entering frame, select the…
Error message
Selector cannot start with entering frame, select the iframe first
What it means
splitSelectorByFrame rejects an 'enter-frame' control token when it begins a chunk — i.e. there is no preceding selector that picks the iframe element. Two consecutive enter-frame tokens also trip this (the chunk's last part is itself enter-frame). You must select the iframe element before descending into it.
Solutions
- Precede enter-frame with a selector matching the iframe element: 'iframe >> internal:control=enter-frame >> button'.
- Use the frameLocator API: page.frameLocator('iframe').locator('button') — Playwright handles enter-frame for you.
- Never put two enter-frame tokens back-to-back; always select an iframe between them.
Example fix
// before
await page.locator('internal:control=enter-frame >> button').click();
// after
await page.frameLocator('iframe').locator('button').click(); Defensive patterns
Strategy: try-catch
Validate before calling
function selectorEntersFrameSafely(sel: string): boolean {
const parts = sel.split('>>').map(p => p.trim());
if (/internal:control=enter-frame/.test(parts[0])) return false;
for (let i = 1; i < parts.length; i++)
if (/internal:control=enter-frame/.test(parts[i]) && /internal:control=enter-frame/.test(parts[i-1])) return false;
return true;
} Try / catch
try { await page.locator(sel).click(); }
catch (e) { if (isInvalidSelectorError(e) && /select the iframe first/.test(e.message)) { sel = 'iframe >> ' + sel; } else throw e; } Prevention
- Always select the iframe element before enter-frame.
- Use page.frameLocator('iframe').locator(...) instead of raw enter-frame tokens.
- Never place two enter-frame tokens back-to-back.
When it happens
Trigger: Writing a selector that starts with frame descent, e.g. internal:control=enter-frame >> button (no iframe chosen first), or two enter-frame tokens back to back without an iframe selector between them.
Common situations: Authoring iframe selectors by hand and forgetting the iframe locator prefix; chaining frame() calls incorrectly; converting a frame locator chain to a raw selector string and dropping the iframe token.
Related errors
- " " is only allowed as the first selector token, while…
- Selector cannot end with entering frame, while parsing…
- Can not *-capture inside a frame-piercing selector, while…
- Can not capture the selector before diving into the frame…
- " " selector cannot be first
AI-assisted analysis of microsoft/playwright@de214f440b (2026-09-01).
Data as JSON: /api/errors/d6548a7d9e101052.
Report an issue: GitHub.
Appendix: source
Thrown at packages/isomorphic/selectorParser.ts:129
let chunk: ParsedSelector = {
parts: [],
};
let anyFrame = false;
let chunkStartIndex = 0;
for (let i = 0; i < selector.parts.length; ++i) {
const part = selector.parts[i];
if (part.name === 'internal:control' && part.body === 'any-frame') {
// The starting frame applies to the whole selector, so the token only makes sense as the very first one.
if (i !== 0)
throw new InvalidSelectorError(`"${part.body}" is only allowed as the first selector token, while parsing selector ${selectorText}`);
anyFrame = true;
chunkStartIndex = i + 1;
continue;
}
if (part.name === 'internal:control' && part.body === 'enter-frame') {
const lastPart = chunk.parts[chunk.parts.length - 1];
if (!lastPart || (lastPart.name === 'internal:control' && lastPart.body === 'enter-frame'))
throw new InvalidSelectorError('Selector cannot start with entering frame, select the iframe first');
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 (anyFrame && !chunks.length)
throw new InvalidSelectorError(`Selector cannot be empty after frameLocator(), 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);View on GitHub (pinned to de214f440b)