diff --git a/PLAYWRITER_AGENTS.md b/PLAYWRITER_AGENTS.md index c7bd861..393af6a 100644 --- a/PLAYWRITER_AGENTS.md +++ b/PLAYWRITER_AGENTS.md @@ -57,7 +57,7 @@ to test CLI changes without publishing: Get-NetTCPConnection -LocalPort 19988 | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force } tsx playwriter/src/cli.ts -s 1 -e "await page.goto('https://example.com')" - tsx playwriter/src/cli.ts -s 1 -e "console.log(await accessibilitySnapshot({ page }))" + tsx playwriter/src/cli.ts -s 1 -e "console.log(await snapshot({ page }))" tsx playwriter/src/cli.ts session new tsx playwriter/src/cli.ts -s 1 -e "await page.click('button')" ``` diff --git a/README.md b/README.md index 8b0b955..14ecee1 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ ```bash playwriter session new # creates stateful sandbox, outputs session id (e.g. 1) playwriter -s 1 -e "await page.goto('https://example.com')" -playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page }))" +playwriter -s 1 -e "console.log(await snapshot({ page }))" playwriter -s 1 -e "await page.locator('aria-ref=e5').click()" ``` diff --git a/docs/framer-iframe-snapshot-guide.md b/docs/framer-iframe-snapshot-guide.md index 996ba70..d1b9a68 100644 --- a/docs/framer-iframe-snapshot-guide.md +++ b/docs/framer-iframe-snapshot-guide.md @@ -1,9 +1,9 @@ --- title: Framer Plugin Iframe Snapshot Guide -description: Step-by-step instructions to open the Framer MCP plugin iframe and run accessibilitySnapshot on it. +description: Step-by-step instructions to open the Framer MCP plugin iframe and run snapshot on it. prompt: | Create a concise step-by-step guide to open the Framer plugin iframe and - verify accessibilitySnapshot on that iframe using playwriter CLI. Include the + verify snapshot on that iframe using playwriter CLI. Include the exact Framer project URL and the plugins.framercdn iframe URL. Also document the Command+K workflow to open the MCP plugin: press Command+K, search for MCP in the command palette, press Enter, then wait ~1 second for the iframe to @@ -33,7 +33,7 @@ playwriter -s 1 -e "const target = 'https://framer.com/projects/unframer-source- - Verify the palette is open (look for the command dialog and MCP entry in the snapshot output): ```bash -playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page, search: /dialog|Search…|MCP/ }));" +playwriter -s 1 -e "console.log(await snapshot({ page, search: /dialog|Search…|MCP/ }));" ``` - Search for **MCP**, press Enter, then wait about 1 second for the plugin iframe to appear. @@ -55,17 +55,17 @@ playwriter -s 1 -e "const iframe = page.locator(\"iframe[src*='plugins.framercdn - Run the accessibility snapshot on that iframe using `contentFrame()` (FrameLocator is auto-resolved to Frame): ```bash -playwriter -s 1 -e "const frame = await page.locator(\"iframe[src*='plugins.framercdn.com']\").contentFrame(); console.log(await accessibilitySnapshot({ page, frame }));" +playwriter -s 1 -e "const frame = await page.locator(\"iframe[src*='plugins.framercdn.com']\").contentFrame(); console.log(await snapshot({ page, frame }));" ``` - Alternative: use `page.frames()` to get the Frame directly: ```bash -playwriter -s 1 -e "const frame = page.frames().find(f => f.url().includes('plugins.framercdn.com')); console.log(await accessibilitySnapshot({ page, frame }));" +playwriter -s 1 -e "const frame = page.frames().find(f => f.url().includes('plugins.framercdn.com')); console.log(await snapshot({ page, frame }));" ``` - Validate the snapshot contains MCP UI text (confirms the panel is actually loaded): ```bash -playwriter -s 1 -e "const frame = await page.locator(\"iframe[src*='plugins.framercdn.com']\").contentFrame(); console.log(await accessibilitySnapshot({ page, frame, search: /Control Framer with MCP|Login With Google/ }));" +playwriter -s 1 -e "const frame = await page.locator(\"iframe[src*='plugins.framercdn.com']\").contentFrame(); console.log(await snapshot({ page, frame, search: /Control Framer with MCP|Login With Google/ }));" ``` ## Expected iframe URL diff --git a/docs/remote-access.md b/docs/remote-access.md index 0fb0171..7bd8a16 100644 --- a/docs/remote-access.md +++ b/docs/remote-access.md @@ -99,7 +99,7 @@ The **CLI with the skill** is the recommended approach. The skill file (`playwri ```bash playwriter session new # outputs: 1 playwriter -s 1 -e "await page.goto('https://example.com')" -playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page }))" +playwriter -s 1 -e "console.log(await snapshot({ page }))" ``` Alternatively, pass host and token as flags instead of env vars: diff --git a/extension/src/welcome.html b/extension/src/welcome.html index 7661c59..57b433b 100644 --- a/extension/src/welcome.html +++ b/extension/src/welcome.html @@ -325,7 +325,7 @@ playwriter session new # navigate to a URL playwriter -s 1 -e "await page.goto('https://example.com')" # get the accessibility tree of the page -playwriter -s 1 -e "console.log(await accessibilitySnapshot({ page }))" +playwriter -s 1 -e "console.log(await snapshot({ page }))" # click an element by its accessibility reference playwriter -s 1 -e "await page.locator('aria-ref=e5').click()" diff --git a/playwriter/src/executor.ts b/playwriter/src/executor.ts index 0cc8907..00c3643 100644 --- a/playwriter/src/executor.ts +++ b/playwriter/src/executor.ts @@ -548,7 +548,7 @@ export class PlaywrightExecutor { }, } - const accessibilitySnapshot = async (options: { + const snapshot = async (options: { page?: Page /** Optional frame to scope the snapshot (e.g. from iframe.contentFrame() or page.frames()) */ frame?: Frame | FrameLocator @@ -564,7 +564,7 @@ export class PlaywrightExecutor { const { page: targetPage, frame, locator, search, showDiffSinceLastCall = true, interactiveOnly = false } = options const resolvedPage = targetPage || page if (!resolvedPage) { - throw new Error('accessibilitySnapshot requires a page') + throw new Error('snapshot requires a page') } // Use new in-page implementation via getAriaSnapshot @@ -800,7 +800,8 @@ export class PlaywrightExecutor { context, state: this.userState, console: customConsole, - accessibilitySnapshot, + snapshot, + accessibilitySnapshot: snapshot, // backward compat alias refToLocator, getCleanHTML, getPageMarkdown, diff --git a/playwriter/src/executor.unit.test.ts b/playwriter/src/executor.unit.test.ts index 3f67c60..9f7f28e 100644 --- a/playwriter/src/executor.unit.test.ts +++ b/playwriter/src/executor.unit.test.ts @@ -6,6 +6,7 @@ describe('shouldAutoReturn', () => { expect(shouldAutoReturn('1 + 2')).toBe(true) expect(shouldAutoReturn('page.title()')).toBe(true) expect(shouldAutoReturn('await page.title()')).toBe(true) + expect(shouldAutoReturn('snapshot({ page })')).toBe(true) expect(shouldAutoReturn('accessibilitySnapshot({ page })')).toBe(true) expect(shouldAutoReturn('context.pages().map(p => p.url())')).toBe(true) }) diff --git a/playwriter/src/relay-core.test.ts b/playwriter/src/relay-core.test.ts index ac49cab..e343e6f 100644 --- a/playwriter/src/relay-core.test.ts +++ b/playwriter/src/relay-core.test.ts @@ -172,7 +172,7 @@ describe('Relay Core Tests', () => { }) }, 30000) - const accessibilitySnapshotTestCases = [ + const snapshotTestCases = [ { name: 'hacker-news', url: 'https://news.ycombinator.com/item?id=1', @@ -185,7 +185,7 @@ describe('Relay Core Tests', () => { }, ] - for (const testCase of accessibilitySnapshotTestCases) { + for (const testCase of snapshotTestCases) { it(`should get accessibility snapshot of ${testCase.name}`, async () => { await client.callTool({ name: 'execute', @@ -205,8 +205,8 @@ describe('Relay Core Tests', () => { arguments: { code: js` await state.page.goto('${testCase.url}', { waitUntil: 'domcontentloaded' }); - const snapshot = await accessibilitySnapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: true }); - return snapshot; + const snap = await snapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: true }); + return snap; `, }, }) @@ -228,8 +228,8 @@ describe('Relay Core Tests', () => { name: 'execute', arguments: { code: js` - const snapshot = await accessibilitySnapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: false }); - return snapshot; + const snap = await snapshot({ page: state.page, showDiffSinceLastCall: false, interactiveOnly: false }); + return snap; `, }, }) diff --git a/playwriter/src/skill.md b/playwriter/src/skill.md index 8d61bf5..a5acfe1 100644 --- a/playwriter/src/skill.md +++ b/playwriter/src/skill.md @@ -69,11 +69,11 @@ playwriter -s 1 -e "await page.title()" playwriter -s 1 -e "await page.screenshot({ path: 'screenshot.png', scale: 'css' })" # Get accessibility snapshot -playwriter -s 1 -e "await accessibilitySnapshot({ page })" +playwriter -s 1 -e "await snapshot({ page })" # Get accessibility snapshot for a specific iframe const frame = await page.locator('iframe').contentFrame() -await accessibilitySnapshot({ frame }) +await snapshot({ frame }) ``` **Multiline code:** @@ -214,19 +214,19 @@ Each step is a separate execute call. Notice how every action is followed by a s // 1. Open page and observe state.myPage = context.pages().find(p => p.url() === 'about:blank') ?? await context.newPage(); await state.myPage.goto('https://framer.com/projects/my-project', { waitUntil: 'domcontentloaded' }); -await accessibilitySnapshot({ page: state.myPage }).then(console.log) +await snapshot({ page: state.myPage }).then(console.log) ``` ```js // 2. Act: open command palette → observe result await state.myPage.keyboard.press('Meta+k'); -await accessibilitySnapshot({ page: state.myPage, search: /dialog|Search/ }).then(console.log) +await snapshot({ page: state.myPage, search: /dialog|Search/ }).then(console.log) ``` ```js // 3. Act: type search query → observe result await state.myPage.keyboard.type('MCP'); -await accessibilitySnapshot({ page: state.myPage, search: /MCP/ }).then(console.log) +await snapshot({ page: state.myPage, search: /MCP/ }).then(console.log) ``` ```js @@ -234,7 +234,7 @@ await accessibilitySnapshot({ page: state.myPage, search: /MCP/ }).then(console. await state.myPage.keyboard.press('Enter'); await state.myPage.waitForTimeout(1000); const frame = state.myPage.frames().find(f => f.url().includes('plugins.framercdn.com')); -await accessibilitySnapshot({ page: state.myPage, frame: frame || undefined }).then(console.log) +await snapshot({ page: state.myPage, frame: frame || undefined }).then(console.log) ``` **Other ways to observe action results:** @@ -261,7 +261,7 @@ Snapshots are the primary feedback mechanism, but some actions have side effects Always check page state after important actions (form submissions, uploads, typing). Your mental model can diverge from actual browser state: ```js await page.keyboard.type('my text'); -await accessibilitySnapshot({ page, search: /my text/ }) +await snapshot({ page, search: /my text/ }) // If verifying visual layout specifically, use screenshotWithAccessibilityLabels instead ``` @@ -283,7 +283,7 @@ Locators (especially ones with `>> nth=`) can change when the page updates. Alwa await page.locator('[id="old-id"]').click(); // element may have changed // GOOD: get fresh snapshot, then immediately use locators from it -await accessibilitySnapshot({ page, showDiffSinceLastCall: true }) +await snapshot({ page, showDiffSinceLastCall: true }) // Now use the NEW locators from this output ``` @@ -330,7 +330,7 @@ Screenshots + image analysis is expensive and slow. Only use screenshots for vis await page.screenshot({ path: 'check.png', scale: 'css' }); // GOOD: snapshot is text — fast, cheap, searchable -await accessibilitySnapshot({ page, search: /expected text/i }) +await snapshot({ page, search: /expected text/i }) // GOOD: evaluate DOM directly for content checks const text = await page.evaluate(() => document.querySelector('.message')?.textContent); @@ -375,10 +375,10 @@ After any action (click, submit, navigate), verify what happened. **Always prefe ```js // Default: use snapshot with optional filtering -page.url() + '\n' + await accessibilitySnapshot({ page }) +page.url() + '\n' + await snapshot({ page }) // Filter for specific content when snapshot is large -await accessibilitySnapshot({ page, search: /dialog|button|error/i }) +await snapshot({ page, search: /dialog|button|error/i }) ``` Only use `screenshotWithAccessibilityLabels({ page })` for **visual layout issues** (CSS bugs, spatial positioning, colors). For verifying text content, button states, or form values, snapshots are always sufficient. @@ -388,9 +388,11 @@ If nothing changed, try `await waitForPageLoad({ page, timeout: 3000 })` or you ## accessibility snapshots ```js -await accessibilitySnapshot({ page, search?, showDiffSinceLastCall? }) +await snapshot({ page, search?, showDiffSinceLastCall? }) ``` +`accessibilitySnapshot` is still available as an alias for backward compatibility. + - `search` - string/regex to filter results (returns first 10 matching lines) - `showDiffSinceLastCall` - returns diff since last snapshot (default: `true`). Pass `false` to get full snapshot. @@ -413,7 +415,7 @@ to make it unique. If a screenshot shows ref labels like `e3`, resolve them using the last snapshot: ```js -const snapshot = await accessibilitySnapshot({ page }) +const snap = await snapshot({ page }) const locator = refToLocator({ ref: 'e3' }) await page.locator(locator!).click() ``` @@ -427,13 +429,13 @@ await page.locator('role=link[name="Blog"]').click() Search for specific elements: ```js -const snapshot = await accessibilitySnapshot({ page, search: /button|submit/i }) +const snap = await snapshot({ page, search: /button|submit/i }) ``` **Filtering large snapshots in JS** — when the built-in `search` isn't enough (e.g., you need multiple patterns or custom logic), filter the snapshot string directly: ```js -const snap = await accessibilitySnapshot({ page, showDiffSinceLastCall: false }); +const snap = await snapshot({ page, showDiffSinceLastCall: false }); const relevant = snap.split('\n').filter(l => l.includes('dialog') || l.includes('error') || l.includes('button') ).join('\n'); @@ -444,9 +446,9 @@ This is much cheaper than taking a screenshot — use it as your primary debuggi ## choosing between snapshot methods -Both `accessibilitySnapshot` and `screenshotWithAccessibilityLabels` use the same ref system, so you can combine them effectively. +Both `snapshot` and `screenshotWithAccessibilityLabels` use the same ref system, so you can combine them effectively. -**Use `accessibilitySnapshot` when:** +**Use `snapshot` when:** - Page has simple, semantic structure (articles, forms, lists) - You need to search for specific text or patterns - Token usage matters (text is smaller than images) @@ -458,11 +460,11 @@ Both `accessibilitySnapshot` and `screenshotWithAccessibilityLabels` use the sam - DOM order doesn't match visual order - You need to understand the visual hierarchy -**Combining both:** Use screenshot first to understand layout and identify target elements visually, then use `accessibilitySnapshot({ search: /pattern/ })` for efficient searching in subsequent calls. +**Combining both:** Use screenshot first to understand layout and identify target elements visually, then use `snapshot({ search: /pattern/ })` for efficient searching in subsequent calls. ## selector best practices -**For unknown websites**: use `accessibilitySnapshot()` - it shows what's actually interactive with stable locators. +**For unknown websites**: use `snapshot()` - it shows what's actually interactive with stable locators. **For development** (when you have source code access), prefer stable selectors in this order: @@ -746,7 +748,7 @@ await editor.edit({ url: matches[0].url, oldString: 'DEBUG = false', newString: **screenshotWithAccessibilityLabels** - take a screenshot with Vimium-style visual labels overlaid on interactive elements. Shows labels, captures screenshot, then removes labels. The image and accessibility snapshot are automatically included in the response. Can be called multiple times to capture multiple screenshots. Use a timeout of **20 seconds** for complex pages. -Prefer this for pages with grids, image galleries, maps, or complex visual layouts where spatial position matters. For simple text-heavy pages, `accessibilitySnapshot` with search is faster and uses fewer tokens. +Prefer this for pages with grids, image galleries, maps, or complex visual layouts where spatial position matters. For simple text-heavy pages, `snapshot` with search is faster and uses fewer tokens. ```js await screenshotWithAccessibilityLabels({ page }); @@ -904,7 +906,7 @@ console.log(JSON.stringify(info, null, 2)); await page.keyboard.press('Enter'); await page.waitForTimeout(2000); -const snap = await accessibilitySnapshot({ page, search: /dialog|error|message/ }); +const snap = await snapshot({ page, search: /dialog|error|message/ }); const logs = await getLatestLogs({ page, search: /error/i, count: 10 }); console.log('UI:', snap); console.log('Logs:', logs);