microsoft/playwright · error · NonRecoverableDOMError
Pierce-frame mode matched elements from multiple frames.
Error message
Pierce-frame mode matched elements from multiple frames.
What it means
In pierce-frames mode the resolver may fan out across multiple iframes. If the selector matches elements in more than one frame, the result is ambiguous, so Playwright throws NonRecoverableDOMError ('Pierce-frame mode matched elements from multiple frames.') rather than silently picking one.
Source
Thrown at packages/playwright-core/src/server/frameSelectors.ts:331
return func({ injected, elements, info: params.info }, params.arg);
}, { info, scope, functionText: String(pageFunction), arg, callWithoutMatches: options.callWithoutMatches, markTargets: options.markTargets, returnByValue });
if (returnByValue && evalResult === '--playwright--no--result--value--')
return;
if (!returnByValue && (evalResult as JSHandle)._value === '--playwright--no--result--value--') {
(evalResult as JSHandle).dispose();
return;
}
return { result: evalResult as R | SmartHandle<R> };
};
const maybeResult = noStall ? await frame.raceAgainstEvaluationStallingEvents(getResult).catch(e => {
if (e instanceof EvaluationStalledError)
return;
throw e;
}) : await getResult();
if (!maybeResult)
continue;
if (aggregatedResult)
throw new NonRecoverableDOMError(`Pierce-frame mode matched elements from multiple frames.`);
aggregatedResult = { frame, info, result: maybeResult.result };
}
return aggregatedResult;
}
async callOnSelector<Arg, R>(
selector: string,
options: types.StrictOptions & { mainWorld?: boolean, callWithoutMatches?: boolean, scope?: ElementHandle, markTargets?: 'all' | 'first' | 'none', noDefaultPierce?: boolean },
pageFunction: MatchedElementsCallback<Arg, R>,
arg: Arg,
): Promise<{ frame: Frame, info: SelectorInfo, result: R } | null> {
const result = await this._callOnSelectorInternal(selector, options, pageFunction, arg, true /* returnByValue */);
return result as { frame: Frame, info: SelectorInfo, result: R } | null;
}
async callOnSelectorHandle<Arg, R>(
selector: string,
options: types.StrictOptions & { mainWorld?: boolean, scope?: ElementHandle, markTargets?: 'all' | 'first' | 'none' },View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Make the selector more specific so it matches in only one frame.
- Target the desired frame explicitly via frameLocator rather than relying on piercing.
- Scope the query to a particular iframe element handle before querying inside.
- Disable pierceFrames and resolve frames manually.
Example fix
// before
await page.locator('button#go').click(); // matches 2 iframes -> throws
// after: target one frame
await page.frameLocator('iframe[name=main]').locator('button#go').click(); Defensive patterns
Strategy: validation
Validate before calling
// Pre-count matching frames to surface ambiguity early
const frames = page.frames().filter(f => await f.locator('button#go').count() > 0);
if (frames.length > 1) throw new Error('selector matches multiple frames'); Type guard
null
Try / catch
null
Prevention
- Prefer explicit frameLocator over pierce-mode ambiguity.
- Make selectors unique across the iframe subtree.
When it happens
Trigger: Using a piercing locator (pierceFrames on or a piercing selector) where the same selector text matches elements inside two or more iframes in the subtree.
Common situations: Pages with multiple iframes that happen to contain the same component/selector (e.g. repeated ad slots, duplicated widgets); tests that worked until a second iframe with the same content was added.
Related errors
- "${part.body}" is only allowed as the first selector token,
- Selector cannot start with entering frame, select the iframe
- Selector cannot be empty when piercing frames, while parsing
- Selector cannot end with entering frame, while parsing selec
- Can not capture the selector before diving into the frame. O
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/d1fb6d4697015c51.
Report an issue: GitHub.