Skip to content

Commit d4e0a30

Browse files
fix: detect custom clickable elements in take_snapshot
take_snapshot only used the AX tree, which misses custom components (cursor:pointer divs, onclick handlers, etc.) that lack ARIA roles. These elements appeared as role="generic" and were invisible to the agent. Changes: - Merge findCursorInteractiveElements into snapshot() so take_snapshot catches cursor:pointer, onclick, and tabindex elements - Add DisclosureTriangle to INTERACTIVE_ROLES for <summary> elements - Use aria-label as text fallback in cursor detection for icon-only buttons - Fix dedup bug in enhancedSnapshot that was silently dropping all cursor-detected elements by checking against all AX node IDs instead of only already-included output IDs
1 parent ecd31ef commit d4e0a30

2 files changed

Lines changed: 31 additions & 6 deletions

File tree

packages/browseros-agent/apps/server/src/browser/browser.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,30 @@ export class Browser {
389389
const session = await this.resolveSession(page)
390390
const nodes = await this.fetchAXTree(session)
391391
if (nodes.length === 0) return ''
392-
return snapshot.buildInteractiveTree(nodes).join('\n')
392+
393+
const lines = snapshot.buildInteractiveTree(nodes)
394+
395+
try {
396+
const cursorElements =
397+
await snapshot.findCursorInteractiveElements(session)
398+
399+
if (cursorElements.length > 0) {
400+
const includedIds = new Set<number>()
401+
for (const line of lines) {
402+
const match = line.match(/^\[(\d+)\]/)
403+
if (match) includedIds.add(Number(match[1]))
404+
}
405+
406+
for (const el of cursorElements) {
407+
if (includedIds.has(el.backendNodeId)) continue
408+
lines.push(`[${el.backendNodeId}] clickable "${el.text}"`)
409+
}
410+
}
411+
} catch {
412+
// cursor detection is best-effort; AX tree results are still returned
413+
}
414+
415+
return lines.join('\n')
393416
}
394417

395418
async getPageLinks(
@@ -441,15 +464,15 @@ export class Browser {
441464
await snapshot.findCursorInteractiveElements(session)
442465

443466
if (cursorElements.length > 0) {
444-
const existingIds = new Set<number>()
445-
for (const node of nodes) {
446-
if (node.backendDOMNodeId !== undefined)
447-
existingIds.add(node.backendDOMNodeId)
467+
const includedIds = new Set<number>()
468+
for (const line of treeLines) {
469+
const match = line.match(/\[(\d+)\]/)
470+
if (match) includedIds.add(Number(match[1]))
448471
}
449472

450473
const extras: string[] = []
451474
for (const el of cursorElements) {
452-
if (existingIds.has(el.backendNodeId)) continue
475+
if (includedIds.has(el.backendNodeId)) continue
453476
extras.push(
454477
`[${el.backendNodeId}] clickable "${el.text}" (${el.reasons.join(', ')})`,
455478
)

packages/browseros-agent/apps/server/src/browser/snapshot.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const INTERACTIVE_ROLES = new Set([
4141
'option',
4242
'treeitem',
4343
'listbox',
44+
'DisclosureTriangle',
4445
])
4546

4647
const NAMED_CONTENT_ROLES = new Set([
@@ -196,6 +197,7 @@ const CURSOR_INTERACTIVE_JS = `(function() {
196197
if (parent && getComputedStyle(parent).cursor === 'pointer') continue;
197198
}
198199
var text = (el.textContent || '').trim().slice(0, 100);
200+
if (!text) text = (el.getAttribute('aria-label') || '').trim();
199201
if (!text) continue;
200202
var rect = el.getBoundingClientRect();
201203
if (rect.width === 0 || rect.height === 0) continue;

0 commit comments

Comments
 (0)