大模型驱动的无障碍设计审查:自动化 WCAG 合规检测

cover

一、无障碍的"合规盲区":手动审查的覆盖率困境

Web 无障碍(Accessibility)合规是法律要求(欧盟 EN 301 549、美国 ADA),但大多数团队的无障碍审查依赖手动检查。一个中等复杂度的页面包含 50+ 个交互元素,每个元素需要检查颜色对比度、焦点顺序、ARIA 标签、键盘可达性等 10+ 项规则。全量手动审查一个页面需要 2-3 小时,而产品迭代周期可能只有 1 周。结果是:无障碍审查被压缩到"上线前扫一眼",大量合规问题被遗漏。自动化工具(axe-core、Lighthouse)能检测 30%-40% 的规则,但语义层面的审查(如 ARIA 标签是否准确描述了元素功能)仍需人工判断。

二、WCAG 规则体系与自动化检测边界

2.1 WCAG 规则的分类与自动化可行性

flowchart TB
    A[WCAG 2.2 规则] --> B[可自动化检测<br/>约 40%]
    A --> C[需语义理解检测<br/>约 35%]
    A --> D[需上下文判断<br/>约 25%]

    B --> B1[颜色对比度]
    B --> B2[HTML 语义结构]
    B --> B3[焦点顺序]
    B --> B4[表单标签关联]

    C --> C1[ARIA 标签准确性]
    C --> C2[图片替代文本质量]
    C --> C3[链接文本描述性]
    C --> C4[标题层级合理性]

    D --> D1[错误提示可理解性]
    D --> D2[多模态内容等效性]
    D --> D3[动画可暂停性]
    D --> D4[认知负荷评估]

2.2 传统自动化工具的局限

from playwright.sync_api import sync_playwright

def run_axe_audit(url: str) -> dict:
    """使用 axe-core 进行自动化无障碍审查"""
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.goto(url)

        # 注入 axe-core 并执行审查
        results = page.evaluate("""
            async () => {
                const results = await axe.run();
                return {
                    violations: results.violations.map(v => ({
                        id: v.id,
                        impact: v.impact,
                        description: v.description,
                        nodes: v.nodes.map(n => ({
                            html: n.html,
                            target: n.target,
                        })),
                    })),
                };
            }
        """)

        browser.close()
        return results

axe-core 能检测对比度不足、缺少 alt 属性、焦点顺序错误等结构性问题,但无法判断:alt 文本是否准确描述了图片内容、ARIA 标签是否与元素功能匹配、错误提示是否对用户友好。

三、大模型驱动的语义审查方案

3.1 基于 LLM 的 ARIA 标签质量评估

import json

class AriaLabelAuditor:
    """使用 LLM 评估 ARIA 标签的准确性和描述性"""

    def __init__(self, llm_client):
        self.llm = llm_client

    def audit_aria_labels(self, elements: list) -> list:
        """批量审查 ARIA 标签质量"""
        results = []

        for elem in elements:
            prompt = f"""评估以下 HTML 元素的 ARIA 标签质量。

元素类型: {elem['tag']}
元素功能: {elem.get('role', '未指定')}
当前 aria-label: {elem.get('aria_label', '未设置')}
元素上下文: {elem.get('context', '无')}
视觉外观描述: {elem.get('visual_desc', '无')}

请从以下维度评估:
1. 准确性:标签是否准确描述了元素功能
2. 简洁性:标签是否简洁(建议15字以内)
3. 唯一性:标签是否与页面上其他元素区分
4. 一致性:标签风格是否与页面其他标签一致

输出 JSON 格式:
{{"score": 0-10, "issues": [...], "suggestion": "..."}}"""

            response = self.llm.chat(prompt)
            assessment = json.loads(response)
            assessment['element'] = elem
            results.append(assessment)

        return results

3.2 图片替代文本质量评估

class AltTextAuditor:
    """评估图片替代文本的质量"""

    def __init__(self, llm_client, vision_client):
        self.llm = llm_client
        self.vision = vision_client

    def audit_alt_text(self, images: list) -> list:
        """对比图片实际内容与 alt 文本的匹配度"""
        results = []

        for img in images:
            # 1. 使用视觉模型理解图片内容
            actual_content = self.vision.describe(img['src'])

            # 2. 评估 alt 文本与实际内容的匹配度
            prompt = f"""评估图片替代文本的质量。

图片实际内容: {actual_content}
当前 alt 文本: {img.get('alt', '未设置')}

评估维度:
1. 信息完整性:alt 是否传达了图片的关键信息
2. 简洁性:alt 是否避免冗余描述(如"图片显示...")
3. 功能性:对于装饰性图片,alt 是否为空
4. 上下文相关性:alt 是否匹配图片在页面中的用途

输出 JSON: {{"score": 0-10, "issues": [...], "suggestion": "..."}}"""

            response = self.llm.chat(prompt)
            assessment = json.loads(response)
            assessment['image_src'] = img['src']
            results.append(assessment)

        return results

