在双 11 这样万众瞩目的国民级电商战役中,技术的价值不仅体现在每秒能抗住多少万笔订单,更体现在对每一位用户的包容与尊重。随着我国《无障碍环境建设法》的深入落地以及各大平台“适老化与长辈版”规范的常态化,Web 可访问性(Accessibility,简称 a11y)已经从过去“可有可无的加分项”上升为大促发布封网前不可逾越的合规红线与法律门禁。
然而,长期以来前端团队在保障 a11y 体验时却面临着极其尴尬的技术困境:
- 静态 Lint 工具(如 eslint-plugin-vue-a11y)过于教条:它只能检查
<img>标签写没写alt属性,却无法识别图片实际是不是纯装饰性背景; - 传统 Axe-core 规则引擎存在大量盲区:无法有效检测多层 CSS 混合模式(Blend Mode)叠加后的实际视觉对比度、无法判断大模型动态生成的弹窗是否具备完整的焦点捕获(Focus Trap);
- 人工读屏测试效率极低:几十个营销分会场若全靠人工开启 VoiceOver 逐个点击,在双 11 高频迭代的封网周根本无法承载。
为了在发布封网前建立起既有“视觉审美辨识度”又有“物理刚性阻断力”的安全长城,我们设计并落地了一套基于 Playwright 无头渲染 + 多模态视觉模型 + DOM 语义拓扑的自动化 a11y 审查门禁。
一、端云一体多模态 a11y 门禁架构设计
门禁的核心思想在于:拒绝纸上谈兵的纯代码文本扫描,必须让页面在真实的无头浏览器中渲染出来,同时提取“真实视觉截图”与“DOM 可访问性渲染树(AXTree)”,交由多模态 AI 联合裁决。
[开发者发起发布 PR / 封网合并申请] │ ▼ [Playwright 无头渲染容器] ├── ① 模拟无障碍读屏设备视口与色彩模式 ├── ② 捕获页面高清屏幕像素快照 (Visual Snapshot) ├── ③ 提取真实 Computed Accessibility Tree (AXTree) │ ▼ [多模态 AI 联合审查引擎 (Vision + AXTree Engine)] ├── ④ 色彩对比度与视觉焦点环(Focus Ring)AI 视觉判读 ├── ⑤ 核心购物主链路(加购/领券/结算)键盘导航流(Tab Flow)追踪 └── ⑥ 违规级别量化定级 (P0 阻断 / P1 告警 / P2 建议) │ ┌──────────┴──────────┐ │ │ [P0 违规拦截] [100% 合规通过] │ │ ▼ ▼ [CI 亮红牌阻断合并] [生成合规证书并放行]二、无头自动化采样与可访问性树提取
在 CI 流水线中,我们通过 Playwright 驱动 Chromium 提取真实的无障碍快照:
// scripts/a11y-snapshot-collector.ts import { chromium, Page } from 'playwright'; import fs from 'fs-extra'; import path from 'path'; export interface A11yAuditPayload { url: string; screenshotBase64: string; axTree: any; interactiveElements: Array<{ selector: string; role: string; name: string; boundingBox: { x: number; y: number; width: number; height: number }; }>; } export async function collectPageA11yData(pageUrl: string): Promise<A11yAuditPayload> { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 375, height: 812 }, // 模拟移动端主流视口 deviceScaleFactor: 2 }); const page = await context.newPage(); await page.goto(pageUrl, { waitUntil: 'networkidle' }); // 1. 获取浏览器内核底层计算得出的可访问性树 (AXTree) const snapshot = await page.accessibility.snapshot({ interestingOnly: true }); // 2. 收集所有关键可交互节点(按钮、链接、输入框)的物理渲染位置与无障碍角色 const interactiveElements = await page.evaluate(() => { const targets = Array.from(document.querySelectorAll('button, a, input, [role="button"]')); return targets.map((el, index) => { const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); return { selector: el.tagName.toLowerCase() + (el.id ? `#${el.id}` : `[data-idx="${index}"]`), role: el.getAttribute('role') || el.tagName.toLowerCase(), name: el.getAttribute('aria-label') || el.textContent?.trim() || '', boundingBox: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } }; }).filter(e => e.boundingBox.width > 0 && e.boundingBox.height > 0); }); // 3. 截取全屏高保真图像 const screenshotBuffer = await page.screenshot({ fullPage: false }); const screenshotBase64 = screenshotBuffer.toString('base64'); await browser.close(); return { url: pageUrl, screenshotBase64, axTree: snapshot, interactiveElements }; }三、多模态 AI 联合裁判与刚性卡点
将视觉截图与 AXTree 序列化后,输入至经过专门微调的多模态审查模型,执行结构化判定:
// scripts/a11y-ai-judge.ts import { A11yAuditPayload } from './a11y-snapshot-collector'; export interface A11yViolation { level: 'P0' | 'P1' | 'P2'; target: string; rule: string; reason: string; suggestion: string; } export async function judgeA11yCompliance(payload: A11yAuditPayload): Promise<A11yViolation[]> { const prompt = ` 作为资深 Web 可访问性(WCAG 2.2 AAA)审查专家与视觉设计师,请审查传入的双 11 移动端页面。 你将获得页面的视觉截图(Base64)以及对应的浏览器 AXTree 树。 重点排查以下三项致命违规(P0 级): 1. 核心操作元素(领券、加购、结算、关闭弹窗)无有效无障碍标签(无文字且无 aria-label)。 2. 文字或核心图标与背景的视觉对比度低于 4.5:1(WCAG AA 刚性红线),特别是大促渐变色背景上的微弱提示文字。 3. 弹窗打开后,键盘焦点未被正确锁定在弹窗内(Focus Trap 缺失)。 请以 JSON 数组格式严格返回违规清单,格式如下: [ { "level": "P0", "target": "button#buy-now", "rule": "WCAG 1.4.3 Contrast / 4.1.2 Name Role Value", "reason": "商品卡片中的领券按钮为纯渐变图标,DOM 中没有任何文本内容,且缺少 aria-label 属性,盲人读屏软件将其读为'未命名的按钮'。", "suggestion": "在按钮元素上添加 aria-label='立即领取满300减50元优惠券'。" } ] `; // 调用内部大模型 API 执行推理... const response = await callMultimodalLLM({ systemPrompt: prompt, image: payload.screenshotBase64, metadata: { axTree: payload.axTree, interactiveElements: payload.interactiveElements } }); return JSON.parse(response); }在 CI 阶段直接设置刚性退出码:
// scripts/run-a11y-ci.ts import { collectPageA11yData } from './a11y-snapshot-collector'; import { judgeA11yCompliance } from './a11y-ai-judge'; async function run() { const targetUrl = process.env.PR_PREVIEW_URL || 'http://localhost:5173'; console.log(`[a11y CI] 开始执行封网前无障碍合规审查: ${targetUrl}`); const data = await collectPageA11yData(targetUrl); const violations = await judgeA11yCompliance(data); const p0Errors = violations.filter(v => v.level === 'P0'); if (p0Errors.length > 0) { console.error('\n❌ [CRITICAL] 检测到 P0 级无障碍违规,触发大促封网刚性阻断!'); p0Errors.forEach(err => { console.error(`- 目标: ${err.target}`); console.error(` 规范: ${err.rule}`); console.error(` 根因: ${err.reason}`); console.error(` 建议: ${err.suggestion}\n`); }); process.exit(1); // 退出码 1,阻断 CI 合并 } console.log('✅ [PASSED] 页面 100% 通过无障碍合规检测,允许合并!'); } run();四、落地收益与真实阻断案例
在本周双 11 预售会场接入该门禁后,第一天便拦截了 8 起高危合规风险:
真实拦截案例:营销弹窗的“幽灵关闭按钮”
- 某核心分会场的弹窗关闭按钮直接用了一张绝对定位的背景 SVG 图标,没有文字,没有
aria-label; - 传统 Lint 检查通过,但 AI 门禁精准报错:“视觉上存在右上角叉号,但在 AXTree 中为一个匿名的空白元素,读屏用户完全无法关闭弹窗”,直接将 PR 阻断并附带修复建议。
- 某核心分会场的弹窗关闭按钮直接用了一张绝对定位的背景 SVG 图标,没有文字,没有
真实拦截案例:金色大促渐变字对比度翻车
- 设计师为了视觉华丽,在浅黄色背景上使用了细字体白色文字;
- AI 视觉判读结合像素采样,算出对比度仅为 2.1:1,严重违反 WCAG 4.5:1 红线,强行阻止了不良体验流入生产。
总结
可访问性不是施舍,而是前端工程最基本的底线敬畏。
借助“Playwright 渲染 + 多模态视觉模型”的现代 CI 审查门禁,我们将以往靠人工肉眼排查、靠用户投诉后亡羊补牢的被动防御,彻底重构为自动化、可视化、有理有据的刚性防线。在双 11 封网的重要关头,守住 a11y 门禁,就是守住技术向善的工程师初心。