{"record":{"id":"3354383261fc402b","repo":"microsoft/playwright","slug":"nth-can-only-be-the-last-locator-when-piercing-fra","errorCode":null,"errorMessage":"nth can only be the last locator when piercing frames, while querying \"${locator}\"","messagePattern":"nth can only be the last locator when piercing frames, while querying \"(.+?)\"","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/frameSelectors.ts","lineNumber":155,"sourceCode":"    for (const chunk of chunks) {\n      visitAllSelectorParts(chunk, (part, nested) => {\n        if (nested && part.name === 'internal:control' && part.body === 'enter-frame') {\n          const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);\n          throw new InvalidSelectorError(`Frame locators are not allowed inside composite locators, while querying \"${locator}\"`);\n        }\n        if (nested && part.name === 'internal:control' && part.body === 'any-frame') {\n          const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);\n          throw new InvalidSelectorError(`frameLocator() is not allowed inside composite locators, while querying \"${locator}\"`);\n        }\n      });\n    }\n\n    if (!anyFrame && !options.anyFrame) {\n      const result = await this._resolveChainedSelector(this.frame, selector, options, chunks, scope, false /* noStall */);\n      return result ? [result] : [];\n    }\n\n    const result: SelectorInFrame[] = [];\n    const seenFrames = new Set<Frame>();\n    for (const startFrame of await this._anyFrameCandidates(scope)) {\n      const resolved = await this._resolveChainedSelector(startFrame, selector, options, chunks, startFrame === this.frame ? scope : undefined, startFrame !== this.frame /* noStall */);\n      // Different starting frames may resolve into the same frame, e.g. with an aria-ref selector.\n      if (!resolved || seenFrames.has(resolved.frame))\n        continue;\n      seenFrames.add(resolved.frame);\n      result.push(resolved);\n    }\n    return result;\n  }\n\n  // All the frames the selector may start in: the frame subtree, restricted to the scope when given.\n  private async _anyFrameCandidates(scope: ElementHandle | undefined): Promise<Frame[]> {\n    const result: Frame[] = [];\n    const collectSubtree = (frame: Frame) => {\n      result.push(frame);\n      for (const child of frame.childFrames())","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/microsoft/playwright/blob/500c9c822ce7664539a4c8a88810048dfe090c3b/packages/playwright-core/src/server/frameSelectors.ts#L137-L173","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npage.locator('iframe >> button >> nth=0 >> span'); // nth not last\n// after\npage.frameLocator('iframe').locator('button').first().locator('span');","handlingStrategy":"validation","validationCode":"// Ensure nth is only the last token when piercing\nconst parts = sel.split('>>').map(s => s.trim());\nconst nthIdx = parts.findIndex(p => p.startsWith('nth='));\nif (nthIdx !== -1 && nthIdx !== parts.length - 1) throw new Error('nth must be last');","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Use .first()/.nth() methods on the Locator object rather than inline nth tokens.","Re-validate locators after enabling pierceFrames."],"tags":["selector","pierce-frames","nth"],"backgroundTag":null,"analyzedSha":"500c9c822ce7664539a4c8a88810048dfe090c3b","analyzedAt":"2026-08-12T07:26:36.950Z","contentChangedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}