@a11y-oracle/core-engine

中文文档
1.3.2发布于 2026/03/12MITTypeScriptES Module近 30 天下载 3093

Framework-agnostic accessibility speech engine and unified A11yOrchestrator via Chrome DevTools Protocol

原始使用文档
文档目录16 节
  1. Installation
  2. Usage
  3. SpeechEngine (Speech Only)
  4. A11yOrchestrator (Unified State)
  5. Configuration Options
  6. API Reference
  7. SpeechEngine
  8. A11yOrchestrator
  9. Types
  10. Role Mappings
  11. Interactive Roles
  12. Landmark Roles
  13. Structure Roles
  14. Silent Roles
  15. State Mappings
  16. Exports

@a11y-oracle/core-engine

Framework-agnostic accessibility engine for A11y-Oracle. Provides two main APIs:

  1. SpeechEngine — Reads the browser's Accessibility Tree via CDP and generates standardized speech output.
  2. A11yOrchestrator — Unified orchestrator that combines speech, keyboard dispatch, and focus indicator analysis into a single pressKey() call.

This package is the foundation that the Playwright and Cypress plugins build on. You can also use it directly with any CDP-compatible client.

Installation

npm install @a11y-oracle/core-engine

Usage

SpeechEngine (Speech Only)

The speech engine operates through a CDPSessionLike interface, which any CDP client can satisfy:

import { SpeechEngine } from '@a11y-oracle/core-engine';

// Works with Playwright's CDPSession
const cdpSession = await page.context().newCDPSession(page);
const engine = new SpeechEngine(cdpSession);
await engine.enable();

// Get speech for the currently focused element
const result = await engine.getSpeech();
console.log(result?.speech); // "Products, button, collapsed"

// Get speech for every visible element
const all = await engine.getFullTreeSpeech();
const nav = all.find(r => r.speech.includes('navigation landmark'));

// Clean up
await engine.disable();

A11yOrchestrator (Unified State)

The orchestrator coordinates the speech engine, keyboard engine, and focus analyzer:

import { A11yOrchestrator } from '@a11y-oracle/core-engine';

const cdpSession = await page.context().newCDPSession(page);
const orchestrator = new A11yOrchestrator(cdpSession);
await orchestrator.enable();

// Press a key and get unified state
const state = await orchestrator.pressKey('Tab');
console.log(state.speech);                       // "Products, button, collapsed"
console.log(state.focusedElement?.tag);           // "BUTTON"
console.log(state.focusIndicator.meetsWCAG_AA);  // true

// Get state without pressing a key
const current = await orchestrator.getState();

// Tab order extraction
const report = await orchestrator.traverseTabOrder();
console.log(report.totalCount);  // 12

// Keyboard trap detection (WCAG 2.1.2)
const result = await orchestrator.traverseSubTree('#modal', 20);
console.log(result.isTrapped);  // false

await orchestrator.disable();

Configuration Options

const engine = new SpeechEngine(cdpSession, {
  // Include "landmark" suffix on landmark roles (default: true)
  // true:  "Main, navigation landmark"
  // false: "Main, navigation"
  includeLandmarks: true,

  // Include accessible descriptions in output (default: false)
  // true:  "Submit, button, Submits the form"
  // false: "Submit, button"
  includeDescription: false,
});

const orchestrator = new A11yOrchestrator(cdpSession, {
  // All SpeechEngine options, plus:

  // Milliseconds to wait after key press for focus/CSS to settle (default: 50)
  focusSettleMs: 50,
});

API Reference

SpeechEngine

The speech engine. All methods are async and operate through the CDP session.

constructor(cdp: CDPSessionLike, options?: SpeechEngineOptions)

Create a new engine instance.

  • cdp — Any object implementing the CDPSessionLike interface.
  • options.includeLandmarks — Append "landmark" to landmark roles. Default true.
  • options.includeDescription — Include aria-describedby text. Default false.

enable(): Promise<void>

Enable the CDP Accessibility domain. Must be called before any other method.

disable(): Promise<void>

Disable the CDP Accessibility domain. Call when done to free browser resources.

getSpeech(): Promise<SpeechResult | null>

Get the speech output for the currently focused element.

Returns null if no element has focus or the focused element is ignored (e.g., role="presentation").

const result = await engine.getSpeech();
if (result) {
  console.log(result.speech);  // "Products, button, collapsed"
  console.log(result.name);    // "Products"
  console.log(result.role);    // "button"
  console.log(result.states);  // ["collapsed"]
  console.log(result.rawNode); // Full CDP AXNode object
}

getFullTreeSpeech(): Promise<SpeechResult[]>

Get speech for all non-ignored, non-silent nodes in the accessibility tree. Useful for asserting on landmarks, headings, or structural elements that don't have focus.

const all = await engine.getFullTreeSpeech();
const headings = all.filter(r => r.role.includes('heading'));
const landmarks = all.filter(r => r.role.includes('landmark'));

computeSpeech(node: AXNode): SpeechResult | null

Compute speech for a single AXNode. Returns null for ignored or silent nodes.

