From 1ad94f3930fc076237a012778afb32b01f7b9a68 Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Sat, 3 Jan 2026 17:38:28 +0100 Subject: [PATCH] release: playwriter@0.0.39, extension@0.0.66 - fix icon not updating on WS disconnect - increase aria-labels auto-hide timeout to 30s --- .gitignore | 1 + README.md | 6 +-- extension/manifest.json | 2 +- extension/package.json | 2 +- playwriter/CHANGELOG.md | 11 ++++- playwriter/package.json | 2 +- playwriter/src/aria-snapshot.ts | 77 ++++++++++++++++++++++----------- playwriter/src/prompt.md | 4 +- 8 files changed, 70 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 5ff1081..8d7617f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +.vercel dist esm .DS_Store diff --git a/README.md b/README.md index b9df46a..d2286ce 100644 --- a/README.md +++ b/README.md @@ -108,18 +108,18 @@ await page.screenshot({ path: '/tmp/labeled-page.png' }) // Use the snapshot to find elements, then interact using aria-ref selector await page.locator('aria-ref=e5').click() -// Labels auto-hide after 5 seconds, or remove manually: +// Labels auto-hide after 30 seconds, or remove manually: await hideAriaRefLabels({ page }) ``` -**Important:** Labels auto-hide after 5 seconds to prevent stale labels. If the page HTML changes (navigation, dynamic content), call `showAriaRefLabels()` again to get fresh labels matching the current DOM. +**Important:** Labels auto-hide after 30 seconds to prevent stale labels. If the page HTML changes (navigation, dynamic content), call `showAriaRefLabels()` again to get fresh labels matching the current DOM. **Features:** - **Role filtering** - Only shows labels for interactive elements (buttons, links, inputs, etc.) - **Visibility detection** - Skips elements covered by modals or overlays - **Overlap prevention** - Skips labels that would overlap with already-placed ones - **Color-coded by type** - Warm color scheme helps distinguish element types -- **Auto-hide** - Labels disappear after 5 seconds to prevent stale overlays +- **Auto-hide** - Labels disappear after 30 seconds to prevent stale overlays **Color legend:** diff --git a/extension/manifest.json b/extension/manifest.json index c616f2b..260fc3d 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Playwriter MCP", - "version": "0.0.65", + "version": "0.0.66", "description": "Automate your Browser using Cursor, Claude, VS Code. More capable and context efficient than Playwright MCP.", "permissions": ["debugger", "tabGroups", "contextMenus", "tabs"], "host_permissions": [""], diff --git a/extension/package.json b/extension/package.json index f85e8e8..7021625 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "mcp-extension", - "version": "0.0.65", + "version": "0.0.66", "description": "Playwright MCP Browser Extension", "private": true, "repository": { diff --git a/playwriter/CHANGELOG.md b/playwriter/CHANGELOG.md index ba5f561..1b7c5c1 100644 --- a/playwriter/CHANGELOG.md +++ b/playwriter/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.0.39 + +### Patch Changes + +- **Fix icon not updating on WS disconnect**: `maintainLoop` now ensures tabs transition to 'connecting' state when WebSocket is not connected, fixing edge cases where `handleClose` wasn't called +- **Increased aria-labels auto-hide timeout**: Labels now auto-hide after 30 seconds instead of 5 seconds + ## 0.0.38 ### Patch Changes @@ -16,7 +23,7 @@ - Only shows truly interactive roles (button, link, textbox, combobox, checkbox, etc.) - Skips elements covered by opaque overlays using `elementsFromPoint()` - Greedy overlap prevention skips labels that would overlap with already-placed ones - - Auto-hides after 5 seconds to prevent stale labels (timer cancelled if called again) + - Auto-hides after 30 seconds to prevent stale labels (timer cancelled if called again) - Available in MCP execute context ### Usage @@ -25,7 +32,7 @@ const { snapshot, labelCount } = await showAriaRefLabels({ page }); await page.screenshot({ path: '/tmp/labeled-page.png' }); await page.locator('aria-ref=e5').click(); -// Labels auto-hide after 5 seconds, or call hideAriaRefLabels({ page }) manually +// Labels auto-hide after 30 seconds, or call hideAriaRefLabels({ page }) manually ``` ## 0.0.35 diff --git a/playwriter/package.json b/playwriter/package.json index 98f6bd9..0946041 100644 --- a/playwriter/package.json +++ b/playwriter/package.json @@ -1,7 +1,7 @@ { "name": "playwriter", "description": "", - "version": "0.0.38", + "version": "0.0.39", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/playwriter/src/aria-snapshot.ts b/playwriter/src/aria-snapshot.ts index f1f042e..37ee36c 100644 --- a/playwriter/src/aria-snapshot.ts +++ b/playwriter/src/aria-snapshot.ts @@ -197,7 +197,7 @@ export async function getAriaSnapshot({ page }: { page: Page }): Promise defaultColors: [string, string, string] - }) => { + }) { + // Polyfill esbuild's __name helper which gets injected by vite-node but doesn't exist in browser + ;(globalThis as any).__name ||= (fn: any) => fn const doc = (globalThis as any).document const win = globalThis as any @@ -285,8 +286,10 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: { const LABEL_CHAR_WIDTH = 7 // approximate width per character // Parse alpha from rgb/rgba color string (getComputedStyle always returns these formats) - const getColorAlpha = (color: string): number => { - if (color === 'transparent') return 0 + function getColorAlpha(color: string): number { + if (color === 'transparent') { + return 0 + } // Match rgba(r, g, b, a) or rgb(r, g, b) const match = color.match(/rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*(?:,\s*([\d.]+)\s*)?\)/) if (match) { @@ -296,25 +299,31 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: { } // Check if an element has an opaque background that would block elements behind it - const isOpaqueElement = (el: any): boolean => { + function isOpaqueElement(el: any): boolean { const style = win.getComputedStyle(el) // Check element opacity const opacity = parseFloat(style.opacity) - if (opacity < 0.1) return false + if (opacity < 0.1) { + return false + } // Check background-color alpha const bgAlpha = getColorAlpha(style.backgroundColor) - if (bgAlpha > 0.1) return true + if (bgAlpha > 0.1) { + return true + } // Check if has background-image (usually opaque) - if (style.backgroundImage !== 'none') return true + if (style.backgroundImage !== 'none') { + return true + } return false } // Check if element is visible (not covered by opaque overlay) - const isElementVisible = (element: any, rect: any): boolean => { + function isElementVisible(element: any, rect: any): boolean { const centerX = rect.left + rect.width / 2 const centerY = rect.top + rect.height / 2 @@ -322,32 +331,44 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: { const stack = doc.elementsFromPoint(centerX, centerY) as any[] // Find our target element in the stack - const targetIndex = stack.findIndex((el: any) => - element.contains(el) || el.contains(element) - ) + let targetIndex = -1 + for (let i = 0; i < stack.length; i++) { + if (element.contains(stack[i]) || stack[i].contains(element)) { + targetIndex = i + break + } + } // Element not in stack at all - not visible - if (targetIndex === -1) return false + if (targetIndex === -1) { + return false + } // Check if any opaque element is above our target for (let i = 0; i < targetIndex; i++) { const el = stack[i] // Skip our own overlay container - if (el.id === containerId) continue + if (el.id === containerId) { + continue + } // Skip pointer-events: none elements (decorative overlays) - if (win.getComputedStyle(el).pointerEvents === 'none') continue + if (win.getComputedStyle(el).pointerEvents === 'none') { + continue + } // If this element is opaque, our target is blocked - if (isOpaqueElement(el)) return false + if (isOpaqueElement(el)) { + return false + } } return true } // Check if two rectangles overlap - const rectsOverlap = ( + function rectsOverlap( a: { left: number; top: number; right: number; bottom: number }, b: { left: number; top: number; right: number; bottom: number } - ) => { + ) { return a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top } @@ -378,7 +399,13 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: { } // Skip if this label would overlap with any already-placed label - const overlaps = placedLabels.some((placed) => rectsOverlap(labelRect, placed)) + let overlaps = false + for (const placed of placedLabels) { + if (rectsOverlap(labelRect, placed)) { + overlaps = true + break + } + } if (overlaps) { continue } @@ -404,12 +431,12 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: { doc.documentElement.appendChild(container) - // Auto-hide labels after 5 seconds to prevent stale labels + // Auto-hide labels after 30 seconds to prevent stale labels // Store timer ID so it can be cancelled if showAriaRefLabels is called again - win[timerKey] = win.setTimeout(() => { + win[timerKey] = win.setTimeout(function() { doc.getElementById(containerId)?.remove() win[timerKey] = null - }, 5000) + }, 30000) return count }, diff --git a/playwriter/src/prompt.md b/playwriter/src/prompt.md index 109d676..f0feb33 100644 --- a/playwriter/src/prompt.md +++ b/playwriter/src/prompt.md @@ -206,7 +206,7 @@ const matches = await editor.grep({ regex: /console\.log/ }); await editor.edit({ url: matches[0].url, oldString: 'DEBUG = false', newString: 'DEBUG = true' }); ``` -**showAriaRefLabels** - overlay Vimium-style visual labels on interactive elements. Useful for taking screenshots where you can see element references. Labels auto-hide after 5 seconds. Call again if page HTML changes to get fresh labels. +**showAriaRefLabels** - overlay Vimium-style visual labels on interactive elements. Useful for taking screenshots where you can see element references. Labels auto-hide after 30 seconds. Call again if page HTML changes or scrolls to get fresh labels. Use a timeout of 10 seconds at least. ```js const { snapshot, labelCount } = await showAriaRefLabels({ page }); @@ -218,7 +218,7 @@ await page.locator('aria-ref=e5').click(); Labels are color-coded: yellow=links, orange=buttons, coral=inputs, pink=checkboxes, peach=sliders, salmon=menus, amber=tabs. -**hideAriaRefLabels** - manually remove labels before the 5-second auto-hide: +**hideAriaRefLabels** - manually remove labels before the 30-second auto-hide: ```js await hideAriaRefLabels({ page });