{"record":{"id":"dfc166c3688f9f2f","repo":"microsoft/playwright","slug":"composite-locators-are-not-supported-with-piercing","errorCode":null,"errorMessage":"Composite locators are not supported with piercing frames, while querying \"${locator}\"","messagePattern":"Composite locators are not supported with piercing frames, while querying \"(.+?)\"","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/server/frameSelectors.ts","lineNumber":134,"sourceCode":"    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);\n    return result ? [result] : [];\n  }\n\n  private async _resolveChainedSelector(selector: string, options: types.StrictOptions, frameChunks: ParsedSelector[], scope: ElementHandle | undefined): Promise<SelectorInFrame | null> {","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/server/frameSelectors.ts#L116-L152","documentation":"When pierce-frames mode is active (browserContext option pierceFrames, or internal piercing), composite (multi-part chained) locators are not supported. If a nested selector part is encountered while pierce is on, InvalidSelectorError is thrown.","triggerScenarios":"Enabling pierceFrames on a BrowserContext (or using piercing selectors) and then issuing a composite/chained locator that has nested parts, which the piercing resolver cannot decompose.","commonSituations":"Turning on pierceFrames experimentally and reusing existing composite locators; frameworks that auto-generate chained selectors; refactoring tests without re-validating against piercing.","solutions":["Disable pierceFrames if you need composite locators, or","Simplify the locator to a single-part selector when piercing is enabled.","Refactor the query into a frameLocator chain that does not produce nested composite parts."],"exampleFix":"// before\nconst ctx = await browser.newContext({ pierceFrames: true });\nawait page.locator('div >> button').click(); // composite under pierce -> throws\n// after\nawait page.locator('button').click();","handlingStrategy":"validation","validationCode":"// Avoid composite (>>) selectors when piercing is on\nconst isPiercing = !!context.options()?.pierceFrames;\nif (isPiercing && sel.includes('>>')) throw new Error('no composite under pierce');","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Decide between pierceFrames and composite locators up front; do not mix.","Document the piercing mode in test fixtures so contributors use single-part locators."],"tags":["selector","pierce-frames","validation"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}