findFocusedNode(nodes: AXNode[]): AXNode | null

Find the most specific focused node in the flat AXTree array. When multiple nodes report focused: true (e.g., RootWebArea and a menuitem), the deepest node is returned.

A11yOrchestrator

Unified orchestrator coordinating three sub-engines:

EngineResponsibility
SpeechEngineAXTree to speech string
KeyboardEngineCDP key dispatch + document.activeElement
FocusAnalyzerCSS focus indicator + tab order + trap detection

constructor(cdp: CDPSessionLike, options?: A11yOrchestratorOptions)

Create a new orchestrator.

  • cdp — Any CDP-compatible session.
  • options.includeLandmarks — Append "landmark" to landmark roles. Default true.
  • options.includeDescription — Include description text. Default false.
  • options.focusSettleMs — Delay after key press for focus/CSS to settle. Default 50.

enable(): Promise<void>

Enable the CDP Accessibility domain. Must be called before other methods.

disable(): Promise<void>

Disable the CDP Accessibility domain.

pressKey(key: string, modifiers?: ModifierKeys): Promise<A11yState>

Dispatch a key press and return the unified accessibility state.

  1. Sends keyDown + keyUp via CDP Input.dispatchKeyEvent
  2. Waits focusSettleMs for CSS transitions and focus events
  3. Collects speech, focused element, and focus indicator in parallel
const state = await orchestrator.pressKey('Tab');
// state.speech           → "Products, button, collapsed"
// state.focusedElement   → { tag: 'BUTTON', id: 'products-btn', ... }
// state.focusIndicator   → { isVisible: true, contrastRatio: 12.5, meetsWCAG_AA: true }

// With modifier keys
const prev = await orchestrator.pressKey('Tab', { shift: true });

getState(): Promise<A11yState>

Get the current unified state without pressing a key. Useful after programmatic focus changes.

await page.focus('#my-button');
const state = await orchestrator.getState();

traverseTabOrder(): Promise<TabOrderReport>

Extract all tabbable elements in DOM tab order.

const report = await orchestrator.traverseTabOrder();
console.log(report.totalCount);     // 12
console.log(report.entries[0].tag); // "A"
console.log(report.entries[0].id);  // "home-link"

traverseSubTree(selector: string, maxTabs?: number): Promise<TraversalResult>

Detect whether a container traps keyboard focus (WCAG 2.1.2).

Focuses the first tabbable element in the container, presses Tab up to maxTabs times (default 50), and checks whether focus ever escapes.

const result = await orchestrator.traverseSubTree('#modal-container', 20);
if (result.isTrapped) {
  console.log('Keyboard trap detected!');
  console.log(`Focus visited ${result.visitedElements.length} elements`);
} else {
  console.log(`Focus escaped to: ${result.escapeElement?.tag}`);
}

Types

CDPSessionLike

The abstraction boundary between the engine and test frameworks:

interface CDPSessionLike {
  send(method: 'Accessibility.enable'): Promise<void>;
  send(method: 'Accessibility.disable'): Promise<void>;
  send(
    method: 'Accessibility.getFullAXTree',
    params?: { depth?: number; frameId?: string }
  ): Promise<{ nodes: AXNode[] }>;
  send(method: string, params?: Record<string, unknown>): Promise<unknown>;
}

Both Playwright's CDPSession and chrome-remote-interface clients satisfy this interface without adapters.

SpeechResult

Returned by getSpeech() and getFullTreeSpeech():

interface SpeechResult {
  speech: string;              // "Products, button, collapsed"
  name: string;                // "Products"
  role: string;                // "button"
  states: string[];            // ["collapsed"]
  rawNode: Protocol.Accessibility.AXNode;
}

A11yState

Returned by pressKey() and getState():

interface A11yState {
  speech: string;                          // "Products, button, collapsed"
  speechResult: SpeechResult | null;       // Full speech result with raw AXNode
  focusedElement: A11yFocusedElement | null; // DOM info
  focusIndicator: A11yFocusIndicator;      // CSS analysis
}

A11yFocusedElement

interface A11yFocusedElement {
  tag: string;        // "BUTTON"
  id: string;         // "submit-btn"
  className: string;  // "btn primary"
  textContent: string; // "Submit"
  role: string;       // "button"
  ariaLabel: string;  // "Submit form"
  tabIndex: number;   // 0
  rect: { x: number; y: number; width: number; height: number };
}

A11yFocusIndicator

interface A11yFocusIndicator {
  isVisible: boolean;           // true if outline or box-shadow detected
  contrastRatio: number | null; // null if colors unparseable
  meetsWCAG_AA: boolean;        // true if visible and contrast >= 3.0
}

ModifierKeys

interface ModifierKeys {
  shift?: boolean;
  ctrl?: boolean;
  alt?: boolean;
  meta?: boolean;
}

TabOrderReport

interface TabOrderReport {
  entries: TabOrderEntry[];
  totalCount: number;
}

TabOrderEntry

