UNPKG

@mrtkrcm/mcp-puppeteer

Version:

Model Context Protocol server for browser automation using Puppeteer

142 lines (122 loc) 4.11 kB
import { Page, ElementHandle } from 'puppeteer'; /** * Represents a node in the accessibility tree */ export interface AccessibilityNode { role: string; name?: string; value?: string; description?: string; properties?: Record<string, string>; selected?: boolean; checked?: boolean; disabled?: boolean; required?: boolean; focused?: boolean; level?: number; ref: string; children?: AccessibilityNode[]; } /** * Generate a YAML-formatted accessibility snapshot from the current page * @param page Puppeteer page * @returns YAML string representation of the accessibility tree */ export async function generateAccessibilitySnapshot(page: Page): Promise<string> { const snapshot = await page.accessibility.snapshot(); if (!snapshot) { return "- document [ref=s1e1]: No accessibility data available"; } const processedSnapshot = processSnapshot(snapshot, 's1'); return formatAsYaml(processedSnapshot); } /** * Process a raw accessibility node into a structured node with references * @param node Raw accessibility node from Puppeteer * @param refPrefix Reference prefix for this node * @param index Index for generating unique references * @returns Processed accessibility node */ function processSnapshot(node: any, refPrefix: string, index: number = 0): AccessibilityNode { // Process node and its children const ref = `${refPrefix}e${index + 1}`; const result: AccessibilityNode = { role: node.role || 'unknown', ref }; // Add properties if they exist if (node.name) result.name = node.name; if (node.value) result.value = node.value; if (node.description) result.description = node.description; if (node.selected) result.selected = node.selected; if (node.checked) result.checked = node.checked; if (node.disabled) result.disabled = node.disabled; if (node.required) result.required = node.required; if (node.focused) result.focused = node.focused; // Handle heading level as a special case if (node.role === 'heading' && node.level) { result.level = node.level; } // Process children recursively with increased indentation if (node.children && node.children.length > 0) { result.children = node.children.map((child: any, idx: number) => processSnapshot(child, ref, idx) ); } return result; } /** * Format a processed accessibility node as YAML * @param node Processed accessibility node * @param indent Current indentation level * @returns YAML string representation */ function formatAsYaml(node: AccessibilityNode, indent: number = 0): string { const indentStr = ' '.repeat(indent); const lines: string[] = []; // Start with node role and name let line = `${indentStr}- ${node.role}`; // Add name if present (quoted) if (node.name) { line += ` "${escapeString(node.name)}"`; } // Add attributes in brackets const attributes: string[] = []; if (node.value) attributes.push(`value="${escapeString(node.value)}"`); if (node.selected) attributes.push('selected'); if (node.checked) attributes.push('checked'); if (node.disabled) attributes.push('disabled'); if (node.required) attributes.push('required'); if (node.focused) attributes.push('focused'); if (node.level !== undefined) attributes.push(`level=${node.level}`); // Add ref at the end attributes.push(`ref=${node.ref}`); if (attributes.length > 0) { line += ` [${attributes.join('] [')}]`; } // Add colon if there are children if (node.children && node.children.length > 0) { line += ':'; } lines.push(line); // Process children with increased indentation if (node.children && node.children.length > 0) { for (const child of node.children) { lines.push(formatAsYaml(child, indent + 1)); } } return lines.join('\n'); } /** * Escape special characters in strings for YAML * @param str String to escape * @returns Escaped string */ function escapeString(str: string): string { return str .replace(/\\/g, '\\\\') .replace(/"/g, '\\"') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t'); }