can1357/oh-my-pi · error · ToolError
Playwright-only selector ${JSON.stringify(selector)} is not
Error message
Playwright-only selector ${JSON.stringify(selector)} is not supported by the browser tool. Use a puppeteer text selector ("text/Allow all"), an aria selector ("aria/Name"), CSS, or "xpath/...". What it means
The browser tool only accepts puppeteer-compatible selectors: CSS, or query-handler prefixed selectors (`aria/`, `text/`, `xpath/`, `pierce/`, plus legacy prefixes). If a selector matches none of the supported prefixes but looks like a Playwright-specific syntax (e.g. `role=button[name="x"]`, `:has-text(...)`, `::-p-text(...)`), normalizeSelector rejects it with this message.
Source
Thrown at packages/coding-agent/src/tools/browser/tab-worker.ts:284
selector: string,
opts?: { timeout?: number; visible?: boolean; hidden?: boolean },
): Promise<ActionableHandle | null>;
waitForNavigation(opts?: {
waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2";
timeout?: number;
}): Promise<HTTPResponse | null>;
id(n: number): Promise<ActionableHandle>;
ref(id: string): Promise<ActionableHandle>;
}
export function normalizeSelector(selector: string): string {
assertSelectorString(selector);
if (!selector) return selector;
if (
!SELECTOR_HANDLER_PREFIXES.some(prefix => selector.startsWith(prefix)) &&
PLAYWRIGHT_ONLY_SELECTOR_RE.test(selector)
) {
throw new ToolError(
`Playwright-only selector ${JSON.stringify(selector)} is not supported by the browser tool. ` +
`Use a puppeteer text selector ("text/Allow all"), an aria selector ("aria/Name"), CSS, or "xpath/...".`,
);
}
if (selector.startsWith("p-") && !LEGACY_SELECTOR_PREFIXES.some(prefix => selector.startsWith(prefix))) {
throw new ToolError(
`Unsupported selector prefix. Use CSS or puppeteer query handlers (aria/, text/, xpath/, pierce/). Got: ${selector}`,
);
}
if (selector.startsWith("p-text/")) return `text/${selector.slice("p-text/".length)}`;
if (selector.startsWith("p-xpath/")) return `xpath/${selector.slice("p-xpath/".length)}`;
if (selector.startsWith("p-pierce/")) return `pierce/${selector.slice("p-pierce/".length)}`;
if (selector.startsWith("p-aria/")) {
const rest = selector.slice("p-aria/".length);
const nameMatch = rest.match(/\[\s*name\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\]]+))\s*\]/);
const name = nameMatch?.[1] ?? nameMatch?.[2] ?? nameMatch?.[3];
if (name) return `aria/${name.trim()}`;
return `aria/${rest}`;View on GitHub (pinned to 9690622007)
Solutions
- Convert Playwright role selectors to the aria handler: `role=button[name="Submit"]` → `aria/Submit`.
- Convert Playwright text selectors: `text=Allow all` or `:has-text(...)` → `text/Allow all`.
- Use plain CSS for structural matches, or `xpath/...` for XPath expressions.
- If using legacy `p-` prefixed handlers, migrate to the unprefixed forms (`p-text/` → `text/`); bare `p-` selectors now throw.
Example fix
// before
tab.click('button:has-text("Allow all")');
// after
tab.click('text/Allow all'); Defensive patterns
Strategy: validation
Validate before calling
const PLAYWRIGHT_ONLY = /(role=|:has-text\(|:left-of\(|:right-of\(|:near\(|::-p-)/;
if (PLAYWRIGHT_ONLY.test(selector)) {
throw new Error(`convert Playwright selector to aria/, text/, CSS, or xpath/: ${selector}`);
} Type guard
function isSupportedSelector(s: string): boolean {
return !/(role=|:has-text\(|::-p-)/.test(s);
} Prevention
- Never copy selectors from Playwright codegen into the browser tool
- Use aria/<name> or text/<label> for role/text matching
- Lint stored prompts/playbooks for Playwright syntax like :has-text()
When it happens
Trigger: Passing a Playwright locator string to tab.click/handles/fill/etc., e.g. `page.getByRole(...)` strings like `role=button[name="Submit"]`, or selectors using Playwright pseudo-classes like `:has-text("...")`, `:right-of(...)`, or `text="exact"` without the `text/` prefix.
Common situations: Developers porting Playwright scripts or copy-pasting selectors from Playwright codegen/examples into the browser tool; LLM-generated selectors using Playwright conventions.
Related errors
- Unsupported selector prefix. Use CSS or puppeteer query hand
- Timed out clicking ${selector} (seen ${lastSeen} matches; la
- Selected ${sourceName} session is no longer available
- Browser selector must be a string; got ${kind}. tab.click/ty
- tab.ariaSnapshot: selector ${__sel} matched no element
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1abe984c584bd589.
Report an issue: GitHub.