feat: add media elements (img, video, audio) to aria labels

- Added img, video, audio to INTERACTIVE_ROLES for visual tasks
- Light blue color scheme for media element labels
- Agents can now reference images by aria-ref for visual tasks
This commit is contained in:
Tommy D. Rossi
2026-01-05 10:06:51 +01:00
parent 6feb459c1e
commit da24f0e011
+77 -1
View File
@@ -1,4 +1,7 @@
import type { Page, Locator, ElementHandle } from 'playwright-core'
import fs from 'node:fs'
import path from 'node:path'
import os from 'node:os'
export interface AriaRef {
role: string
@@ -6,6 +9,14 @@ export interface AriaRef {
ref: string
}
export interface ScreenshotResult {
path: string
base64: string
mimeType: 'image/jpeg'
snapshot: string
labelCount: number
}
export interface AriaSnapshotResult {
snapshot: string
refToElement: Map<string, { role: string; name: string }>
@@ -17,7 +28,7 @@ export interface AriaSnapshotResult {
const LABELS_CONTAINER_ID = '__playwriter_labels__'
// Roles that represent truly interactive elements (can be clicked, typed into, etc.)
// Roles that represent interactive elements (clickable, typeable) and media elements
const INTERACTIVE_ROLES = new Set([
'button',
'link',
@@ -35,6 +46,10 @@ const INTERACTIVE_ROLES = new Set([
'option',
'tab',
'treeitem',
// Media elements - useful for visual tasks
'img',
'video',
'audio',
])
// Color categories for different role types - warm color scheme
@@ -63,6 +78,10 @@ const ROLE_COLORS: Record<string, [string, string, string]> = {
tab: ['#FFE082', '#FFD54F', '#FFC107'],
option: ['#FFE082', '#FFD54F', '#FFC107'],
treeitem: ['#FFE082', '#FFD54F', '#FFC107'],
// Media elements - light blue
img: ['#B3E5FC', '#81D4FA', '#4FC3F7'],
video: ['#B3E5FC', '#81D4FA', '#4FC3F7'],
audio: ['#B3E5FC', '#81D4FA', '#4FC3F7'],
}
// Default yellow for unknown roles
@@ -471,3 +490,60 @@ export async function hideAriaRefLabels({ page }: { page: Page }): Promise<void>
doc.getElementById(id)?.remove()
}, LABELS_CONTAINER_ID)
}
/**
* Take a screenshot with accessibility labels overlaid on interactive elements.
* Shows Vimium-style labels, captures the screenshot, then removes the labels.
* The screenshot is automatically included in the MCP response.
*
* @param collector - Array to collect screenshots (passed by MCP execute tool)
*
* @example
* ```ts
* await screenshotWithAccessibilityLabels({ page })
* // Screenshot is automatically included in the MCP response
* // Use aria-ref from the snapshot to interact with elements
* await page.locator('aria-ref=e5').click()
* ```
*/
export async function screenshotWithAccessibilityLabels({ page, interactiveOnly = true, collector }: {
page: Page
interactiveOnly?: boolean
collector: ScreenshotResult[]
}): Promise<void> {
// Show labels and get snapshot
const { snapshot, labelCount } = await showAriaRefLabels({ page, interactiveOnly })
// Generate unique filename with timestamp
const timestamp = Date.now()
const random = Math.random().toString(36).slice(2, 6)
const filename = `playwriter-screenshot-${timestamp}-${random}.jpg`
// Use ./tmp folder (gitignored) instead of system temp
const tmpDir = path.join(process.cwd(), 'tmp')
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, { recursive: true })
}
const screenshotPath = path.join(tmpDir, filename)
// Take screenshot
const buffer = await page.screenshot({ type: 'jpeg', quality: 80 })
// Save to file
fs.writeFileSync(screenshotPath, buffer)
// Convert to base64
const base64 = buffer.toString('base64')
// Hide labels
await hideAriaRefLabels({ page })
// Add to collector array
collector.push({
path: screenshotPath,
base64,
mimeType: 'image/jpeg',
snapshot,
labelCount,
})
}