UNPKG

browser-debugger-cli

Version:

DevTools telemetry in your terminal. For humans and agents. Direct WebSocket to Chrome's debugging port.

190 lines 7.71 kB
/** * Accessibility tree inspection commands for semantic element queries. * * Provides three core commands: * - tree: Dump full accessibility tree * - query: Search by role/name/description patterns * - describe: Get A11y properties for CSS selector * * Uses IPC/callCDP pattern for consistency with other DOM commands. */ import { DomElementResolver } from '../../commands/dom/DomElementResolver.js'; import { getDomContext } from '../../commands/dom/helpers.js'; import { runCommand, runJsonCommand } from '../../commands/shared/CommandRunner.js'; import { jsonOption } from '../../commands/shared/commonOptions.js'; import { collectA11yTree, queryA11yTree, parseQueryPattern, resolveA11yNode, } from '../../telemetry/a11y.js'; import { CommandError } from '../../ui/errors/index.js'; import { formatA11yTree, formatA11yQueryResult, formatA11yNodeWithContext, } from '../../ui/formatters/a11y.js'; import { elementNotFoundError, invalidQueryPatternError, noA11yNodesFoundError, elementNotAccessibleError, } from '../../ui/messages/errors.js'; import { EXIT_CODES } from '../../utils/exitCodes.js'; /** * Handle bdg dom a11y tree command * * Dumps the full accessibility tree for the current page via IPC. * Filters out ignored nodes for cleaner output. * * JSON output returns nodes as an array for natural jq filtering: * bdg dom a11y tree --json | jq '.data.nodes[] | select(.role == "checkbox")' * bdg dom a11y tree --json | jq '.data.nodes[0]' * * @param options - Command options */ async function handleA11yTree(options) { if (options.json) { await runJsonCommand(async () => { const tree = await collectA11yTree(); return { root: tree.root, nodes: Array.from(tree.nodes.values()), count: tree.count, }; }); } const tree = await collectA11yTree(); await runCommand(() => Promise.resolve({ success: true, data: tree }), options, formatA11yTree); } /** * Handle bdg dom a11y query <pattern> command * * Queries the accessibility tree using role/name/description patterns via IPC. * Pattern format: "role:button name:Submit" (space-separated key:value pairs) * * @param pattern - Query pattern string * @param options - Command options * * @example * ```bash * bdg dom a11y query "role:button name:Submit" * bdg dom a11y query "role:textbox" * bdg dom a11y query "name:Email" * ``` */ async function handleA11yQuery(pattern, options) { await runCommand(async () => { const queryPattern = parseQueryPattern(pattern); if (!queryPattern.role && !queryPattern.name && !queryPattern.description) { const err = invalidQueryPatternError(pattern); throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS); } const tree = await collectA11yTree(); const result = queryA11yTree(tree, queryPattern); if (result.count === 0) { const err = noA11yNodesFoundError(pattern); throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_NOT_FOUND); } return { success: true, data: result }; }, options, formatA11yQueryResult); } /** * Handle bdg dom a11y describe <selectorOrIndex> command * * Gets accessibility properties for a DOM element by CSS selector or numeric index. * Supports index-based access from query results (e.g., "bdg dom a11y describe 0"). * Useful for understanding how an element is exposed to assistive technologies. * Includes DOM context (tag, classes, text preview) when a11y data is sparse. * * @param selectorOrIndex - CSS selector (e.g., "button.submit") or numeric index from query results * @param options - Command options * * @example * ```bash * bdg dom a11y describe "button.submit" * bdg dom a11y describe "#email" * bdg dom a11y describe "form input[type=password]" * bdg dom a11y describe 0 # Uses cached query results * ``` */ async function handleA11yDescribe(selectorOrIndex, options) { const resolver = DomElementResolver.getInstance(); const isNumericIndex = resolver.isNumericIndex(selectorOrIndex); /** * Fetch a11y node data for a given selector or index. */ async function fetchA11yNodeData() { let node; let nodeId; if (isNumericIndex) { const index = parseInt(selectorOrIndex, 10); const targetNode = await resolver.getNodeIdForIndex(index); nodeId = targetNode.nodeId; node = await resolveA11yNode('', nodeId); } else { node = await resolveA11yNode(selectorOrIndex); } if (!node) { if (isNumericIndex) { const err = elementNotAccessibleError(parseInt(selectorOrIndex, 10)); throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE); } throw new CommandError(elementNotFoundError(selectorOrIndex), {}, EXIT_CODES.RESOURCE_NOT_FOUND); } let domContext = null; const domNodeId = node.backendDOMNodeId ?? nodeId; if (domNodeId) { domContext = await getDomContext(domNodeId); } return { node, domContext }; } if (options.json) { await runJsonCommand(fetchA11yNodeData); } await runCommand(async () => { const data = await fetchA11yNodeData(); return { success: true, data }; }, options, formatA11yNodeWithContext); } /** * Register accessibility commands under 'bdg dom a11y' * * @param domCmd - Parent DOM command */ export function registerA11yCommands(domCmd) { const a11y = domCmd .command('a11y') .description('Accessibility tree inspection and semantic queries') .argument('[search]', 'Quick search: index (describe), CSS selector (#id, .class), pattern with ":" (query), or name search') .enablePositionalOptions() .action(async (search, options) => { if (!search) { a11y.help(); return; } const isNumericIndex = /^\d+$/.test(search); const isCssSelector = /^[#.[]/u.test(search) || search.includes(' '); const isPatternQuery = search.includes(':') || search.includes('='); if (isNumericIndex || isCssSelector) { await handleA11yDescribe(search, options); } else if (isPatternQuery) { await handleA11yQuery(search, options); } else { await handleA11yQuery(`name:*${search}*`, options); } }); a11y .command('tree') .description('Dump full accessibility tree (filters ignored nodes)') .addOption(jsonOption()) .action(async (options) => { await handleA11yTree(options); }); a11y .command('query') .description('Query elements by accessibility properties (e.g., "role:button", "name:Submit")') .argument('<pattern>', 'Pattern with field prefix - role:button, name:Submit, name:*search* (supports wildcards)') .addOption(jsonOption()) .action(async (pattern, options) => { await handleA11yQuery(pattern, options); }); a11y .command('describe') .description('Get accessibility properties for CSS selector or index') .argument('<selectorOrIndex>', 'CSS selector (e.g., "button.submit") or numeric index from query results') .addOption(jsonOption()) .action(async (selectorOrIndex, options) => { await handleA11yDescribe(selectorOrIndex, options); }); } //# sourceMappingURL=a11y.js.map