filter aria ref labels to only interactive roles

Reduces label clutter by ~66-88% by only showing labels for:
button, link, textbox, combobox, searchbox, checkbox, radio,
slider, spinbutton, switch, menuitem, option, tab, treeitem

Added interactiveOnly option (default: true) to showAriaRefLabels()
This commit is contained in:
Tommy D. Rossi
2026-01-03 15:25:33 +01:00
parent 802e6768e2
commit 355053cbf9
6 changed files with 468 additions and 434 deletions
+38 -4
View File
@@ -17,6 +17,26 @@ export interface AriaSnapshotResult {
const LABELS_CONTAINER_ID = '__playwriter_labels__'
// Roles that represent truly interactive elements (can be clicked, typed into, etc.)
const INTERACTIVE_ROLES = new Set([
'button',
'link',
'textbox',
'combobox',
'searchbox',
'checkbox',
'radio',
'slider',
'spinbutton',
'switch',
'menuitem',
'menuitemcheckbox',
'menuitemradio',
'option',
'tab',
'treeitem',
])
// Use String.raw for CSS syntax highlighting in editors
const css = String.raw
@@ -144,10 +164,13 @@ export async function getAriaSnapshot({ page }: { page: Page }): Promise<AriaSna
}
/**
* Show Vimium-style labels on all interactive elements.
* Show Vimium-style labels on interactive elements.
* 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.
*
* By default, only shows labels for truly interactive roles (button, link, textbox, etc.)
* to reduce visual clutter. Set `interactiveOnly: false` to show all elements with refs.
*
* @example
* ```ts
* const { snapshot, labelCount } = await showAriaRefLabels({ page })
@@ -157,11 +180,22 @@ export async function getAriaSnapshot({ page }: { page: Page }): Promise<AriaSna
* await hideAriaRefLabels({ page })
* ```
*/
export async function showAriaRefLabels({ page }: { page: Page }): Promise<{
export async function showAriaRefLabels({ page, interactiveOnly = true }: {
page: Page
interactiveOnly?: boolean
}): Promise<{
snapshot: string
labelCount: number
}> {
const { snapshot, refHandles } = await getAriaSnapshot({ page })
const { snapshot, refHandles, refToElement } = await getAriaSnapshot({ page })
// Filter to only interactive elements if requested
const filteredRefs = interactiveOnly
? refHandles.filter(({ ref }) => {
const info = refToElement.get(ref)
return info && INTERACTIVE_ROLES.has(info.role)
})
: refHandles
// Single evaluate call: create container, styles, and all labels
// ElementHandles get unwrapped to DOM elements in browser context
@@ -216,7 +250,7 @@ export async function showAriaRefLabels({ page }: { page: Page }): Promise<{
return count
},
{
refs: refHandles.map(({ ref, handle }) => ({ ref, element: handle })),
refs: filteredRefs.map(({ ref, handle }) => ({ ref, element: handle })),
containerId: LABELS_CONTAINER_ID,
containerStyles: CONTAINER_STYLES,
labelStyles: LABEL_STYLES,
Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 236 KiB

@@ -31,7 +31,7 @@
- generic [ref=e55]:
- text: "Google offered in:"
- link [ref=e56] [cursor=pointer]:
- /url: https://www.google.com/setprefs?sig=0_AUHfGjXqCW2k22WacFgno6C3bdw%3D&hl=it&source=homepage&sa=X&ved=0ahUKEwjJxPW8xu-RAxWF6ckDHZ8wN1QQ2ZgBCBU
- /url: https://www.google.com/setprefs?sig=0_WWAwchirMrWB5pgMRu-Ccbzsvw0%3D&hl=it&source=homepage&sa=X&ved=0ahUKEwiz58mLyO-RAxVON94AHXxmKlkQ2ZgBCBU
- text: Italiano
- contentinfo [ref=e58]:
- generic [ref=e59]: Italy
Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 86 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 199 KiB