首页
/ Langflow 前端可访问性批量扫描实战:基于 IBM ACE 的 Python 路由扫描器 a11y_scan.py

Langflow 前端可访问性批量扫描实战:基于 IBM ACE 的 Python 路由扫描器 a11y_scan.py

2026-09-04 19:09:43作者:吴年前Myrtle

本文围绕 Langflow 仓库中 .agents/skills/ibm-a11y-route-scan 技能文档展开,系统讲解如何使用仓库内置的 Python 脚本 a11y_scan.py 对 Langflow 前端路由进行批量可访问性(Accessibility,a11y)扫描:从扫描器调用与参数、路由清单(route manifest)选择、模态框状态(state)扫描,到 JSON/Markdown/HTML 三类报告的字段解读。读完本文,你可以独立对任意一组前端路由产出可访问性问题报告,并能结合源码理解扫描器如何注入 IBM ACE 引擎、等待网络静默、追踪 API 请求并输出结果。

技能定位:只报告、不修复

该技能文档 .agents/skills/ibm-a11y-route-scan/SKILL.md 明确界定了工具的边界:

  • 扫描范围:只扫描每条路由“默认加载完成”后的 DOM 状态;需要扫描模态框等交互状态时,必须通过显式的 state 文件描述操作,而不是让扫描器自动乱点页面。
  • 只报告(reports findings only):该工具不修改代码、不运行 Playwright/axe 测试套件,也不做正式的 Level 1 审计。需要写测试、做正式审计或 PR 级别修复时,应转向仓库中对应的其他技能文档(ibm-a11y-testing-guideibm-a11y-level1-auditibm-a11y-pr-remediation)。

这与 src/frontend/tests/a11y/README.md 中描述的双层 a11y 体系一致:Playwright 用例(src/frontend/tests/a11y/ 目录)是 CI 回归宿主,由 GitHub Actions 工作流 a11y-scan.yml 驱动;而 scripts/a11y/a11y_scan.py 是“ad-hoc 路由/报告工具”,用于自定义路由批次、模态框状态或本地问题分诊时快速产出 Markdown/HTML 报告。

扫描器与调用方式