3.3 综合审查管线

class AccessibilityAuditPipeline:
    """自动化 + LLM 的综合无障碍审查管线"""

    def __init__(self, llm_client, vision_client):
        self.aria_auditor = AriaLabelAuditor(llm_client)
        self.alt_auditor = AltTextAuditor(llm_client, vision_client)

    def audit_page(self, url: str) -> dict:
        """执行完整的页面无障碍审查"""

        # 阶段1:结构化自动化检测(快速、确定性)
        auto_results = run_axe_audit(url)

        # 阶段2:提取需要语义审查的元素
        elements = self._extract_semantic_elements(url)

        # 阶段3:LLM 语义审查(慢速、概率性)
        aria_results = self.aria_auditor.audit_aria_labels(
            elements['interactive']
        )
        alt_results = self.alt_auditor.audit_alt_text(
            elements['images']
        )

        # 阶段4:合并结果并生成报告
        report = {
            'url': url,
            'auto_violations': len(auto_results['violations']),
            'semantic_issues': len(aria_results) + len(alt_results),
            'total_score': self._compute_score(
                auto_results, aria_results, alt_results
            ),
            'details': {
                'auto': auto_results,
                'aria': aria_results,
                'alt_text': alt_results,
            },
        }

        return report

    def _extract_semantic_elements(self, url: str) -> dict:
        """从页面提取需要语义审查的元素"""
        with sync_playwright() as p:
            browser = p.chromium.launch()
            page = browser.new_page()
            page.goto(url)

            elements = page.evaluate("""
                () => {
                    const interactive = Array.from(
                        document.querySelectorAll(
                            '[aria-label], [aria-labelledby], button, a, input'
                        )
                    ).map(el => ({
                        tag: el.tagName,
                        role: el.getAttribute('role'),
                        aria_label: el.getAttribute('aria-label'),
                        context: el.parentElement?.tagName,
                        visual_desc: el.textContent?.slice(0, 100),
                    }));

                    const images = Array.from(
                        document.querySelectorAll('img')
                    ).map(el => ({
                        src: el.src,
                        alt: el.alt,
                        role: el.getAttribute('role'),
                    }));

                    return { interactive, images };
                }
            """)

            browser.close()
            return elements

四、边界分析与架构权衡

4.1 LLM 审查的不确定性

同一元素在不同 LLM 调用中可能给出不同评分,评分标准受模型版本和提示词措辞影响。缓解策略:对关键规则使用确定性规则引擎(axe-core),LLM 仅补充语义层面审查;对 LLM 结果取多次采样的众数。

4.2 审查延迟与成本

一个包含 50 个交互元素和 20 张图片的页面,LLM 语义审查需要 70 次 API 调用,耗时约 2-3 分钟,成本约 0.5-1 美元。在 CI/CD 管线中,这可能导致构建时间显著增加。优化方案:增量审查(只检查变更元素)、缓存已审查结果。

4.3 视觉模型的描述偏差

视觉模型对图片的理解可能存在偏差——将装饰性图标描述为"信息图表",导致 alt 文本建议过于详细。需要结合 HTML 上下文(元素尺寸、位置、role 属性)综合判断图片的功能性角色。

4.4 合规性 vs 用户体验

WCAG 合规是底线,但合规不等于好的无障碍体验。一个技术上合规的 ARIA 标签可能对屏幕阅读器用户毫无帮助(如 aria-label="按钮")。LLM 审查应同时评估"合规性"和"实用性",后者需要更多用户测试数据支撑。

五、总结

大模型驱动的无障碍设计审查,填补了传统自动化工具在语义层面的检测空白。axe-core 负责结构化规则检测(对比度、焦点顺序、HTML 语义),LLM 负责 ARIA 标签准确性、alt 文本质量和错误提示可理解性等语义审查。两者结合可将无障碍检测覆盖率从 40% 提升到 75%+。工程实践中需注意 LLM 审查的不确定性和延迟成本,采用增量审查和结果缓存策略优化管线效率。最终,自动化审查是辅助工具,不能完全替代真实用户的无障碍测试。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