release: playwriter@0.0.39, extension@0.0.66
- fix icon not updating on WS disconnect - increase aria-labels auto-hide timeout to 30s
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.vercel
|
||||||
dist
|
dist
|
||||||
esm
|
esm
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -108,18 +108,18 @@ await page.screenshot({ path: '/tmp/labeled-page.png' })
|
|||||||
// Use the snapshot to find elements, then interact using aria-ref selector
|
// Use the snapshot to find elements, then interact using aria-ref selector
|
||||||
await page.locator('aria-ref=e5').click()
|
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 })
|
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:**
|
**Features:**
|
||||||
- **Role filtering** - Only shows labels for interactive elements (buttons, links, inputs, etc.)
|
- **Role filtering** - Only shows labels for interactive elements (buttons, links, inputs, etc.)
|
||||||
- **Visibility detection** - Skips elements covered by modals or overlays
|
- **Visibility detection** - Skips elements covered by modals or overlays
|
||||||
- **Overlap prevention** - Skips labels that would overlap with already-placed ones
|
- **Overlap prevention** - Skips labels that would overlap with already-placed ones
|
||||||
- **Color-coded by type** - Warm color scheme helps distinguish element types
|
- **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:**
|
**Color legend:**
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Playwriter MCP",
|
"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.",
|
"description": "Automate your Browser using Cursor, Claude, VS Code. More capable and context efficient than Playwright MCP.",
|
||||||
"permissions": ["debugger", "tabGroups", "contextMenus", "tabs"],
|
"permissions": ["debugger", "tabGroups", "contextMenus", "tabs"],
|
||||||
"host_permissions": ["<all_urls>"],
|
"host_permissions": ["<all_urls>"],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-extension",
|
"name": "mcp-extension",
|
||||||
"version": "0.0.65",
|
"version": "0.0.66",
|
||||||
"description": "Playwright MCP Browser Extension",
|
"description": "Playwright MCP Browser Extension",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# 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
|
## 0.0.38
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
@@ -16,7 +23,7 @@
|
|||||||
- Only shows truly interactive roles (button, link, textbox, combobox, checkbox, etc.)
|
- Only shows truly interactive roles (button, link, textbox, combobox, checkbox, etc.)
|
||||||
- Skips elements covered by opaque overlays using `elementsFromPoint()`
|
- Skips elements covered by opaque overlays using `elementsFromPoint()`
|
||||||
- Greedy overlap prevention skips labels that would overlap with already-placed ones
|
- 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
|
- Available in MCP execute context
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
@@ -25,7 +32,7 @@
|
|||||||
const { snapshot, labelCount } = await showAriaRefLabels({ page });
|
const { snapshot, labelCount } = await showAriaRefLabels({ page });
|
||||||
await page.screenshot({ path: '/tmp/labeled-page.png' });
|
await page.screenshot({ path: '/tmp/labeled-page.png' });
|
||||||
await page.locator('aria-ref=e5').click();
|
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
|
## 0.0.35
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "playwriter",
|
"name": "playwriter",
|
||||||
"description": "",
|
"description": "",
|
||||||
"version": "0.0.38",
|
"version": "0.0.39",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export async function getAriaSnapshot({ page }: { page: Page }): Promise<AriaSna
|
|||||||
* Labels are yellow badges positioned above each element showing the aria ref (e.g., "e1", "e2").
|
* Labels are yellow badges positioned above each element showing the aria ref (e.g., "e1", "e2").
|
||||||
* Use with screenshots so agents can see which elements are interactive.
|
* Use with screenshots so agents can see which elements are interactive.
|
||||||
*
|
*
|
||||||
* Labels auto-hide after 5 seconds to prevent stale labels remaining on the page.
|
* Labels auto-hide after 30 seconds to prevent stale labels remaining on the page.
|
||||||
* Call this function again if the page HTML changes to get fresh labels.
|
* Call this function again if the page HTML changes to get fresh labels.
|
||||||
*
|
*
|
||||||
* By default, only shows labels for truly interactive roles (button, link, textbox, etc.)
|
* By default, only shows labels for truly interactive roles (button, link, textbox, etc.)
|
||||||
@@ -209,7 +209,7 @@ export async function getAriaSnapshot({ page }: { page: Page }): Promise<AriaSna
|
|||||||
* await page.screenshot({ path: '/tmp/screenshot.png' })
|
* await page.screenshot({ path: '/tmp/screenshot.png' })
|
||||||
* // Agent sees [e5] label on "Submit" button
|
* // Agent sees [e5] label on "Submit" button
|
||||||
* await page.locator('aria-ref=e5').click()
|
* await page.locator('aria-ref=e5').click()
|
||||||
* // Labels auto-hide after 5 seconds, or call hideAriaRefLabels() manually
|
* // Labels auto-hide after 30 seconds, or call hideAriaRefLabels() manually
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export async function showAriaRefLabels({ page, interactiveOnly = true }: {
|
export async function showAriaRefLabels({ page, interactiveOnly = true }: {
|
||||||
@@ -238,10 +238,9 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: {
|
|||||||
|
|
||||||
// Single evaluate call: create container, styles, and all labels
|
// Single evaluate call: create container, styles, and all labels
|
||||||
// ElementHandles get unwrapped to DOM elements in browser context
|
// ElementHandles get unwrapped to DOM elements in browser context
|
||||||
// Using 'any' types here since this code runs in browser context
|
|
||||||
const labelCount = await page.evaluate(
|
const labelCount = await page.evaluate(
|
||||||
// Using 'any' for browser types since this runs in browser context
|
// Using 'any' for browser types since this runs in browser context
|
||||||
({ refs, containerId, containerStyles, labelStyles, roleColors, defaultColors }: {
|
function ({ refs, containerId, containerStyles, labelStyles, roleColors, defaultColors }: {
|
||||||
refs: Array<{
|
refs: Array<{
|
||||||
ref: string
|
ref: string
|
||||||
role: string
|
role: string
|
||||||
@@ -252,7 +251,9 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: {
|
|||||||
labelStyles: string
|
labelStyles: string
|
||||||
roleColors: Record<string, [string, string, string]>
|
roleColors: Record<string, [string, string, string]>
|
||||||
defaultColors: [string, string, string]
|
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 doc = (globalThis as any).document
|
||||||
const win = globalThis as any
|
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
|
const LABEL_CHAR_WIDTH = 7 // approximate width per character
|
||||||
|
|
||||||
// Parse alpha from rgb/rgba color string (getComputedStyle always returns these formats)
|
// Parse alpha from rgb/rgba color string (getComputedStyle always returns these formats)
|
||||||
const getColorAlpha = (color: string): number => {
|
function getColorAlpha(color: string): number {
|
||||||
if (color === 'transparent') return 0
|
if (color === 'transparent') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
// Match rgba(r, g, b, a) or rgb(r, g, b)
|
// 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*)?\)/)
|
const match = color.match(/rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*(?:,\s*([\d.]+)\s*)?\)/)
|
||||||
if (match) {
|
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
|
// 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)
|
const style = win.getComputedStyle(el)
|
||||||
|
|
||||||
// Check element opacity
|
// Check element opacity
|
||||||
const opacity = parseFloat(style.opacity)
|
const opacity = parseFloat(style.opacity)
|
||||||
if (opacity < 0.1) return false
|
if (opacity < 0.1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Check background-color alpha
|
// Check background-color alpha
|
||||||
const bgAlpha = getColorAlpha(style.backgroundColor)
|
const bgAlpha = getColorAlpha(style.backgroundColor)
|
||||||
if (bgAlpha > 0.1) return true
|
if (bgAlpha > 0.1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Check if has background-image (usually opaque)
|
// Check if has background-image (usually opaque)
|
||||||
if (style.backgroundImage !== 'none') return true
|
if (style.backgroundImage !== 'none') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if element is visible (not covered by opaque overlay)
|
// 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 centerX = rect.left + rect.width / 2
|
||||||
const centerY = rect.top + rect.height / 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[]
|
const stack = doc.elementsFromPoint(centerX, centerY) as any[]
|
||||||
|
|
||||||
// Find our target element in the stack
|
// Find our target element in the stack
|
||||||
const targetIndex = stack.findIndex((el: any) =>
|
let targetIndex = -1
|
||||||
element.contains(el) || el.contains(element)
|
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
|
// 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
|
// Check if any opaque element is above our target
|
||||||
for (let i = 0; i < targetIndex; i++) {
|
for (let i = 0; i < targetIndex; i++) {
|
||||||
const el = stack[i]
|
const el = stack[i]
|
||||||
// Skip our own overlay container
|
// Skip our own overlay container
|
||||||
if (el.id === containerId) continue
|
if (el.id === containerId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Skip pointer-events: none elements (decorative overlays)
|
// 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 this element is opaque, our target is blocked
|
||||||
if (isOpaqueElement(el)) return false
|
if (isOpaqueElement(el)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if two rectangles overlap
|
// Check if two rectangles overlap
|
||||||
const rectsOverlap = (
|
function rectsOverlap(
|
||||||
a: { left: number; top: number; right: number; bottom: number },
|
a: { left: number; top: number; right: number; bottom: number },
|
||||||
b: { 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
|
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
|
// 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) {
|
if (overlaps) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -404,12 +431,12 @@ export async function showAriaRefLabels({ page, interactiveOnly = true }: {
|
|||||||
|
|
||||||
doc.documentElement.appendChild(container)
|
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
|
// 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()
|
doc.getElementById(containerId)?.remove()
|
||||||
win[timerKey] = null
|
win[timerKey] = null
|
||||||
}, 5000)
|
}, 30000)
|
||||||
|
|
||||||
return count
|
return count
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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' });
|
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
|
```js
|
||||||
const { snapshot, labelCount } = await showAriaRefLabels({ page });
|
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.
|
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
|
```js
|
||||||
await hideAriaRefLabels({ page });
|
await hideAriaRefLabels({ page });
|
||||||
|
|||||||
Reference in New Issue
Block a user