From 123b2d6b888c5892d0357e673dc67252446ef23e Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Wed, 19 Nov 2025 16:07:05 +0100 Subject: [PATCH] accessibilitySnapshot({ page, searchString: /textarea|form/i }) --- playwriter/src/mcp.ts | 48 +++++++++++++++++++++++++++++++++++++--- playwriter/src/prompt.md | 25 +++++++++++++++++++-- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/playwriter/src/mcp.ts b/playwriter/src/mcp.ts index cac11a3..4855318 100644 --- a/playwriter/src/mcp.ts +++ b/playwriter/src/mcp.ts @@ -46,7 +46,11 @@ interface VMContext { error: (...args: any[]) => void debug: (...args: any[]) => void } - accessibilitySnapshot: (page: Page) => Promise + accessibilitySnapshot: (options: { + page: Page + searchString?: string | RegExp + contextLines?: number + }) => Promise resetPlaywright: () => Promise<{ page: Page; context: BrowserContext }> require: NodeRequire import: (specifier: string) => Promise @@ -223,14 +227,52 @@ server.tool( }, } - const accessibilitySnapshot = async (targetPage: Page) => { + const accessibilitySnapshot = async (options: { + page: Page + searchString?: string | RegExp + contextLines?: number + }) => { + const { page: targetPage, searchString, contextLines = 10 } = options if ((targetPage as any)._snapshotForAI) { const snapshot = await (targetPage as any)._snapshotForAI() const snapshotStr = typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2) - return snapshotStr + + if (!searchString) { + return snapshotStr + } + + const lines = snapshotStr.split('\n') + const matches: { line: string; index: number }[] = [] + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + let isMatch = false + if (searchString instanceof RegExp) { + isMatch = searchString.test(line) + } else { + isMatch = line.includes(searchString) + } + + if (isMatch) { + matches.push({ line, index: i }) + if (matches.length >= 10) break + } + } + + if (matches.length === 0) { + return 'No matches found' + } + + return matches + .map((m) => { + const start = Math.max(0, m.index - contextLines) + const end = Math.min(lines.length, m.index + contextLines + 1) + return lines.slice(start, end).join('\n') + }) + .join('\n\n---\n\n') } throw new Error('accessibilitySnapshot is not available on this page') } diff --git a/playwriter/src/prompt.md b/playwriter/src/prompt.md index 2a3b0dd..ac2e5ff 100644 --- a/playwriter/src/prompt.md +++ b/playwriter/src/prompt.md @@ -44,7 +44,7 @@ await state.localhostPage.bringToFront(); after you click a button or submit a form you ALWAYS have to then check what is the current state of the page. you cannot assume what happened after doing an action. instead run the following code to know what happened after the action: -`console.log('url:', page.url()); console.log(await accessibilitySnapshot(page).then(x => x.slice(0, 1000)));` +`console.log('url:', page.url()); console.log(await accessibilitySnapshot({ page }).then(x => x.slice(0, 1000)));` if nothing happened you may need to wait before the action completes, using something like `page.waitForNavigation({timeout: 3000})` or `await page.waitForLoadState('networkidle', {timeout: 3000})` @@ -59,7 +59,10 @@ always detach event listener you create at the end of a message using `page.remo you have access to some functions in addition to playwright methods: -- `async accessibilitySnapshot(page)`: gets a human readable snapshot of clickable elements on the page. useful to see the overall structure of the page and what elements you can interact with +- `async accessibilitySnapshot({ page, searchString, contextLines })`: gets a human readable snapshot of clickable elements on the page. useful to see the overall structure of the page and what elements you can interact with. + - `page`: the page object to snapshot + - `searchString`: (optional) a string or regex to filter the snapshot. If provided, returns the first 10 matches with surrounding context + - `contextLines`: (optional) number of lines of context to show around each match (default: 10) - `async resetPlaywright()`: recreates the CDP connection and resets the browser/page/context. Use this when the MCP stops responding, you get connection errors, assertion failures, or timeout issues. After calling this, the page and context variables are automatically updated in the execution environment. IMPORTANT: this completely resets the execution context, removing any custom properties you may have added to the global scope AND clearing all keys from the `state` object. Only `page`, `context`, `state` (empty), `console`, and utility functions will remain To bring a tab to front and focus it, use the standard Playwright method `await page.bringToFront()` @@ -88,6 +91,24 @@ Then you can use `page.locator(`aria-ref=${ref}`)` to get an element with a spec IMPORTANT: notice that we do not add any quotes in `aria-ref`! it MUST be called without quotes +## finding specific elements with snapshot + +You can use `searchString` to find specific elements in the snapshot without reading the whole page structure. This is useful for finding forms, textareas, or specific text. + +Example: find a textarea or form using case-insensitive regex: + +```js +const snapshot = await accessibilitySnapshot({ page, searchString: /textarea|form/i }) +console.log(snapshot) +``` + +Example: find elements containing "Login": + +```js +const snapshot = await accessibilitySnapshot({ page, searchString: "Login" }) +console.log(snapshot) +``` + ## getting outputs of code execution You can use `console.log` to print values you want to see in the tool call result. For seeing logs across runs you can store then in `state.logs` and then print them later, filtering and paginating them too.