{"record":{"id":"1abe984c584bd589","repo":"can1357/oh-my-pi","slug":"playwright-only-selector-json-stringify-selector","errorCode":null,"errorMessage":"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/...\".","messagePattern":"Playwright-only selector (.+?) is not supported by the browser tool\\. Use a puppeteer text selector \\(\"text/Allow all\"\\), an aria selector \\(\"aria/Name\"\\), CSS, or \"xpath/\\.\\.\\.\"\\.","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/browser/tab-worker.ts","lineNumber":284,"sourceCode":"\t\tselector: string,\n\t\topts?: { timeout?: number; visible?: boolean; hidden?: boolean },\n\t): Promise<ActionableHandle | null>;\n\twaitForNavigation(opts?: {\n\t\twaitUntil?: \"load\" | \"domcontentloaded\" | \"networkidle0\" | \"networkidle2\";\n\t\ttimeout?: number;\n\t}): Promise<HTTPResponse | null>;\n\tid(n: number): Promise<ActionableHandle>;\n\tref(id: string): Promise<ActionableHandle>;\n}\n\nexport function normalizeSelector(selector: string): string {\n\tassertSelectorString(selector);\n\tif (!selector) return selector;\n\tif (\n\t\t!SELECTOR_HANDLER_PREFIXES.some(prefix => selector.startsWith(prefix)) &&\n\t\tPLAYWRIGHT_ONLY_SELECTOR_RE.test(selector)\n\t) {\n\t\tthrow new ToolError(\n\t\t\t`Playwright-only selector ${JSON.stringify(selector)} is not supported by the browser tool. ` +\n\t\t\t\t`Use a puppeteer text selector (\"text/Allow all\"), an aria selector (\"aria/Name\"), CSS, or \"xpath/...\".`,\n\t\t);\n\t}\n\tif (selector.startsWith(\"p-\") && !LEGACY_SELECTOR_PREFIXES.some(prefix => selector.startsWith(prefix))) {\n\t\tthrow new ToolError(\n\t\t\t`Unsupported selector prefix. Use CSS or puppeteer query handlers (aria/, text/, xpath/, pierce/). Got: ${selector}`,\n\t\t);\n\t}\n\tif (selector.startsWith(\"p-text/\")) return `text/${selector.slice(\"p-text/\".length)}`;\n\tif (selector.startsWith(\"p-xpath/\")) return `xpath/${selector.slice(\"p-xpath/\".length)}`;\n\tif (selector.startsWith(\"p-pierce/\")) return `pierce/${selector.slice(\"p-pierce/\".length)}`;\n\tif (selector.startsWith(\"p-aria/\")) {\n\t\tconst rest = selector.slice(\"p-aria/\".length);\n\t\tconst nameMatch = rest.match(/\\[\\s*name\\s*=\\s*(?:\"([^\"]+)\"|'([^']+)'|([^\\]]+))\\s*\\]/);\n\t\tconst name = nameMatch?.[1] ?? nameMatch?.[2] ?? nameMatch?.[3];\n\t\tif (name) return `aria/${name.trim()}`;\n\t\treturn `aria/${rest}`;","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/browser/tab-worker.ts#L266-L302","documentation":"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.","triggerScenarios":"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.","commonSituations":"Developers porting Playwright scripts or copy-pasting selectors from Playwright codegen/examples into the browser tool; LLM-generated selectors using Playwright conventions.","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."],"exampleFix":"// before\ntab.click('button:has-text(\"Allow all\")');\n// after\ntab.click('text/Allow all');","handlingStrategy":"validation","validationCode":"const PLAYWRIGHT_ONLY = /(role=|:has-text\\(|:left-of\\(|:right-of\\(|:near\\(|::-p-)/;\nif (PLAYWRIGHT_ONLY.test(selector)) {\n  throw new Error(`convert Playwright selector to aria/, text/, CSS, or xpath/: ${selector}`);\n}","typeGuard":"function isSupportedSelector(s: string): boolean {\n  return !/(role=|:has-text\\(|::-p-)/.test(s);\n}","tryCatchPattern":null,"preventionTips":["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()"],"tags":["selector","playwright","puppeteer","browser-tool"],"backgroundTag":"unsupported-selector-syntax","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}