mcpField
T
Tactual

Tactual

本地服务
github.com/tactual-dev开发工具119 周前更新

关于 Tactual

Tactual 分析屏幕阅读器用户发现、抵达和操作页面控件所需的成本。开发者可在本地或 CI 中运行它,检查导航路径、比较结果并确定修复优先级。

主要功能

  1. 01评估屏幕阅读器导航成本
  2. 02模拟 NVDA、JAWS 和 VoiceOver 播报
  3. 03用键盘探索隐藏界面
  4. 04追踪通往交互目标的路径
  5. 05比较分析结果并建议修复

适用场景

  1. 01排查预发布网站的导航障碍
  2. 02检查菜单和对话框的键盘行为
  3. 03通过前后结果验证无障碍改动

MCP 相关

Tactual 内置 MCP 服务,向 MCP 客户端提供页面分析、路径追踪、验证、配置文件列表、结果比较、修复建议和多页面排查工具。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"tactual-dev-tactual": {
"command": "npx",
"args": [
"-y",
"tactual"
]
}
}
}
启动命令npx -y tactual

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmtactual0.2.1

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

Tactual

Tactual 徽标

屏幕阅读器导航成本分析器。测量屏幕阅读器用户在特定辅助技术配置(NVDA、JAWS、VoiceOver)下,发现、到达并操作页面上每个交互目标所需的击键次数。

功能

现有无障碍工具检查的是合规性——ARIA 是否正确?对比度是否足够?

Tactual 衡量的是导航成本——屏幕阅读器用户需要执行多少次操作才能到达结账按钮?如果他们操作过头了会怎样?他们能否发现该按钮的存在?菜单是否真的会在按 Enter 时打开,还是只能通过点击打开?焦点会落在第一个菜单项上,还是停留在触发器上?

工作原理:

  • 捕获 Playwright 无障碍快照,并模拟屏幕阅读器播报
  • 可选探索隐藏分支(菜单、对话框、选项卡、展开项),并使用真实键盘事件进行探测,包括 APG 风格的组件契约和表单错误流程
  • 构建带有入口点(地标、标题、线性 Tab)的导航图,并为每个目标评分
  • 可选使用 @guidepup/virtual-screen-reader 验证预测路径,以进行校准

Tactual 是一款用于分析自有网站和预发布环境的开发者工具。可在本地、CI 中或编辑器里的 MCP 服务中运行。它不是公共扫描服务。

适用场景

Tactual 可与 axe-core、Lighthouse 和 Pa11y 等合规性扫描器互补。这些工具仍是广泛检查 WCAG 和 ARIA 规则的首选。Tactual 关注的是下一个问题:页面标记通过验证后,AT 用户发现、到达并操作重要目标的成本有多高?

使用 Tactual 进行屏幕阅读器导航成本分诊、路径追踪、键盘和组件实测证据分析、前后差异比较、CI 优先级排序,以及 Agent 需要简洁结果(包含源代码选择器和修复建议)的 MCP 工作流。对于关键用户旅程、对时序敏感的流程、浏览器/AT 设置,以及有意区别于常见 APG 示例的实现模式,请使用真实屏幕阅读器并进行手动测试,以完成最终验证。

Agent 快速入门: 本 README 兼具产品概览和参考文档。Agent 应先查看 docs/AGENT-RECIPES.md,了解任务模式;再查看 docs/MCP-TOOLS.md,了解完整的 MCP 架构,然后再回到这里查看产品背景、安装说明和发布界面示例。

安装

需要 Node.js 20 或更高版本。

npm install tactual

Tactual 将 Playwright 安装为运行时依赖,因此无需单独将 Playwright 安装到 npx 缓存中,即可使用一次性 npx tactual@latest ... 命令。MCP SDK 也作为运行时依赖提供,因此从已安装的 tactual 软件包运行 tactual-mcp 无需单独安装 SDK。

快速开始

CLI
# Analyze a URL (default profile: generic-mobile-web-sr-v0)
npx tactual analyze-url https://example.com

# Analyze with a specific AT profile
npx tactual analyze-url https://example.com --profile voiceover-ios-v0

# Explore hidden UI (menus, tabs, dialogs, disclosures)
npx tactual analyze-url https://example.com --explore

# Use a scoring preset for your use case
npx tactual analyze-url https://shop.com --preset ecommerce-checkout
npx tactual analyze-url https://docs.example.com --preset docs-site

# Output as JSON, Markdown, or SARIF
npx tactual analyze-url https://example.com --format json --output report.json
npx tactual analyze-url https://example.com --format sarif --output report.sarif

# Compare two analysis runs
npx tactual diff-results baseline.json candidate.json
npx tactual diff-results baseline.json candidate.json --format json

# Print what NVDA would say as you Tab through the page
npx tactual transcript https://example.com
npx tactual transcript https://example.com --at voiceover

# List available AT profiles and scoring presets
npx tactual profiles
npx tactual presets

# Run benchmark suites
npx tactual benchmark
npx tactual benchmark --suite all

Benchmark fixtures ship with the npm package, so the benchmark command works from a fresh install and does not require cloning the repository fixtures into your current directory.

# Validate predicted paths against a virtual screen reader (reachability + step count)
# Requires optional deps, installed by default with tactual
npx tactual validate-url https://example.com --max-targets 10 --strategy semantic

# Initialize a tactual.json config file
npx tactual init

# Analyze a bot-protected site with stealth + real Chrome
npx tactual analyze-url https://www.npmjs.com/ --stealth --channel chrome

# Deep keyboard probing including revealed widgets and form-error flows
npx tactual analyze-url https://docs.example.com --probe --explore --probe-mode deep

# Focus probing on one opened branch, such as a dialog trigger
npx tactual analyze-url https://app.example.com/settings \
  --probe \
  --entry-selector "[aria-controls='profile-dialog']" \
  --probe-strategy modal-return-focus

# Analyze + inline virtual-SR validation in one command (predicted vs validated steps)
npx tactual analyze-url https://example.com --validate --validate-max-targets 10

