{"record":{"id":"aaacd73a4e2fd56c","repo":"microsoft/playwright","slug":"frame-locators-are-not-allowed-inside-composite-lo","errorCode":null,"errorMessage":"Frame locators are not allowed inside composite locators, while querying \"${locator}\"","messagePattern":"Frame locators are not allowed inside composite locators, while querying \"(.+?)\"","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/frameSelectors.ts","lineNumber":130,"sourceCode":"    const body = info.parsed.parts[0].body as string;\n    const match = body.match(/^f(\\d+)e\\d+$/);\n    if (!match)\n      return frame;\n    const frameSeq = +match[1];\n    const jumptToFrame = this.frame._page.frameManager.frames().find(frame => frame.seq === frameSeq);\n    if (!jumptToFrame)\n      throw new InvalidSelectorError(`Invalid frame in aria-ref selector \"${selector}\"`);\n    return jumptToFrame;\n  }\n\n  private async _resolveFramesForSelector(selector: string, options: types.StrictOptions & { noDefaultPierce?: boolean } = {}, scope?: ElementHandle): Promise<SelectorInFrame[]> {\n    const pierceByDefault = !!this.frame._page.browserContext._options.pierceFrames && !options.noDefaultPierce;\n    const { pierce, chunks } = splitSelectorByFrame(selector, pierceByDefault);\n    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 && pierce) {\n          const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);\n          throw new InvalidSelectorError(`Composite locators are not supported with piercing frames, while querying \"${locator}\"`);\n        }\n      });\n    }\n\n    if (pierce) {\n      const parsed = chunks[0];  // Only one chunk is allowed with pierce, it may contain enter-frame parts.\n      if (parsed.parts.some((part, index) => part.name === 'nth' && index !== parsed.parts.length - 1)) {\n        const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);\n        throw new InvalidSelectorError(`nth can only be the last locator when piercing frames, while querying \"${locator}\"`);\n      }\n      return await this._resolveFramePiercingSelector(parsed, options, scope);\n    }\n\n    const result = await this._resolveChainedSelector(selector, options, chunks, scope);","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/frameSelectors.ts#L112-L148","documentation":"Frame locators (the internal:control enter-frame part, i.e. frameLocator(...).find(...)) cannot be nested inside a composite (chained) locator. visitAllSelectorParts flags any nested enter-frame part and throws InvalidSelectorError naming the offending locator.","triggerScenarios":"Composing a selector where an iframe-descending locator is used as an inner/non-final part of a larger chain, e.g. combining frameLocator(...).locator(...) as an intermediate segment in a multi-part locator string.","commonSituations":"Building complex selectors by concatenation; converting hand-written query strings that mix iframe traversal with other composite operators; migrations from older selector syntax.","solutions":["Restructure so the frameLocator is the outer chain and only descendant locators live inside it.","Use the frameLocator(...).locator(...).locator(...) API rather than string concatenation.","Split the operation into explicit frame resolution + a within-frame locator query."],"exampleFix":"// before (conceptual): enter-frame nested inside a composite\npage.locator('iframe >> [data-x] >> iframe >> button'); // invalid nesting\n// after: chain frame locators via the API\npage.frameLocator('iframe').locator('[data-x]').locator('button');","handlingStrategy":"validation","validationCode":"// Reject nested frame-locator strings before sending them\nfunction hasNestedFrameLocator(sel: string) {\n  return (sel.match(/>>(?:[^>]*>>){2,}/) || []).length > 0;\n}","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Use the Locator API (frameLocator/locator) instead of raw selector strings.","Never concatenate iframe-traversal tokens into one composite string."],"tags":["selector","frame-locator","validation"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}