Langflow 前端 a11y 可访问性扫描实践:双层扫描架构、路由清单与 CI 基线断言
【免费下载链接】langflowLangflow is a powerful tool for building and deploying AI-powered agents and workflows.项目地址: https://gitcode.com/GitHub_Trending/la/langflow
Langflow 的前端可访问性(accessibility,简称 a11y)体系由「Playwright 回归宿主 + 独立路由扫描脚本」两层构成。本文基于 a11y 测试目录的 README,完整覆盖静态路由覆盖、状态化覆盖、本地命令与基线断言的用法,并结合 CI 工作流 a11y-scan.yml、路由清单 a11y_routes.json 和扫描器 a11y_scan.py 的源码实现,说明每个环节背后「为什么这样做」以及如何在本地复现。读完本文,你可以独立在本地运行完整 a11y 扫描、生成按路由/规则分组的 HTML 报告,并理解 CI 是如何自动发现并断言扫描基线的。
两层扫描架构概览
Langflow 对 a11y 检查明确分为两个层次:
- Playwright 回归宿主:src/frontend/tests/a11y/ 目录下的 spec 文件是「回归宿主(regression hosts)」。CI 工作流 a11y-scan.yml 会运行所有调用了
page.runA11yScan(...)的 spec——不是硬编码文件列表,而是通过grep -rl "runA11yScan(" tests --include="*.spec.ts"动态发现,因此新增的扫描宿主无需修改工作流即可被纳入。 - 临时路由/报告工具:scripts/a11y/a11y_scan.py 是一个独立的路由感知扫描器,当你需要针对自定义路由批次、模态框状态(modal state)做 Markdown/HTML 报告或本地排查(local triage)时使用它。
这种分工对应了两种截然不同的诉求:CI 层要求稳定、便宜、可断言,所以只扫「便宜的、可预测的」静态路由;临时工具则要求灵活,支持任意路由组合和 UI 状态操作序列。
静态路由覆盖(Static Route Coverage)
静态路由的「唯一事实来源」是 scripts/a11y/a11y_routes.json。Playwright spec static-routes.a11y.spec.ts 在启动时会读取该清单(依次尝试../../scripts/a11y/a11y_routes.json和scripts/a11y/a11y_routes.json两个候选路径),并为static组中的每一个条目生成一条scans ${route.path}测试,调用page.runA11yScan(\route-${route.id}`)`。
清单文件将路由分为四组,这一划分在 a11y_scan_routes.md 中有明确说明:
| 分组 | 含义 | 示例 |
|---|---|---|
static | 常规扫描与 CI 默认覆盖的已认证路由页面 | /flows、/components、/settings/api-keys等 13 条 |
dynamic | 需要真实 ID 数据才能扫描的路由,不属于默认批次 | /flow/:id/、/playground/:id/ |
gated | 需要特定认证/角色/环境状态的页面 | /login、/signup、/login/admin |
excluded | 重定向、别名或渲染相同组件的路由,附排除理由 | /(重定向到/flows)、/all、*/(兜底重定向链) |
每条静态路由条目包含以下字段(可对照 a11y_routes.json 原文):
id:稳定标识符,会转化为 IBM 报告标签,例如route-settings-api-keys;path:路由路径(清单声明BASENAME为空,路径从/开始);surface:人类可读的页面描述,如 "Files page";ready:就绪检查数组,用于确认页面真正渲染完成而非重定向;requiresMainContent(可选):为true时(如/assets/files、/assets/knowledge-bases),spec 会先执行awaitBootstrapTest(page, { skipModal: true })等待主内容引导完成,再跳转路由。
就绪检查(ready check)语法
README 强调:新增路由时必须带上稳定的ready检查,这样如果路由发生重定向或停止渲染,CI 会直接失败。从 static-routes.a11y.spec.ts 的locatorForReadyCheck/expectReadyCheck实现看,ready数组支持以下检查项:
{ "testId": "settings_menu_header" } // getByTestId 可见 { "testId": "mainpage_title", "containsText": "Files" } // 可见且包含文本 { "role": "heading", "name": "Langflow MCP Client" } // 按 role+name 匹配 { "oneOf": [ { "role": "button", "name": "add knowledge" }, { "testId": "search-kb-input" } ] } // 任一候选可见即可name匹配是大小写不敏感的(实现为new RegExp(name, "i")),oneOf用 Playwright 的or组合定位器后断言first()可见。清单中还附带了assumptions数组,记录当前功能开关假设(如ENABLE_FILE_MANAGEMENT为 true 使/assets路由生效、ENABLE_KNOWLEDGE_BASES为 true 使知识库路由生效),提示维护者开关变更时需要同步更新清单。
静态路由的扫描流程细节
每条路由测试在waitForRouteToSettle中执行(static-routes.a11y.spec.ts):
- 若路由标记
requiresMainContent,先执行引导等待; page.goto(route.path)后注入一段 CSS,将所有animation/transition-duration强制置 0——消除动画导致的截图/DOM 抖动;- 断言最终 URL 仍匹配
route.path(允许末尾斜杠),这是防重定向的核心防线; - 依次执行所有
ready检查; - 尝试等待
networkidle(失败静默忽略),最后执行runA11yScan。
README 对静态路由还有三条约束,与清单excluded组的排除理由一一对应:每个独立页面表面只做一次扫描;不添加重定向别名、文件夹过滤路由,或仅数据不同的同组件路由。例如/components/folder/:folderId被排除,理由就是 "Same components list page, folder-filtered data"。
状态化覆盖(Stateful Coverage)
需要 UI 操作才能到达的状态——流程画布(flow canvas)、配置面板、认证校验、toast、对话框、playground——应写在聚焦的独立 spec中,而不是塞进static-routes.a11y.spec.ts。README 给出的原则是:静态路由必须保持「便宜且可预测」。从目录实际内容看,这层覆盖由约 15 个聚焦 spec 承担,包括 files.a11y.spec.ts、auth-pages.a11y.spec.ts、mcp-servers.a11y.spec.ts、shared-playground.a11y.spec.ts 等;dynamic组中/playground/:id/条目也显式标注了coveredBy指向对应 spec。
目录下的baselines/存放着按「浏览器__检查名」命名的已提交基线 JSON(如chromium__assets-files-actions-menu.json),供断言模式比对扫描结果是否回归。
本地命令
以下命令均在src/frontend目录下执行:
cd src/frontend # 只跑静态路由扫描 RUN_A11Y=true npx playwright test tests/a11y/static-routes.a11y.spec.ts --project=chromium --workers=5 # 跑整个 a11y 目录 RUN_A11Y=true npx playwright test tests/a11y --project=chromium --workers=5 # 汇总所有 JSON 报告并生成 HTML npm run a11y:html-report --silent # 生成本轮作业的文本摘要 npm run a11y:job-summary --silentRUN_A11Y=true是扫描的开关,只有开启后runA11yScan调用才会真正产出报告。三个 npm 脚本定义在 src/frontend/package.json:a11y:report(tests/utils/aggregate-a11y-reports.mjs,聚合 JSON)、a11y:html-report(build-a11y-html-report.mjs)、a11y:job-summary(build-a11y-job-summary.mjs)。
HTML 报告写入:
coverage/accessibility-reports/index.html它按「先路由、后规则」两级分组展示问题,每个问题条目包含:IBM 消息、目标元素、DOM 路径、ARIA 路径、元素边界(element bounds)、代码片段(snippet)以及对应 IBM 规则链接。HTML 构建脚本还会读取 a11y_routes.json 把报告标签(如route-settings-api-keys)映射回路由路径与 surface 名称(见 a11y_scan_routes.md)。
对基线断言
要针对 checker 基线做断言(即扫描结果偏离已提交基线时让测试失败),加上RUN_A11Y_ASSERT=true:
RUN_A11Y=true RUN_A11Y_ASSERT=true npx playwright test tests/a11y --project=chromium --workers=5CI 集成:a11y-scan.yml 工作流
a11y-scan.yml 的触发方式有三种:pull_request(每次 PR)、每日 02:00 UTC 定时(cron 只从默认分支触发,因此定时运行会先解析出最新的release-*分支再扫描,与 nightly_build 同模式)、以及workflow_dispatch手动触发(可传入ref和assert参数)。
关键步骤值得注意:
- 环境:Node 22、Playwright 1.60.0(只安装 chromium),Python 3.13 + uv;
- 动态发现扫描宿主:
SCAN_SPECS=$(grep -rl "runA11yScan(" tests --include="*.spec.ts" | sort) test -n "$SCAN_SPECS" npx playwright test $SCAN_SPECS --project=chromium --workers=1 --retries=2注意 CI 与本地命令的差异:CI 用--workers=1 --retries=2保证确定性和重试容错,而本地 README 推荐--workers=5提速;RUN_A11Y_ASSERT仅在手动触发且勾选assert输入时置为true。
- 报告与产物:无论扫描成败都会执行
Build IBM Scan Summary(if: always()),只要coverage/accessibility-reports/下存在 JSON 报告,就生成 HTML 报告并把npm run a11y:job-summary的输出写入 GitHub Step Summary;随后上传ibm-a11y-reports-${run_attempt}工件(30 天保留期),在 run 页面下载后打开index.html即可查看完整的路由级报告。 - 扫描前关闭追踪:设置
LANGFLOW_DEACTIVATE_TRACING=true,避免遥测干扰被测页面。
临时扫描器:a11y_scan.py 的参数与能力
scripts/a11y/a11y_scan.py 是一个「路由感知的 IBM ACE 扫描 + API 请求跟踪」工具。它的典型调用方式(摘自 a11y_scan_routes.md)直接消费路由清单:
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-canonical.json \ --markdown /tmp/langflow-a11y-static-canonical.md \ --html /tmp/langflow-a11y-static-canonical.html \ --timeout-ms 45000完整命令行参数(对应 parse_args):
| 参数 | 默认值 | 说明 |
|---|---|---|
--url(必填) | — | 基础 URL 或完整页面 URL |
--routes/--route | 空 | 逗号分隔/可重复的路由,显式指定时覆盖清单 |
--routes-file | 空 | 路由清单 JSON(即a11y_routes.json) |
--route-group | static | 清单中的路由分组 |
--states-file | 空 | 每个路由加载后要执行的模态/状态动作 JSON |
--levels | violation | 逗号分隔:violation,potentialviolation,recommendation,manual |
--timeout-ms/--quiet-ms | 30000/1000 | 导航超时 / 网络静默窗口 |
--out | a11y-scan-report.json | JSON 报告输出路径 |
--markdown/--html | 空 | 可选的 Markdown / 自包含 HTML 报告路径 |
--ace-url | unpkg 上的accessibility-checker-engine@latest/ace.js | IBM ACE 脚本来源,可指向本地文件 |
--browser-executable | 环境变量PLAYWRIGHT_CHROMIUM_EXECUTABLE | 指定 Chrome/Chromium 可执行文件,也可自动探测系统 Chrome/Chromium/Edge |
--headed | off | 有头模式运行 |
扫描机制的源码细节
理解该脚本的三处实现,能解释它报告里那些字段从何而来:
- 网络静默判定(settled network):wait_for_settled_network 维护一个「在途请求」集合,只有当集合为空且经过
--quiet-ms的静默窗口后才认为页面就绪,而不是简单等待load事件。 - API 请求跟踪:脚本通过
page.on("request"/"response"/"requestfailed")钩子,把同源于/api/、/health、/config的请求记入每个结果的apiRequests(含 method、url、status),失败请求记入requestFailures。这样每条路由的 a11y 报告同时回答「页面加载过程中后端接口是否正常」,扫描结果里若某非closed阶段没有任何 API 请求还会打印warn: no same-origin API/config/health requests observed。 - 状态动作序列:
--states-file中每个状态可声明open/close动作列表,支持click、clickText、clickRole、fill、press、waitFor、waitForHidden、waitForText、wait九种原子动作(run_action)。每个状态在open阶段执行 ACE 检查并附加模态诊断(可见 dialog 数量、焦点是否落在 dialog 内、打开前的焦点元素),close阶段再执行关闭动作并生成closed阶段记录。这使工具能覆盖「点击按钮弹出对话框后」这类静态扫描触及不到的表面。
报告产物
- JSON(
--out):包含generatedAt、url、routes、reportLevels、totalIssues与逐路由/状态的results(每条含route、state、phase、finalUrl、durationMs、apiRequests、requestFailures、visibleText、diagnostics、issues); - Markdown(
--markdown):路由汇总表 + Top Rules 规则计数表 + 逐路由 Findings(每条 issue 附 ruleId、message、path、source、snippet); - HTML(
--html):自包含单文件,按「route → state → issue」三级<details>折叠展开,摘要区显示总 issue 数、路由数、levels、base URL,支持深浅色主题自动切换。
检查标准参考
a11y 目录下另有两份检查标准文档:ibm-a11y-level1-criteria.md 与 ibm-able-level-1-requirements.md,描述项目对齐的 Level 1 要求,可作为修复 issue 时对照的规范基线。
小结:新增一个路由的完整流程
综合 README 与各工具源码,为 Langflow 新增 a11y 覆盖的标准路径是:
- 判断该路由属于「独立页面表面」还是「同组件数据变体」——后者应加入 a11y_routes.json 的
excluded组并写明理由; - 静态路由:在
static组新增条目,附id、path、surface和稳定的ready检查(testId/role+name/oneOf均可),无需改动 spec——static-routes.a11y.spec.ts 会自动为清单中的每条路由生成测试; - 状态化表面:写一个新的聚焦 spec 并调用
page.runA11yScan(...),CI 的 grep 发现机制会自动把它纳入工作流; - 本地验证:
RUN_A11Y=true npx playwright test tests/a11y --project=chromium,再用npm run a11y:html-report检查coverage/accessibility-reports/index.html;需要断言基线时加RUN_A11Y_ASSERT=true; - 需要临时批次或模态状态排查时,用 a11y_scan.py 配
--states-file输出独立报告。
整个体系的不变式是:路由目标只维护在a11y_routes.json一份清单里,Python 扫描器、Playwright spec、HTML 报告构建器三方共用同一来源,任何一侧的路线变更都会立刻在其他侧暴露为失败,而不是静默漂移。
【免费下载链接】langflowLangflow is a powerful tool for building and deploying AI-powered agents and workflows.项目地址: https://gitcode.com/GitHub_Trending/la/langflow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考