控制台输出会为每条发现结果提供一行精简的路径,展示屏幕阅读器用户如何到达该目标:

  ██████░░ 70  link:reference structural
               D:47 R:71 O:100 Rec:100
               getByRole('link', { name: 'Reference' })
               ↪ Tab ×2 "v19.2" → K "Learn" → Tab "Reference"
               → Target is not efficiently reachable via heading or landmark navigation

其中,Tab = nextItem,H = nextHeading,; = nextLandmark,K = nextLink,B = nextButton,Enter = 激活。连续的相同操作步骤会合并(Tab ×2)。

从审计到修复

在本地应用或预览环境中开展无障碍工作时,Tactual 可为细小、易于审查的更改提供依据:

  • 每项发现都包含 selector、penalties、suggestedFixes 和证据摘要
  • 汇总输出中包含分组后的 issueGroups 和修复候选项
  • analyze_pages.site.repeatedNavigation 用于统计不同路由中重复导航的成本
  • diff-results / diff_results 用于验证更改前后的差异

先进行全面初筛,再深入检查一个路由,然后再修改代码:

# Site-level triage. Redirect JSON for tool consumption.
npx tactual analyze-pages \
  https://app.example.com/ \
  https://app.example.com/docs \
  https://app.example.com/settings \
  --profile nvda-desktop-v0 \
  --format json > tactual-site.json

# Deepen one route and produce a reviewable markdown report.
npx tactual analyze-url https://app.example.com/docs \
  --profile nvda-desktop-v0 \
  --explore --probe --probe-mode standard \
  --format markdown --output tactual-report.md

# When one branch is the target, open it first and spend probe budget there.
npx tactual analyze-url https://app.example.com/docs \
  --profile nvda-desktop-v0 \
  --probe \
  --entry-selector "[aria-controls='search-panel']" \
  --probe-selector "#search-panel" \
  --probe-strategy composite-widget \
  --format markdown --output tactual-search-panel.md

# Save a baseline before editing, then verify the patch.
npx tactual analyze-url https://app.example.com/docs --explore --probe --format json --output baseline.json
# Edit one root cause in the local repo, rebuild/restart the preview, then re-run:
npx tactual analyze-url https://app.example.com/docs --explore --probe --format json --output candidate.json
npx tactual diff-results baseline.json candidate.json

可将候选项部分作为排查重复根本原因的起点,例如共享组件、导航模式或小组件约定。确认源组件,并按照项目要求的 issue 或 PR 格式,写明路由、命令、发现证据、用户影响、代码更改和验证情况。评分变化是有用的辅助证据,但应首先说明无障碍行为发生了什么变化。

MCP 客户端可以使用相同的精简输出,让审查流程以路由、选择器、证据、源代码更改和前后验证为依据。

库 API
import { analyze, getProfile } from "tactual";
import { captureState } from "tactual/playwright";
import { chromium } from "playwright";

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com");

const state = await captureState(page);
await browser.close();

const profile = getProfile("generic-mobile-web-sr-v0");
const result = analyze([state], profile);

for (const finding of result.findings) {
  console.log(finding.targetId, finding.scores.overall, finding.severity);
}

屏幕阅读器播报模拟器 — 预测 NVDA、JAWS 或 VoiceOver 对每个目标会播报什么,并提供状态信息(已选中、已展开、已选择、模态、值、必填、无效等):

import {
  simulateScreenReader,
  buildAnnouncement,
  buildMultiATAnnouncement,
  buildTranscript,
} from "tactual/playwright";

const report = await simulateScreenReader(page, state.targets);

for (const a of report.formFields) {
  console.log(a.announcement);
  // → "Subscribe, check box, checked"
  // → "Country, combo box, collapsed"
  // → "Email, edit, invalid entry, required, you must use a work address"
}

// Compare across screen readers
const tx = state.targets[5];
buildAnnouncement(tx, "nvda"); // → "Country, combo box, collapsed"
buildAnnouncement(tx, "voiceover"); // → "Country, popup button"

// All three at once
buildMultiATAnnouncement(tx);
// → { nvda: "...", jaws: "...", voiceover: "..." }

// Linear navigation transcript — what an SR user hears Tabbing through
const transcript = buildTranscript(state.targets, "nvda");
// → [{ step: 1, kind: "landmark", announcement: "Main, main landmark" }, ...]

// Multi-target navigation modes (linear, by-heading, by-landmark, by-form-control)
import { buildNavigationTranscript } from "tactual/playwright";

// Heading-only navigation (NVDA: H key)
const headings = buildNavigationTranscript(state.targets, { mode: "by-heading" });

// Navigate from one element to another
const path = buildNavigationTranscript(state.targets, {
  from: "link:before-main",
  to: "heading:welcome",
  mode: "linear",
});

// Demoted landmarks (in DOM but stripped by HTML rules, e.g. <header> in <section>)
for (const d of report.demotedLandmarks) {
  console.warn(d.demotionReason);
}

验证与校准 API — 将模型输出与虚拟屏幕阅读器验证运行结果或人工观察数据集进行比较:

import { validateFindingsInJsdom } from "tactual/validation";
import { runCalibration, formatCalibrationReport } from "tactual/calibration";

// Given a JSDOM instance, PageState, AnalysisResult, and calibration dataset:
const validation = await validateFindingsInJsdom(dom, state, result.findings, {
  maxTargets: 10,
  strategy: "semantic",
});

const calibration = runCalibration(dataset, new Map([[state.url, result]]));
console.log(validation, formatCalibrationReport(calibration));

也可以通过 CLI 使用:

npx tactual transcript https://example.com --at voiceover
npx tactual calibration-report my-calibration.json --analysis example-nvda.json

模拟器提供的是启发式预测,并非真实的屏幕阅读器输出。模拟器本身速度很快(在捕获的目标上运行纯 JavaScript,一旦目标载入内存即可在一秒内完成),但完整的 analyze-url 运行包含启动浏览器、捕获页面和评分,在简单页面上需要数秒;使用 --probe 时耗时更久(约 30 秒以上),使用 --explore 时,在复杂的 SPA 上约需 1–5 分钟。默认情况下,分析在无头浏览器中运行,因此工作时不会弹出窗口。(对于可见网站或受机器人防护的网站,可使用 --no-headless 或 --channel chrome --stealth。)

