microsoft/playwright · error · InvalidSelectorError

Frame locators are not allowed inside composite locators, wh

Error message

Frame locators are not allowed inside composite locators, while querying "${locator}"

What it means

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.

Source

Thrown at packages/playwright-core/src/server/frameSelectors.ts:130

    const body = info.parsed.parts[0].body as string;
    const match = body.match(/^f(\d+)e\d+$/);
    if (!match)
      return frame;
    const frameSeq = +match[1];
    const jumptToFrame = this.frame._page.frameManager.frames().find(frame => frame.seq === frameSeq);
    if (!jumptToFrame)
      throw new InvalidSelectorError(`Invalid frame in aria-ref selector "${selector}"`);
    return jumptToFrame;
  }

  private async _resolveFramesForSelector(selector: string, options: types.StrictOptions & { noDefaultPierce?: boolean } = {}, scope?: ElementHandle): Promise<SelectorInFrame[]> {
    const pierceByDefault = !!this.frame._page.browserContext._options.pierceFrames && !options.noDefaultPierce;
    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);

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Restructure so the frameLocator is the outer chain and only descendant locators live inside it.
  2. Use the frameLocator(...).locator(...).locator(...) API rather than string concatenation.
  3. Split the operation into explicit frame resolution + a within-frame locator query.

Example fix

// before (conceptual): enter-frame nested inside a composite
page.locator('iframe >> [data-x] >> iframe >> button'); // invalid nesting
// after: chain frame locators via the API
page.frameLocator('iframe').locator('[data-x]').locator('button');
Defensive patterns

Strategy: validation

Validate before calling

// Reject nested frame-locator strings before sending them
function hasNestedFrameLocator(sel: string) {
  return (sel.match(/>>(?:[^>]*>>){2,}/) || []).length > 0;
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: 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.

Common situations: Building complex selectors by concatenation; converting hand-written query strings that mix iframe traversal with other composite operators; migrations from older selector syntax.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/aaacd73a4e2fd56c. Report an issue: GitHub.