扫描基于 a11y_scan.py 实现。该脚本通过 Playwright 打开 Chromium,把从 CDN 拉取的 IBM ACE(Accessiblity Checker Engine,默认地址为 https://unpkg.com/accessibility-checker-engine@latest/ace.js,见源码 a11y_scan.py 第 22 行DEFAULT_ACE_URL)注入页面,对当前 DOM 执行检查。技能文档给出的标准批量扫描命令如下:

uv run python scripts/a11y/a11y_scan.py \
  --url http://localhost:3000 \
  --routes-file scripts/a11y/a11y_routes.json \
  --route-group static \
  --out /tmp/langflow-a11y-report.json \
  --markdown /tmp/langflow-a11y-report.md \
  --html /tmp/langflow-a11y-report.html \
  --timeout-ms 45000

前提是本机已启动 Langflow 前端(默认 http://localhost:3000),并通过 uv 提供 Python 运行环境。

脚本参数完整说明

技能文档列出的参数与源码 parse_args()(a11y_scan.py 第 45-82 行) 完全对应,合并整理如下(含源码中额外可见的 --ace-url--browser-executable):

参数 说明 默认值
--url 应用基础 URL,通常为 http://localhost:3000,也可传完整页面 URL(必填)
--routes-file 路由清单 JSON 文件,建议使用 scripts/a11y/a11y_routes.json
--route-group 使用清单中的哪个分组 static
--routes 逗号分隔的路由路径列表
--route 单个路由路径,可重复使用以指定多个路由
--levels 逗号分隔的问题级别:violation,potentialviolation,recommendation,manual violation
--out JSON 报告输出路径 a11y-scan-report.json
--markdown 可选的 Markdown 报告路径
--html 可选的自包含 HTML 报告路径
--timeout-ms 每条路由的超时时间(毫秒) 30000
--quiet-ms 扫描前的网络静默窗口(毫秒) 1000
--states-file 显式模态框/状态操作 JSON 文件
--headed 有头模式,扫描时可见浏览器窗口 关闭
--ace-url 自定义 ACE 引擎下载地址 unpkg 上的最新版 ACE
--browser-executable 指定 Chrome/Chromium 可执行文件路径,也可用环境变量 PLAYWRIGHT_CHROMIUM_EXECUTABLE 自动探测(含 macOS 常见浏览器路径,见 find_chromium_executable,第 164-174 行

路由来源的优先级可以从 main() 入口(第 959-964 行) 看出:--routes/--route 显式参数 > --routes-file 指定的清单分组 > state 文件中出现过的路由 > 仅扫描 --url 对应的路径。也就是说,命令行显式路由始终覆盖清单。

路由选择:以路由清单为唯一事实来源

技能文档要求把 scripts/a11y/a11y_routes.json 作为路由选择的 source of truth。该清单按语义划分为四个分组:

  • static:默认批量扫描目标,即“常规 CI/本地批次”。当前清单包含 13 条静态路由,覆盖 /flows/components/mcp/assets/files/assets/knowledge-bases 以及 /settings/ 下的 8 个设置页(general、global-variables、model-providers、db-providers、mcp-servers、mcp-client、api-keys、shortcuts、messages 等)。每条条目除 id/path/surface 外,还带有 ready 就绪检查(如 mainpage_title testid、设置页的 settings_menu_header),供 CI 的 Playwright 用例在扫描前确认页面真正渲染完成。
  • dynamic:需要真实 ID 才能扫描的路由模板,包括 /flow/:id/(流程编辑器)、/playground/:id/(要求流程 access_type 为 PUBLIC 的共享 playground 页)、/assets/knowledge-bases/:sourceId/chunks(知识库源分块页)。文档明确要求:先从上载好的应用、API 响应或现有测试数据中取得真实 ID,替换占位符后再扫描。
  • gated:需要特定认证状态的路由,如 /login/signup/login/admin。清单注明这些页面在已登录时会重定向到 /flows,因此必须在“未登录/禁用自动登录”模式下扫描。
  • excluded:被默认批次排除的别名与重定向,例如 /(运行时重定向到 /flows)、/all(同一 HomePage 表面)、/assets(重定向到 /assets/files)、各 folder/:folderId 变体(同一列表页仅数据不同),以及兜底 * 路由(重定向链最终落在 /flows)。

清单顶部的 assumptions 字段还显式记录了扫描假设:BASENAME 为空(路由以 / 开头)、ENABLE_CUSTOM_PARAM 为 false(租户前缀变体未激活)、ENABLE_FILE_MANAGEMENTENABLE_KNOWLEDGE_BASES 均为 true(因此 /assets 与知识库路由生效)。这些假设决定了清单适用的部署前提。

常规清单驱动命令(技能文档的 Common manifest-backed command):

uv run python scripts/a11y/a11y_scan.py \
  --url http://localhost:3000 \
  --routes-file scripts/a11y/a11y_routes.json \
  --route-group static \
  --out /tmp/langflow-a11y-static.json \
  --markdown /tmp/langflow-a11y-static.md \
  --html /tmp/langflow-a11y-static.html

配套说明文档 scripts/a11y/a11y_scan_routes.md 进一步强调:不要在 Markdown 中手工维护路由列表,增删扫描目标应只改 JSON 清单,因为 Python 扫描器、Playwright a11y 用例与 HTML 报告都读取同一份清单。

常见扫描示例

技能文档给出了四类典型用法,全部可直接复制运行:

1. 扫描单条路由(最小化输出,只产 JSON):

uv run python scripts/a11y/a11y_scan.py \
  --url http://localhost:3000 \
  --route /flows \
  --out /tmp/langflow-a11y-flows.json

2. 扫描清单中的多条路由(标准 static 批次,三格式输出):

uv run python scripts/a11y/a11y_scan.py \
  --url http://localhost:3000 \
  --routes-file scripts/a11y/a11y_routes.json \
  --route-group static \
  --out /tmp/langflow-a11y-report.json \
  --markdown /tmp/langflow-a11y-report.md \
  --html /tmp/langflow-a11y-report.html

3. 扫描比 violation 更宽的级别(加入潜在违规与建议项):

uv run python scripts/a11y/a11y_scan.py \
  --url http://localhost:3000 \
  --routes-file scripts/a11y/a11y_routes.json \
  --route-group static \
  --levels violation,potentialviolation,recommendation \
  --out /tmp/langflow-a11y-expanded.json

4. 路由 + 模态框状态扫描(通过 --states-file 描述显式交互):

uv run python scripts/a11y/a11y_scan.py \
  --url http://localhost:3000 \
  --states-file /tmp/langflow-a11y-states.json \
  --out /tmp/langflow-a11y-modal-report.json \
  --markdown /tmp/langflow-a11y-modal-report.md \
  --html /tmp/langflow-a11y-modal-report.html \
  --timeout-ms 45000

State 文件格式与受支持的操作

--states-file 的结构是一个 JSON 数组,每个条目包含 routestates 列表;每个 state 有 name 以及打开(open)/关闭(close)两组动作序列。技能文档给出的示例(在“全局变量”设置页打开新建全局变量模态框):

[
  {
    "route": "/settings/global-variables",
    "states": [
      {
        "name": "new-global-variable-modal",
        "open": [
          { "click": "[data-testid='api-key-button-store']" },
          { "waitFor": "[role='dialog']" }
        ],
        "close": [
          { "press": "Escape" },
          { "waitForHidden": "[role='dialog']" }
        ]
      }
    ]
  }
]

受支持的 10 种 state 动作及其源码映射(run_action(),a11y_scan.py 第 286-316 行):

  • { "click": "<css selector>" }:点击第一个匹配元素;
  • { "clickText": "<visible text>" }:按可见文本(精确匹配)点击;
  • { "clickRole": { "role": "button", "name": "Create" } }:按 ARIA role + 可访问名点击;
  • { "fill": { "selector": "<css selector>", "value": "text" } }:向输入框填充文本;
  • { "press": "Escape" }:向页面按键;
  • { "press": { "selector": "<css selector>", "key": "Enter" } }:对指定元素按键;
  • { "waitFor": "<css selector>" }:等待元素可见;
  • { "waitForHidden": "<css selector>" }:等待元素隐藏;
  • { "waitForText": "<visible text>" }:等待可见文本出现;
  • { "wait": 500 }:固定等待毫秒数。

从源码实现看,每个动作执行后都会调用 wait_for_settled_network第 177-189 行):持续检查同源非静态资源请求,直到“无在途请求且连续 --quiet-ms 毫秒网络静默”才认为页面稳定,然后才执行 ACE 检查。这解释了为什么扫描动态数据丰富的页面时通常需要更大的 --timeout-ms

扫描器对状态的处理也值得注意:open 动作完成后会记录 phase: "open" 的结果并附加模态框诊断(当前可见的 [role="dialog"] 数量、焦点是否位于对话框内、打开前的活动元素);close 动作完成后记录 phase: "closed" 的结果,但关闭阶段不输出 issues(见 scan_route() 第 472-489 行closed 阶段固定 issues: []),主要用于验证对话框能正确关闭与诊断状态。

报告格式与字段解读

扫描器总是写出 JSON(--out),Markdown 与 HTML 为可选展示格式。技能文档给出的选择建议:用 JSON 获取精确数据、用 Markdown 贴 PR 评论或 issue、用 HTML 做可浏览报告。

汇总时需要覆盖的信息

  • 报告文件路径,以及生成的 Markdown/HTML 路径;
  • 问题总数(totalIssues);
  • 每条路由的问题数、API 请求数、请求失败数;
  • 高频 rule ID(top rules)。

报告字段

顶层字段:generatedAturlroutesreportLevelstotalIssuesresults(见 main() 报告组装,第 1002-1009 行)。每条 results[] 记录包含:

  • results[].route:路由路径;
  • results[].state:状态名,基础加载态为 base,模态框态为 state 文件中的 name
  • results[].phaseloaded(基础加载)、open(模态框打开)、closed(模态框关闭);
  • results[].apiRequests:扫描期间观察到的同源 API 请求(method/url/status);
  • results[].requestFailures:失败的请求及失败原因;
  • results[].diagnostics:模态框诊断(dialogCountfocusedWithinDialogactiveElementbeforeOpenActiveElement);
  • results[].issues[].ruleId:ACE 规则 ID,另有 messagesourcepath(DOM/ARIA 路径)、snippetvalue

从源码看,--levels 的四个取值在浏览器端被映射为 ACE 的判定值(evaluate_ace(),第 192-226 行):violation 对应 FAILpotentialviolation 对应 POTENTIALrecommendation 对应 RECOMMENDATIONmanual 对应 MANUAL。因此默认 --levels violation 只报告确定违规,加宽级别会把潜在违规与改进建议纳入统计。

Markdown 报告结构(write_markdown_report(),第 533-596 行)依次为:头部元信息(生成时间、基础 URL、级别、路由、总问题数)→ Route Summary 表(Route/State、Issues、API、Failures、Dialogs、Focus In Dialog)→ Top Rules 规则计数表 → Findings By Route 明细(每条 issue 带 message、DOM path、source、snippet)。HTML 报告(write_html_report(),第 617-956 行)是自包含单文件,按“路由 → 状态 → 问题”三级折叠面板组织,支持深浅色主题,且每个路由聚合了该路由所有状态的 issue/API/失败数。

与 Playwright CI 层的关系

清单并非只服务于 Python 脚本。static-routes.a11y.spec.ts 读取同一份 scripts/a11y/a11y_routes.json第 28-46 行),遍历 static 分组并对每条路由执行 ready 检查(支持 testIdrole+nameoneOf 组合与 containsText 断言),在禁用动画、等待 networkidle 后进行 a11y 扫描;路由 id 会作为 IBM 报告标签(例如 route-settings-api-keys)。build-a11y-html-report.mjs 也读取清单,把报告标签映射回路由路径与 surface 名称。

这带来一个实操结论:新增/调整扫描路由时只改 a11y_routes.json,并配好稳定的 ready 检查,CI 与本地 Python 扫描会同步生效;反之在脚本里用 --route 临时指定的路由不会进入 CI 回归。

使用规则与注意事项

技能文档的 Rules 部分规定了使用纪律,这些规则均能在源码中找到落点:

  • 只依据扫描器输出下结论:findings 全部来自 ACE 检查结果,不做人工臆测;
  • 不虚构路由名:路由来源以清单和前端路由定义(routes.tsx)为准,不要凭记忆拼路径;
  • 不自动乱点按钮找模态框:模态框状态必须用显式 state 动作描述(对应 run_actions 的显式序列执行);
  • 避免破坏性模态框操作:除非用户明确要求且数据安全,否则不要执行删除类操作;
  • API 请求为零时要提示:若某路由 apiRequests 为空,说明扫描时未观察到任何同源 API/config/health 请求,扫描质量可能受限。扫描器本身在控制台对此发出警告(main() 第 996-997 行warn: no same-origin API/config/health requests observed)。从 is_api_request()(第 103-107 行) 可见,判定 API 请求的标准是同源于路径包含 /api//health/config
  • 只做报告:本技能不编辑文件;如需应用修复,应交接给 ibm-a11y-pr-remediationibm-a11y-level1-audit

小结

ibm-a11y-route-scan 技能把 Langflow 前端可访问性分诊收敛为一条可复制的命令链:以 a11y_routes.json 清单的 static 分组为默认批次,用 a11y_scan.py 在 Playwright 驱动的 Chromium 中注入 IBM ACE 引擎,配合网络静默等待与 API 请求追踪,产出带 ruleId/DOM 路径/源码片段的 JSON、Markdown 或 HTML 报告;需要覆盖模态框时通过显式 state 文件描述 open/close 动作序列,动态路由则需先取到真实 ID 替换占位符。该工具定位为“报告与分诊”,与 CI 侧的 Playwright a11y 回归(src/frontend/tests/a11y/)共享同一份路由清单,两者互补而不重复。

登录后查看全文
热门项目推荐
相关项目推荐