数据质量。 根据 W3C ARIA-AT 项目中的词元级断言完成校准:在三种辅助技术(NVDA、JAWS、VoiceOver)中,77/77 项角色/名称/状态词元断言全部通过,准确率达 100%;覆盖 36 种单目标模式(按钮、切换按钮、所有菜单按钮变体、披露控件、手风琴、复选框/三态复选框、开关、滑块、对话框、警报、链接、标签页、组合框、单选按钮组、微调按钮、菜单栏),以及 4 种多目标地标场景。运行 npm run build 后执行 npm run calibrate,即可根据最新的上游断言进行验证。这是对模拟器的校准,并不能证明其在浏览模式、详细程度设置、时序或所有有效小组件变体中都能完全忠实于屏幕阅读器。校准范围之外的特定辅助技术覆盖规则,会在源代码中标注 HIGH/MEDIUM/LOW 置信度。

MCP 服务

Tactual 内置了供 AI Agent 使用的 MCP 服务:

# Start the MCP server (stdio transport — default)
npx tactual-mcp

# Start with HTTP transport (for hosted platforms, remote clients)
npx tactual-mcp --http              # listens on http://127.0.0.1:8787/mcp
npx tactual-mcp --http --port=3000  # custom port (or set PORT env var)
npx tactual-mcp --http --port 3000  # space-separated form is also supported
npx tactual-mcp --http --host=0.0.0.0  # bind to all interfaces (default: 127.0.0.1)

对于面向网络的 MCP 部署,请将 HTTP 传输置于经过身份验证的 TLS 代理之后,并将其限定为受信任的客户端。有关托管检查清单和威胁模型,请参阅 SECURITY.md。

可用的 MCP 工具:

工具描述
analyze_url分析页面的 SR 导航成本(默认输出 SARIF)。支持选择性探索、键盘/控件/表单探测、限定范围/目标导向探测、针对受机器人防护的网站进行隐身/通道探测,以及筛选。
trace_path提供到达目标的分步导航路径,并模拟 SR 播报。
validate_url使用 @guidepup/virtual-screen-reader 验证预测路径。返回每种策略(线性/语义)的可访问性和平均准确率,形成预测与验证的闭环。
calibration_report使用已保存的完整分析 JSON 运行观测校准数据集,并返回结构化评分信号,用于调优/审查工作流。
list_profiles列出可用的辅助技术配置。
diff_results比较两项分析结果:改进、退步和严重程度变化。
suggest_remediations按影响程度排序的修复建议。
save_auth进行身份验证并保存会话状态,以便分析受保护的内容。
analyze_pages对多页面网站进行分类分析,汇总统计数据,并整理跨页面重复出现的导航成本组。

完整参数参考:docs/MCP-TOOLS.md

按 AI 工具设置

首先在项目中安装所需的软件包:

npm install tactual

Claude Code — 添加到项目根目录中的 .mcp.json:

{
  "mcpServers": {
    "tactual": {
      "type": "stdio",
      "command": "npx",
      "args": ["tactual-mcp"]
    }
  }
}

GitHub Copilot — 添加到 .copilot/mcp.json 或 ~/.copilot/mcp-config.json:

{
  "mcpServers": {
    "tactual": {
      "type": "stdio",
      "command": "npx",
      "args": ["tactual-mcp"]
    }
  }
}

Cursor / Windsurf / Cline — 在编辑器的 MCP 配置中使用相同格式:

{
  "mcpServers": {
    "tactual": {
      "command": "npx",
      "args": ["tactual-mcp"]
    }
  }
}

直接安装(全局安装) — 如果你不想使用 npx:

npm install -g tactual
tactual-mcp  # starts the MCP server on stdio
GitHub Actions

使用 GitHub Actions Marketplace 中的复合操作:

jobs:
  a11y:
    runs-on: ubuntu-latest
    permissions:
      security-events: write # for SARIF upload
      pull-requests: write # for comment-on-pr
    steps:
      - name: Analyze accessibility
        uses: tactual-dev/[email protected]
        with:
          url: https://your-app.com
          profile: nvda-desktop-v0
          explore: "true"
          probe: "true"
          probe-mode: standard
          fail-below: "70"
          comment-on-pr: "true"

该操作会安装 Tactual 和 Chromium 浏览器二进制文件、运行分析、将 SARIF 上传到 GitHub Code Scanning,并在平均分低于阈值时使构建失败。将 comment-on-pr: "true" 设为启用后,会在拉取请求上发布摘要评论(重新运行时会更新)。该操作会输出 average-score 和 result-file,供后续步骤使用。操作版本与 Tactual 版本同步——更新 uses: 行即可获取补丁。

默认设置较为保守:除非启用,否则不会运行 probe,因为它会发送真实的键盘事件;强制颜色模式图标检查仅适用于声明了 visualModes 的配置文件,例如 nvda-desktop-v0 和 jaws-desktop-v0。

也可以直接使用 CLI,以获得更多控制:

- name: Install Tactual
  run: npm install tactual

- name: Install browsers
  run: npx playwright install chromium --with-deps

- name: Run accessibility analysis
  run: npx tactual analyze-url https://your-app.com --format sarif --output results.sarif --threshold 70

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: results.sarif
回归门禁(CI 在结果劣于基线时失败)

将 --baseline 与 --fail-on-regression 配合使用,可将 Tactual 设为严格的 CI 门禁:从已知良好的构建中保存基线,然后在变更导致问题数量比基线增加 N 个或更多时,让 PR 检查失败。也可以单独运行 diff-results 命令,生成便于阅读的前后对比报告。

# One-time: snapshot main as the baseline
- name: Snapshot baseline
  if: github.ref == 'refs/heads/main'
  run: |
    npx tactual analyze-url https://preview.your-app.com \
      --format json --output tactual-baseline.json

- name: Upload baseline
  if: github.ref == 'refs/heads/main'
  uses: actions/upload-artifact@v4
  with:
    name: tactual-baseline
    path: tactual-baseline.json

# On PRs: compare against the baseline, fail on regressions
- name: Fetch baseline
  uses: actions/download-artifact@v4
  with:
    name: tactual-baseline

