microsoft/playwright · error · InvalidSelectorError
nth can only be the last locator when piercing frames, while
Error message
nth can only be the last locator when piercing frames, while querying "${locator}" What it means
In pierce-frames mode, the nth pseudo-part (>> nth=N) is only valid as the final part of a selector. If nth appears anywhere except the last position, InvalidSelectorError is thrown because the piercing resolver cannot reorder it.
Source
Thrown at packages/playwright-core/src/server/frameSelectors.ts:143
const { pierce, chunks } = splitSelectorByFrame(selector, pierceByDefault);
for (const chunk of chunks) {
visitAllSelectorParts(chunk, (part, nested) => {
if (nested && part.name === 'internal:control' && part.body === 'enter-frame') {
const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);
throw new InvalidSelectorError(`Frame locators are not allowed inside composite locators, while querying "${locator}"`);
}
if (nested && pierce) {
const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);
throw new InvalidSelectorError(`Composite locators are not supported with piercing frames, while querying "${locator}"`);
}
});
}
if (pierce) {
const parsed = chunks[0]; // Only one chunk is allowed with pierce, it may contain enter-frame parts.
if (parsed.parts.some((part, index) => part.name === 'nth' && index !== parsed.parts.length - 1)) {
const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);
throw new InvalidSelectorError(`nth can only be the last locator when piercing frames, while querying "${locator}"`);
}
return await this._resolveFramePiercingSelector(parsed, options, scope);
}
const result = await this._resolveChainedSelector(selector, options, chunks, scope);
return result ? [result] : [];
}
private async _resolveChainedSelector(selector: string, options: types.StrictOptions, frameChunks: ParsedSelector[], scope: ElementHandle | undefined): Promise<SelectorInFrame | null> {
let frame: Frame = this.frame;
for (let i = 0; i < frameChunks.length - 1; ++i) {
const info = this._parseSelector(frameChunks[i], options);
frame = this._jumpToAriaRefFrameIfNeeded(selector, info, frame);
const context = await frame.context(info.world);
const injectedScript = await context.injectedScript();
const handle = await injectedScript.evaluateHandle((injected, { info, scope, selectorString }) => {
const element = injected.querySelector(info.parsed, scope || document, info.strict);
if (element && element.nodeName !== 'IFRAME' && element.nodeName !== 'FRAME')View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Move nth to the end of the locator string.
- Re-record/re-generate the locator so nth is last.
- Wrap with frameLocator/locator API and call .first()/.nth() as a method on the final locator instead of an inline token.
Example fix
// before
page.locator('iframe >> button >> nth=0 >> span'); // nth not last
// after
page.frameLocator('iframe').locator('button').first().locator('span'); Defensive patterns
Strategy: validation
Validate before calling
// Ensure nth is only the last token when piercing
const parts = sel.split('>>').map(s => s.trim());
const nthIdx = parts.findIndex(p => p.startsWith('nth='));
if (nthIdx !== -1 && nthIdx !== parts.length - 1) throw new Error('nth must be last'); Type guard
null
Try / catch
null
Prevention
- Use .first()/.nth() methods on the Locator object rather than inline nth tokens.
- Re-validate locators after enabling pierceFrames.
When it happens
Trigger: Using a piercing locator whose nth component is not last, e.g. locator('iframe >> button >> nth=0 >> span') with pierceFrames on, or chaining after nth.
Common situations: Authoring locators by hand with nth in the middle; generated locators from recorder placing nth before trailing parts; enabling piercing on existing nth-bearing locators.
Related errors
- Composite locators are not supported with piercing frames, w
- Pierce-frame mode matched elements from multiple frames.
- Unsupported token "${unsupportedToken.toSource()}" while par
- Error while parsing css selector "${selector}". Did you mean
- Conflicting exactness in internal:role selector: ${stringify
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/3354383261fc402b.
Report an issue: GitHub.