interface TabOrderEntry {
  index: number;     // Position in tab order (0-based)
  tag: string;       // "BUTTON"
  id: string;        // "submit-btn"
  textContent: string;
  tabIndex: number;
  role: string;
  rect: { x: number; y: number; width: number; height: number };
}

TraversalResult

interface TraversalResult {
  isTrapped: boolean;                  // true if focus never escaped
  tabCount: number;                    // Total Tab presses attempted
  visitedElements: TabOrderEntry[];    // Elements that received focus
  escapeElement: TabOrderEntry | null; // First element outside container
}

Role Mappings

The ROLE_TO_SPEECH record maps 50+ CDP role values to speech strings. Roles are grouped into four categories:

Interactive Roles

CDP RoleSpeech
buttonbutton
linklink
checkboxcheckbox
radioradio button
textboxedit text
comboboxcombo box
sliderslider
switchswitch
tabtab
menuitemmenu item
menuitemcheckboxmenu item checkbox
menuitemradiomenu item radio
optionoption
searchboxsearch text
spinbuttonspin button

Landmark Roles

Landmarks automatically append "landmark" when includeLandmarks is true:

CDP RoleSpeech
navigationnavigation landmark
mainmain landmark
bannerbanner landmark
contentinfocontent info landmark
complementarycomplementary landmark
searchsearch landmark
regionregion landmark
formform landmark

Structure Roles

CDP RoleSpeech
headingheading
listlist
listitemlist item
imgimage
tabletable
rowrow
cellcell
dialogdialog
alertalert
menumenu
menubarmenu bar
toolbartoolbar
treetree
treeitemtree item
tablisttab list
tabpaneltab panel
progressbarprogress bar
tooltiptooltip

Silent Roles

These roles produce no speech output:

generic, none, presentation, StaticText, InlineTextBox, LineBreak, RootWebArea, WebArea, paragraph, DescriptionListDetail, DescriptionListTerm, DescriptionList

Unknown roles pass through as-is for forward compatibility with new ARIA roles.

State Mappings

The STATE_MAPPINGS array defines how boolean ARIA properties translate to spoken strings. States appear in the output in the order listed below:

CDP Propertytruefalse
expandedexpandedcollapsed
checkedcheckednot checked
selectedselected(silent)
pressedpressednot pressed
disableddimmed(silent)
requiredrequired(silent)
invalidinvalid(silent)
readonlyread only(silent)
multiselectablemulti selectable(silent)

Heading levels are a special case: level property produces level N (e.g., level 2).

When multiple states are present, they are joined in the fixed order above:

"Submit, button, expanded, required"
"Email, edit text, invalid, required"

Exports

// Main classes
export { SpeechEngine } from '@a11y-oracle/core-engine';
export { A11yOrchestrator } from '@a11y-oracle/core-engine';

// Core types
export type {
  CDPSessionLike,
  SpeechResult,
  SpeechEngineOptions,
  A11yState,
  A11yFocusedElement,
  A11yFocusIndicator,
  A11yOrchestratorOptions,
} from '@a11y-oracle/core-engine';

// Data (for advanced customization)
export { ROLE_TO_SPEECH, LANDMARK_ROLES } from '@a11y-oracle/core-engine';
export { STATE_MAPPINGS, extractStates } from '@a11y-oracle/core-engine';
export type { StateMapping, AXNodeProperty } from '@a11y-oracle/core-engine';

// Re-exports from sub-engines
export type { ModifierKeys, FocusedElementInfo, KeyDefinition } from '@a11y-oracle/core-engine';
export { KEY_DEFINITIONS } from '@a11y-oracle/core-engine';
export type { FocusIndicator, TabOrderEntry, TabOrderReport, TraversalResult } from '@a11y-oracle/core-engine';

版本与发布记录

@a11y-oracle/core-engine 共发布 10 个版本,首次发布于 2026/03/05;过去 12 个月发布了 10 个版本。

版本发布日期距今
1.3.2latest2026/03/127 个月前
1.3.12026/03/117 个月前
1.3.02026/03/068 个月前
1.2.02026/03/068 个月前
1.1.42026/03/068 个月前
1.1.32026/03/068 个月前

关于 @a11y-oracle/core-engine 的常见问题

@a11y-oracle/core-engine 是什么?

Framework-agnostic accessibility speech engine and unified A11yOrchestrator via Chrome DevTools Protocol

如何安装 @a11y-oracle/core-engine?

在项目目录运行 npm install @a11y-oracle/core-engine,或使用 pnpm add / yarn add / bun add。

@a11y-oracle/core-engine 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@a11y-oracle/core-engine 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@a11y-oracle/core-engine 可以商用吗?

@a11y-oracle/core-engine 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@a11y-oracle/core-engine 的下载量有多少?

根据 npm 官方下载统计,@a11y-oracle/core-engine 在 2026/10/05 – 2026/11/03 这 30 天内被下载 3,093 次,日均约 103 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@a11y-oracle/core-engine 还在维护吗?

最新版本 1.3.2 发布于 2026/03/12(7 个月前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/03/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。