- name: Analyze + gate on regressions
  run: |
    npx tactual analyze-url https://pr-preview-${{ github.event.number }}.your-app.com \
      --format sarif --output results.sarif \
      --baseline tactual-baseline.json \
      --fail-on-regression 3     # fail if 3+ findings regressed

也可以通过操作使用:

- uses: tactual-dev/[email protected]
  with:
    url: https://pr-preview.your-app.com
    baseline: tactual-baseline.json
    fail-on-regression: "3"

该操作会映射 analyze-url CLI 的分析输入,并通过 CI 到 CLI 的契约测试确保这些字段保持一致。一些工作流控制属于操作编排,而非直接的 CLI 标志:fail-below 对应 CLI 的 --threshold,comment-on-pr 控制 PR 评论步骤,SARIF 上传则由工作流处理。常用输入包括 profile、explore、explore-depth、explore-budget、explore-timeout、probe、probe-mode、probe-strategy、scope-selector、probe-selector、entry-selector、goal-target、goal-pattern、stealth、channel、wait-for-selector、exclude、exclude-selector、focus、min-severity、max-findings、baseline、fail-on-regression、fail-below、validate、storage-state、summary-only。如果希望使用与操作固定版本不同的 Tactual 版本,仍建议采用上面的直接 CLI 调用方式。

界面命名约定示例
CLIkebab-case 标志--probe-strategy modal-return-focus
MCP 和库选项camelCase 字段probeStrategy: "modal-return-focus"
GitHub Actionkebab-case 输入probe-strategy: modal-return-focus

配置

CLI 标志
Options:
  -p, --profile <id>              AT profile (default: generic-mobile-web-sr-v0)
  -f, --format <format>           json | markdown | console | sarif (default: console)
  -o, --output <path>             Write to file instead of stdout
  -d, --device <name>             Playwright device emulation
  -e, --explore                   Explore hidden branches
  --explore-depth <n>             Max exploration depth (default: 3)
  --explore-budget <n>            Max exploration actions (default: 50)
  --explore-timeout <ms>          Total exploration timeout; includes probe time when combined with --probe (default: 60000)
  --explore-max-targets <n>       Max accumulated targets before stopping (default: 2000)
  --allow-action <patterns...>    Allow exploring controls matching these patterns (overrides safety)
  --exclude <patterns...>         Exclude targets by name/role glob
  --exclude-selector <css...>     Exclude elements by CSS selector
  --scope-selector <css...>       Capture, score, and probe only these subtrees
  --focus <landmarks...>          Only analyze within these landmarks
  --suppress <codes...>           Suppress diagnostic codes
  --top <n>                       Show only worst N findings
  --min-severity <level>          Minimum severity to report
  --threshold <n>                 Exit non-zero if avg score < N
  --preset <name>                 Scoring preset (ecommerce-checkout, docs-site, dashboard, form-heavy)
  --config <path>                 Path to tactual.json
  --no-headless                   Headed browser (for bot-blocked sites)
  --channel <name>                Browser channel: chrome, chrome-beta, msedge (uses installed browser; bypasses most bot detection)
  --stealth                       Anti-detection defaults: realistic UA, override navigator.webdriver, spoof plugins/languages
  --user-agent <ua>               Override User-Agent string
  --timeout <ms>                  Page load timeout (default: 30000)
  --probe                         Opt-in runtime keyboard probes for interactive targets
                                    (focus, activation, Escape, Tab).
                                    Also probes menu, dialog, tab, disclosure, combobox/listbox,
                                    and form-error patterns.
                                    When combined with --explore, probes revealed-state targets too
                                    (menu items, dialog bodies, expanded widgets).
  --probe-budget <n>              Override generic-probe budget (default: per --probe-mode)
  --probe-mode <mode>             fast | standard (default) | deep.
                                    fast=5 generic/5 menu/3 modal/5 widget;
                                    standard=20/20/10/20; deep=50/40/20/40.
                                    Budget is shared across initial + all revealed states.
  --probe-selector <css...>       Probe only these subtrees without changing capture/scoring
  --entry-selector <css>          Activate this trigger before capture/probe
  --goal-target <target>          Exact-ish target id/name/role/kind/selector hint
  --goal-pattern <pattern>        Glob target id/name/role/kind/selector hint
  --probe-strategy <strategy>     all | overlay | composite-widget | form |
                                    navigation | modal-return-focus | menu-pattern
  --validate                      Run the virtual screen reader over the captured DOM and include
                                    a predicted-vs-validated step comparison in the output.
                                    Requires optional deps: jsdom + @guidepup/virtual-screen-reader.
                                    Installed by default unless optional deps were omitted.
  --validate-max-targets <n>      Max findings to validate (default: 10)
  --validate-strategy <mode>      Virtual-SR nav strategy: linear | semantic (default: semantic)
  --check-visibility              Force per-icon contrast check across the profile's visualModes
  --no-check-visibility           Disable per-icon contrast check even if profile declares modes
  --detect-routes                 Record SPA route changes during analysis
  --descend-frames                Include iframe accessibility targets; Chromium can recover many cross-origin OOPIFs via CDP
  --auto-scroll                   Scroll before capture to surface lazy/infinite-scroll content
  --dismiss-banners               Best-effort dismissal of safe cookie/consent banners
  --probe-hover                   Hover likely triggers to expose hover-only popup content
  --walk-tab-order                Record Tab traversal to detect focus-order/focus-trap issues
  --diff-viewports                Compare desktop and mobile captures for hidden content
  --wait-for-selector <css>       Wait for selector before capturing (for SPAs)
  --wait-time <ms>                Additional wait after page load
  --storage-state <path>          Playwright storageState JSON for authenticated pages
  --also-json <path>              Also write JSON to this path (single analysis run for CI)
  --summary-only                  Return only summary stats, no individual findings
  -q, --quiet                     Suppress info diagnostics
tactual.json

使用 tactual init 创建,或手动创建:

{
  "preset": "ecommerce-checkout",
  "profile": "voiceover-ios-v0",
  "exclude": ["easter*", "admin*", "debug*"],
  "excludeSelectors": ["#easter-egg", ".admin-only", ".third-party-widget"],
  "scopeSelectors": ["main"],
  "probeSelectors": [".checkout-dialog"],
  "probeStrategy": "modal-return-focus",
  "focus": ["main"],
  "suppress": ["possible-cookie-wall"],
  "threshold": 70,
  "priority": {
    "checkout*": "critical",
    "footer*": "low",
    "analytics*": "ignore"
  }
}

