microsoft/playwright · error · InvalidSelectorError
Invalid frame in aria-ref selector
Error message
Invalid frame in aria-ref selector "${selector}" What it means
During internal aria-ref resolution (used by recorder/locator snapshots), Playwright extracts a frame sequence id from an aria-ref like 'f<seq>e<n>'. If no live frame in the page matches that sequence id, it throws InvalidSelectorError indicating the ref points at a frame that no longer exists.
Solutions
- Re-capture the selector/locator against the current page state.
- Wait for the target iframe to attach (frameLocator + waitFor) before resolving.
- Prefer stable, semantic locators (getByRole) over recorded refs when possible.
- Avoid reusing aria-refs across page navigations or sessions.
Example fix
null
Defensive patterns
Strategy: validation
Validate before calling
// Ensure the frame exists before using a recorded locator const frames = page.frames(); if (!frames.some(f => f.url().includes(expectedHost))) await page.waitForURL(expectedHost);
Type guard
null
Try / catch
null
Prevention
- Treat aria-refs as ephemeral; do not hardcode them.
- Re-record after intentional DOM/iframe restructuring.
When it happens
Trigger: Replaying a captured aria-ref/locator snapshot against a page where the referenced iframe has been removed, navigated, or not yet attached. Typically produced by recorder/replay or by reusing a stale ref captured in a previous session.
Common situations: Locator recorded against one page state and replayed after the DOM/iframes changed; iframes added dynamically and not yet present; cross-frame recording in flaky tests; recorder artifacts from older builds.
Related errors
- Can not *-capture inside a frame-piercing selector, while…
- Can not capture the selector before diving into the frame…
- " " is only allowed as the first selector token, while…
- Selector cannot be empty when piercing frames, while…
- Selector cannot end with entering frame, while parsing…
AI-assisted analysis of microsoft/playwright@500c9c822c (2026-08-12).
Data as JSON: /api/errors/d0c9dde322c6b8ae.
Report an issue: GitHub.
Appendix: source
Thrown at packages/playwright-core/src/server/frameSelectors.ts:131
return [];
// Note: adopting elements one by one may be slow. If we encounter the issue here,
// we might introduce 'useMainContext' option or similar to speed things up.
const targetContext = await elementHandles[0]._frame.mainContext();
return Promise.all(elementHandles.map(handle => adoptIfNeeded(handle, targetContext)));
}
private _jumpToAriaRefFrameIfNeeded(selector: string, info: SelectorInfo, frame: Frame): Frame {
if (info.parsed.parts[0].name !== 'aria-ref')
return frame;
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;
}
async resolveFramesForSelector(selector: string, options: types.StrictOptions & { anyFrame?: boolean } = {}, scope?: ElementHandle): Promise<SelectorInFrame[]> {
const { anyFrame, chunks } = splitSelectorByFrame(selector);
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 && part.name === 'internal:control' && part.body === 'any-frame') {
const locator = asLocator(this.frame._page.browserContext._browser.sdkLanguage(), selector);
throw new InvalidSelectorError(`frameLocator() is not allowed inside composite locators, while querying "${locator}"`);
}
});
}
View on GitHub (pinned to 500c9c822c)