配置会从工作目录中自动检测(tactual.json 或 .tactualrc.json)。CLI 标志会与配置设置合并,并覆盖配置设置。

AT 配置

配置平台描述
generic-mobile-web-sr-v0移动端标准化的移动端 SR 基础操作(默认)
voiceover-ios-v0移动端iOS Safari 上的 VoiceOver——基于转子导航
talkback-android-v0移动端Android Chrome 上的 TalkBack——朗读控制
nvda-desktop-v0桌面端Windows 上的 NVDA——浏览模式快捷键
jaws-desktop-v0桌面端Windows 上的 JAWS——带自动表单模式的虚拟光标

配置定义了各项导航操作的成本、评分维度权重、costSensitivity(用于缩放可达性衰减曲线)以及依赖上下文的修正项。实现详情请参阅 src/profiles/。

移动端配置的限制。 voiceover-ios-v0 和 talkback-android-v0 配置准确模拟了操作成本和 SR 播报措辞,但 Tactual 的键盘探测(--probe)仅测试桌面端交互(Tab、Enter、Escape)。它们不会模拟触摸手势(单击、双击、向右滑动、三指滑动、转子旋转等)。对于移动端配置,评分维度反映的是配置模型预测的成本,而非实测行为。仍需在真实设备上测试,以验证移动端无障碍体验。

视觉模式。 nvda-desktop-v0 和 jaws-desktop-v0 配置声明了一个 visualModes 矩阵(浅色/深色 × 强制颜色开启/关闭),因此分析器会采集每种组合下各图标的对比度。移动端和通用配置不包含此项,因为 Windows 高对比度模式并非现实的移动端关注事项。请参阅下文的可见性检查。

可见性检查

当当前配置文件声明了 visualModes 矩阵时,Tactual 会在初始捕获后重新模拟每种 (colorScheme, forcedColors) 组合,并采样每个图标的计算样式。发现项构建器会将每个图标的计算 fill 与最近的非透明祖先元素的 background-color 进行比较,并在对比度低于 WCAG 1.4.11 非文本阈值(3:1)时发出扣分项。

四种扣分项措辞,三个评分等级:

扣分项触发条件对可操作性的影响
Icon invisible in <mode>对比度 < 1.5:1,且没有相邻文本标签可操作性最高为 60
Decorative icon invisible in <mode>对比度 < 1.5:1,控件有可见文本标签可操作性 −5
Low icon contrast in <mode>对比度为 1.5–3.0:1,且没有相邻文本标签可操作性 −5
Author-set SVG fill in <mode>在 Playwright 中对比度正常(≥3:1),但模式为 forced-colors: active,且填充色是作者设置的 CSS 字面值(非系统颜色,也非 currentColor)可操作性 −2

此检查会跳过已适用于 HCM 的图标:fill="currentColor"、fill: ButtonText(或任何系统颜色)、forced-color-adjust: none(作者选择退出),或计算后的 fill === color(通过 CSS 应用的 currentColor)。如果低对比度图标旁边有可见文本标签,则会完全忽略——标签已能识别控件,图标只是辅助提示。

为何存在替代风险等级。 不同用户的 HCM 主题具有不同的 Canvas/ButtonText/系统颜色值。作者设置的字面填充色(例如 svg { fill: #e4e6e6 })可能与 Chromium 的默认 HCM 调色板对比度良好,但与某个特定用户主题的对比度较差。对于 forced-color-adjust: preserve-parent-color(SVG 路径的默认值),Playwright、Chrome 和 Edge 对字面值本身的浏览器渲染是一致的——实际问题在于主题差异,而不是 OS 绘制时的隐式替换。Tactual 会标记这种模式,以便你使用有代表性的 HCM 主题在真实的 Edge 中进行验证,而不是因为 Playwright 的对比度测量具有误导性。

可通过 --no-check-visibility、在 tactual.json 中设置 checkVisibility: false,或在 MCP analyze_url 工具上设置 checkVisibility: false 来明确禁用。即使配置文件未声明模式,也可通过 --check-visibility 强制启用(未声明模式时不执行任何操作)。

此检查会使每个页面中每种已声明模式的耗时增加约 +50–200ms——重新模拟媒体的开销很小;每种模式不会新建浏览器上下文。

评分预设

预设为常见用例打包了焦点筛选器和优先级映射。它们的优先级低于配置文件和 CLI 标志(预设 → tactual.json → CLI 标志)。

预设用例焦点关键目标
ecommerce-checkout购物流程maincheckout、cart、payment、buy
docs-site文档main、navigationsearch、nav
dashboardWeb 应用main、navigationsave、submit、create、delete、search
form-heavy表单页面mainsubmit、save、next、continue、error
npx tactual analyze-url https://shop.com --preset ecommerce-checkout
npx tactual presets  # list all presets with details

默认情况下,预设会抑制 Cookie 横幅和分析目标。若要覆盖此设置,请使用 --exclude 或在 tactual.json 中设置 priority。预设不能组合使用——一次只能启用一个 --preset。

评分

每个目标都会获得一个五维评分向量:

维度衡量内容
可发现性用户能否知道该目标存在?
可达性到达该目标的导航成本是多少?
可操作性控件的行为是否可预测?
恢复能力操作过头后恢复有多困难?
互操作风险辅助技术/浏览器支持差异的可能性有多大?(惩罚项)

各配置的维度权重有所不同:

配置DROReccostSensitivity
generic-mobile-web-sr-v00.300.400.200.101.0
voiceover-ios-v00.300.350.200.151.1
talkback-android-v00.250.450.200.101.3
nvda-desktop-v00.350.250.300.100.7
jaws-desktop-v00.300.250.350.100.6

**综合评分:**加权几何平均值:overall = exp(sum(w_i * ln(score_i)) / sum(w_i)) - interopRisk。为避免出现 log(0),每个维度的分数最低按 1 计算。任一维度为 0,都会使该维度对几何平均值的贡献归零,从而显著拉低综合评分——无法操作一个无法到达的目标。

严重程度等级:

分数等级含义
90-100强风险较低
75-89可接受仍可改进
60-74中等应进行分级处理
40-59高可能存在明显阻碍
0-39严重可能导致无法完成操作

诊断

Tactual 会针对采集可靠性、页面结构、视觉访问、运行时证据、ARIA 有效性和重复性成本模式生成诊断信息。警告是供审核时参考的提示,并不代表自动判定为不符合要求。许多视觉/内容检查采用启发式方法,应结合具体情况确认后再提交缺陷报告。

代码级别含义
blocked-by-bot-protectionerror检测到机器人验证页面;捕获的内容并非目标页面。
empty-pageerror未找到任何目标。
okinfo捕获生成了目标集,且没有可靠性警告。
possibly-degraded-contentwarningHTTP 页面上的目标数量少得可疑。
sparse-contentwarning仅找到 1-4 个目标。
possible-login-wallwarning疑似内容需要身份验证,或发生了登录重定向。
possible-cookie-wallinfoCookie 同意提示可能遮挡内容。
redirect-detectedinfo/warning捕获跳转到了不同的 URL 或域名。
timeout-during-renderwarning请求的渲染等待未能在捕获前完成。
framework-detectedinfo捕获期间检测到了前端框架信号。
spa-route-changesinfo分析期间发生了 SPA 路由更改。
exploration-no-new-stateswarning已运行 --explore,但未发现其他状态。
frames-descendedinfo深入 iframe,捕获或跳过了子框架。
auto-scrolledinfo捕获前执行了自动滚动,并报告其发现的内容。
banners-dismissedinfo已尝试关闭 Cookie/同意提示横幅。
tab-order-walkedinfo/warningTab 顺序遍历记录了焦点停留位置;若存在正值 tabindex,则发出警告。
viewport-divergencewarning桌面端/移动端视口差异检查发现目标、地标或标题缺失。
no-headingswarning未找到标题元素。
heading-skipwarning标题层级跳过了某一级,例如 h1 -> h3。
empty-headingwarning标题存在,但没有文本。
numeric-headingwarning标题文本仅包含数字、标点或无实际意义的单字符内容。
h1-countinfo/warning页面没有有用的单个 H1,或有多个值得检查的 H1。
no-landmarkswarning未找到地标区域。
no-main-landmarkwarning缺少 <main> 地标。
no-banner-landmarkinfo缺少 <header> / banner 地标。
no-contentinfo-landmarkinfo缺少 <footer> / contentinfo 地标。
no-nav-landmarkinfo缺少 <nav> / navigation 地标。
landmark-demotedwarningHTML 地标存在,但因嵌套上下文而降级。
structural-summaryinfo一行结构概览。
no-skip-linkwarning目标数量达到 5 个或以上的页面没有跳转到内容的链接。
broken-skip-linkwarning跳转链接指向不存在的片段目标。
skip-link-not-firstwarning跳转链接存在,但在前两个 Tab 停留位置中无法访问。
visual-order-divergencewarning视觉顺序似乎与 DOM/屏幕阅读器导航顺序不一致。
shared-structural-issuewarning影响超过 50% 目标的扣分项被提升为页面级问题。
redundant-tab-stopswarning多个链接目标会产生指向相同目的地的重复 Tab 停留位置。
data-flow-dependenciesinfo探索到的状态显示,某些控件只有在执行先前操作后才会启用。
form-summaryinfo/warning汇总表单,并在表单似乎缺少提交控件时发出警告。
missing-autocompletewarning标准表单字段缺少有用的 autocomplete 标记,或禁用了该功能。
empty-interactivewarning交互目标没有可访问名称。
fake-interactive-elementswarning可点击的非语义元素无法通过键盘/屏幕阅读器访问。
cdp-click-listenerswarningCDP 在非交互元素上发现了类似点击的监听器。
ambiguous-link-nameswarning具有相同可访问名称的链接指向不同目的地。
media-without-controlswarning音频/视频缺少控件且未隐藏。
duplicate-idwarning重复的 id 值可能导致标签和 ARIA 引用失效。
nested-interactivewarning交互控件嵌套在其他交互控件中。
meta-refreshwarning页面通过 meta refresh 自动刷新或重定向。
missing-image-altwarning图片缺少 alt 属性。
suspicious-image-altwarning图片替代文本看起来像填充内容或类似文件名的占位文本。
missing-iframe-titlewarningiframe 缺少 title 或可访问标签。
missing-html-langwarning缺少 <html lang>,或其值不像 BCP 47 语言标签。
poor-document-titlewarning文档标题缺失、为空、过短或过于通用。
viewport-blocks-zoomwarning视口 meta 设置限制了用户缩放。
low-contrast-textwarning交互文本或标题未达到类似 WCAG 的文本对比度阈值。
color-only-conveyancewarning文本似乎仅依靠颜色传达含义。
color-blindness-contrast-failwarning在模拟色觉缺陷的情况下,文本对比度不足。
lang-switch-without-markerwarning文本语言似乎发生了变化,但没有 lang 标记。
invalid-aria-rolewarning存在非标准 ARIA 角色。
unknown-aria-attrwarning存在未知的 aria-* 属性。
invalid-aria-attr-valuewarningARIA 属性值不在允许的值范围内。
missing-required-aria-attrwarningARIA 角色缺少必需的状态或属性。
aria-naming-prohibitedwarning为禁止命名的角色设置了名称。
unsupported-aria-attr-for-rolewarning该 ARIA 属性不受元素角色支持。

探索

--explore 标志会启用有界分支探索:

  • 打开菜单、标签页、折叠区、手风琴面板和对话框
  • 从隐藏 UI 中捕获新的无障碍状态
  • 将发现的目标标记为 requiresBranchOpen
  • 遵循深度、操作次数、目标数量和新颖度预算
  • 安全操作策略会阻止破坏性操作

对于包含大量隐藏 UI 的页面(例如下拉菜单、标签式界面、模态对话框),探索十分有用。

探索候选项会在迭代前按稳定键(角色 + 名称)排序,因此相同的页面内容在不同运行中会产生相同的探索顺序。

探测

--probe 标志用于测量重要的交互模式在无障碍树中出现后是否正常工作。探测为可选操作,因为它会发送真实键盘事件并增加运行时间。从 0.4.0 版本开始,探测包括通用焦点/激活检查、菜单契约、模态对话框契约、触发器到对话框的流程、标签页、折叠区、组合框、列表框和必填字段错误流程。探测结果包含证据摘要,以便报告区分实测失败与模型评分或启发式评分。

面向目标的控件可让复杂 SPA 中的深度探测保持实用:

需求CLIMCP/操作字段效果
分析一个子树--scope-selector "#drawer"scopeSelector / scope-selector仅捕获、评分和探测所选子树。
探测一个子树--probe-selector "#drawer"probeSelector / probe-selector保留整页评分,但仅在所选子树中使用探测预算。
先打开一个分支--entry-selector "[aria-controls='menu']"entrySelector / entry-selector在捕获/探测前激活触发器,并优先处理新显示的目标。
指定已知目标--goal-target "checkout"goalTarget / goal-target将探测范围缩小到匹配的目标 ID、名称、角色、类型或选择器。
使用 glob 指定目标--goal-pattern "*dialog*"goalPattern / goal-pattern与目标指定方式相同,但使用 glob 匹配。
按意图使用预算--probe-strategy modal-return-focusprobeStrategy / probe-strategy运行与 all、overlay、composite-widget、form、navigation、modal-return-focus 或 menu-pattern 相关的探测类型。

例如,要评估模态对话框分支而不遍历无关菜单:

npx tactual analyze-url https://app.example.com/settings \
  --profile nvda-desktop-v0 \
  --probe \
  --entry-selector "[aria-controls='profile-dialog']" \
  --probe-strategy modal-return-focus \
  --format markdown
探索预算
预算CLI 标志默认值用途
深度--explore-depth3最大递归深度
操作--explore-budget50所有分支的总点击预算
目标--explore-max-targets2000累积目标数超过此值时停止
时间--explore-timeout60000 ms限制总探索时间,包括初始探测、分支捕获和已显示状态探测

设置建议:

页面类型建议设置原因
营销网站、文档页面、博客默认值页面内容较少,通常不会触及默认限制
带侧边栏/菜单的仪表板--explore-depth 3 --explore-budget 50(默认值)捕获一层菜单的展开状态
复杂应用(Figma、Notion 等)--explore-depth 4 --explore-budget 100 --explore-max-targets 5000菜单层级更深,状态更多
包含大量隐藏 UI 的页面(表情选择器、颜色网格)--explore-max-targets 10000 加上 --exclude "emoji-*"限制或过滤大量内容
快速检查未知页面--explore-depth 1 --explore-budget 10只快速展开明显的分支

如果探索在打开有用分支之前就超时,请逐步提高 --explore-timeout 和 --explore-budget,或者使用 --entry-selector、--probe-selector 和 --probe-strategy,将相同预算用于你关心的分支。如果输出中有看起来重复的目标,请降低 --explore-depth(深度递归可能通过不同路径重新发现相同元素)。

SPA 框架检测

Tactual 会在捕获无障碍树之前检测 SPA 内容是否已渲染。可检测的框架包括:React、Next.js、Vue、Nuxt、Angular、Svelte 和 SvelteKit。它也会检查通用 HTML5 内容信号(地标、标题、导航、链接)。对于未包含在自动检测范围内的 SPA,请使用 --wait-for-selector(CLI)或 waitForSelector(MCP/API)指定一个表示应用已完成 hydration 的 CSS 选择器。

完成初始框架检测后,Tactual 会使用基于收敛的轮询——重复获取无障碍树快照,直到目标数量稳定为止;无论使用何种框架,此方法均可奏效。

对于 SPA 内容较多的应用,以下可选捕获辅助功能很有用:

npx tactual analyze-url https://app.example.com \
  --wait-for-selector "main" \
  --detect-routes \
  --auto-scroll \
  --descend-frames \
  --diff-viewports
  • --detect-routes 会记录分析期间发生的 pushState、replaceState、popstate 和 hashchange 事件。
  • --auto-scroll 会在捕获前显示由 IntersectionObserver 驱动的延迟加载内容。
  • --descend-frames 会附加带有 frame URL 归属信息的 iframe 目标。同源 frame 使用 Playwright 的 frame 级无障碍快照;当常规快照路径不可访问时,Chromium 会回退到 CDP,以处理跨源 OOPIF。对于无法访问的 frame,Firefox/WebKit 会保留现有的跳过行为。
  • --diff-viewports 会捕获在桌面视口和移动视口之间消失的目标、地标或标题内容。
  • --dismiss-banners、--probe-hover 和 --walk-tab-order 会为常见 SPA 覆盖层和焦点顺序错误添加有针对性的运行时证据。
已知页面基准测试

针对复杂的公共 SPA/组件库页面进行发布证据验证时,请运行:

npm run benchmark:known-pages

该脚本会构建软件包,在启用 SPA 辅助工具栈的情况下运行 analyze-url,并将 run-results.json、summary.json 和 REPORT.md 写入 build/known-pages-* 目录。它有意不作为 CI 门禁:公共网站会发生变化、会阻止自动化访问,而且随时间推移提供的内容也会不同。使用报告发现偏差和类别层面的意外情况,然后使用本地测试固件或项目自有页面设置确定性的回归门禁。若要进行范围有限的冒烟测试或 APG/W3C 捕获质量探测,可直接调用脚本,例如 node scripts/known-pages-corpus.mjs --build --limit 1 或 node scripts/known-pages-corpus.mjs --build --include-capture-probes。

回归跟踪

比较两次分析运行结果以发现回归:

# Save a baseline
npx tactual analyze-url https://your-app.com --format json --output baseline.json

# After changes, run again and diff
npx tactual analyze-url https://your-app.com --format json --output candidate.json
npx tactual diff-results baseline.json candidate.json

差异报告会显示有所改进、退步或严重程度发生变化的目标,以及已消除和新增的惩罚项。在 CI 中,使用 comment-on-pr 操作的输入项,即可在每个拉取请求中自动发布结果。

互操作性风险

Tactual 包含一份静态快照,其中的 ARIA 角色/属性支持数据源自 a11ysupport.io 和 ARIA-AT 项目。对于已知存在跨辅助技术/浏览器支持差异的角色,会施加互操作性风险惩罚。

角色风险说明
button、link、heading0支持良好
dialog5焦点管理方式各不相同
combobox8互操作性问题最多的模式
tree10在 JAWS 之外支持较差
application15使用不当时风险很高

解读发现

Tactual 的发现有意涵盖多个证据领域:

  • 屏幕阅读器导航:地标、标题、标签、分支发现、顺序遍历成本和模拟播报。
  • 键盘操作性:焦点移动、激活、Escape 键恢复、Tab 键陷阱和运行时组件探测。
  • 结构语义:缺少名称、标题/地标结构、降级的地标、重复的共同成因。
  • 互操作性风险:已知存在跨辅助技术/浏览器支持差异的角色和状态。
  • 指针相关检查:可能影响屏幕阅读器导航模型之外用户的目标尺寸和图标可见性问题。

这意味着页面即使屏幕阅读器导航评分较高,仍可能收到跳过链接、目标尺寸或可见性警告。请将它们视为不同的修复类别,而非矛盾。

基于探测得出的 APG 发现属于一致性警告。许多组件都有有效的实现变体,尤其是组合框和类似展开/折叠的模式,因此在将警告视为必须替换的依据前,请先对照预期模式进行核实。仍应使用目标浏览器/辅助技术组合检查关键流程。

输出格式建议

格式典型大小最适合的场景
console~8KB在终端中进行人工审查
markdown~11KBPR 和问题评论
json~18KB程序化使用
sarif~4-40KBGitHub Code Scanning / CI

除 SARIF 外,所有报告器格式默认都会输出摘要:统计数据、分组问题、修复候选项、证据摘要和最严重的问题(最多 15 项)。SARIF 最多输出 25 个结果。输出被截断时,顶部会显示提示。库 API 会公开完整的 AnalysisResult;除非请求 includeStates 之类的特定字段,否则 CLI 和 MCP 报告器的输出会保持精简。

对于 MCP 用法,sarif 是默认且推荐的格式。使用 summaryOnly: true 可进行精简的健康检查,其中包含统计数据、严重性计数、诊断信息和排名前 3 的问题。

校准

Tactual 包含一个校准框架(src/calibration/,导出为 tactual/calibration),用于针对真实数据集调整评分参数。详情请参阅 docs/CALIBRATION.md。

校准观察数据还可以包含来自 OSS 审查工作的确定性播报反馈:已知测试输出时,记录 observedAnnouncement;如果具体措辞有差异,但角色/名称/状态令牌明确,则记录 observedAnnouncementTokens。Tactual 会将这些数据与其针对匹配目标建模的播报进行比较,并报告缺失或意外的令牌。使用 tactual calibration-report 或 MCP calibration_report,可针对已保存的 analyze-url --full-json 输出运行数据集并生成 scoringSignals。使用 tactual observe-announcement 可根据已保存的分析生成或追加仅包含播报的观察数据;也可以在此仓库中使用 npm run -- nvda:vm:observe -- ... 来整理受控 NVDA VM 捕获文件夹。仓库中版本化的数据集位于 calibration/corpus/;运行 npm run calibration:corpus 可审查覆盖率门槛,运行 npm run build 后再运行 npm run calibration:matrix,即可根据 MAE、偏差、方差和过时的序列计划偏移,对可达性调优工作进行排序。

发布就绪情况和已知边界记录在 docs/RELEASE_TEST_MATRIX.md、docs/LIMITATIONS.md 和 docs/NVDA_VM_OBSERVER.md 中。

开发

npm install                    # Install dependencies
npm run build                  # Build with tsup
npm run test                   # Run unit + integration tests
npm run test:shard -- --list   # List bounded Vitest release shards
npm run test:shard -- capture  # Run one bounded Vitest shard
npm run test:shards            # Run all bounded Vitest shards
npm run test:benchmark         # Run benchmark suites
npm run typecheck              # TypeScript type checking
npm run lint                   # ESLint
npm run test:release           # Full split release gate

安全

浏览器沙箱

Tactual 始终使用启用了默认 Chromium 沙箱的 Playwright。它绝不会禁用 Web 安全功能或修改浏览器的安全模型。所有页面交互都在标准 Chromium 进程沙箱内进行。

安全操作策略

启用探索功能(--explore)时,Tactual 会先将交互元素划分为三个等级,再执行操作:

等级操作示例
安全激活标签页、菜单项、展开控件、手风琴控件、同页锚点
谨慎谨慎激活外部链接、含义不明确的按钮
不安全跳过提交按钮(搜索表单之外)、删除、退出登录、购买、部署、取消订阅

这是一种基于关键词的启发式方法,无法识别语义欺骗(例如,实际会删除数据的“保存”按钮),也无法检查服务器端行为。在生产环境中使用时,始终应在可信或沙箱化的环境中运行探索功能。

URL 验证

所有 URL 在导航前都会经过验证。CLI 接受 http:、https: 和 file: 协议,以便使用本地测试文件;MCP 中接受 URL 的工具仅接受 http: 和 https:,以避免通过由 Agent 控制的浏览器导航暴露本地文件。javascript:、data:、blob: 和 vbscript: 均会被拒绝。包含嵌入式凭据的 URL(例如 https://user:pass@host/)也会被拒绝。不会过滤私有/内部 IP 范围——在可访问内部服务的环境中运行 Tactual,等同于允许任何其他由 Playwright 驱动的工具访问这些服务,因此请将 URL 输入视为可信输入。

许可证

Apache-2.0

致谢

模拟器中的角色/状态措辞根据 W3C ARIA-AT project 进行了校准,该项目采用 CC-BY 4.0 许可。Tactual 不会捆绑 ARIA-AT 数据;校准脚本(npm run calibrate)会在运行时从上游仓库获取断言。如果你发布 Tactual 校准结果,请注明 W3C ARIA-AT project 是真实基准断言的来源。

互操作性风险评分中引用的 ARIA 角色/属性支持数据源自 a11ysupport.io 和同一 ARIA-AT project。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。