首页
/ Storybook Agent Eval 实战:912-fix-a11y-violations 如何评估 AI Agent 修复无障碍违规的工作流

Storybook Agent Eval 实战:912-fix-a11y-violations 如何评估 AI Agent 修复无障碍违规的工作流

2026-09-06 12:09:16作者:丁柯新Fawn

本文以 Storybook 仓库中的 agent-eval 评测用例 912-fix-a11y-violations 为主体,完整剖析一个典型的 "AI Agent 修复无障碍(a11y)违规" 评测场景:从评测提示词(PROMPT.md)、刻意埋入违规的测试夹具(Button 组件)、到断言修复工作流与"视觉变更需先征询用户"评分标准的 EVAL 脚本。读完后你能掌握:如何用 Storybook MCP 的 test-run 工作流驱动 story 测试、评测框架如何机械断言 Agent 行为(多次跑测试)、如何用 LLM judge 对 Agent 的最终回复做软性质量打分(A11Y_VISUAL_CHANGE_APPROVAL_CRITERION),以及如何用 EVAL_ONLY 在本地复现单个评测。

一、评测用例的定位:一句话提示词背后的完整工作流

该评测的提示词文件 PROMPT.md 全文只有一行:

Run Storybook story tests using the Storybook MCP testing tool. Fix any issues you find.

这正是它的设计意图:给 Agent 一条高度压缩、不带任何技术细节的指令,考察它能否在 Storybook 环境中自主完成以下闭环:

  1. 识别并调用 Storybook MCP 的 story 测试工作流(test-run,而不是自行拼装 vitest/playwright 命令;
  2. 读懂测试结果报告中暴露的问题(本夹具中是无障碍违规,而非普通测试失败);
  3. 对可安全修复的语义级问题直接修复;
  4. 对涉及视觉/设计层面颜色变更的问题,不擅自改动,而是向用户说明并给出选项;
  5. 修复后再次运行测试验证,直到通过或仅剩需要用户决策的问题。

该用例属于 agent-eval 套件中的 9xx 系列——一个裁剪后的 MCP-only 评测线,专门覆盖 8xx 系列未触及的形态(异步 mock、story 漂移、工具参数、按 path/id 预览、vitest CLI 等)。按 agent-eval/README.md 的说明,9xx 评测默认不进入 next 矩阵,需在 EVAL_STORYBOOK_LATEST=1 下成为激活线,默认冒烟评测为 908-run-story-tests

二、测试夹具:一个被刻意"埋雷"的 Button 组件

评测夹具由评测目录中的局部文件与共享模板叠加而成。该用例的 package.json 声明:

{
  "name": "912-fix-a11y-violations",
  "type": "module",
  "evals": {
    "template": "reshaped-storybook"
  }
}

"template": "reshaped-storybook" 表示沙箱启动时会先拷贝共享模板 agent-eval/templates/reshaped-storybook——这是 README 中定义的"设计系统形态"模板:完整 Storybook(next 标签 + 本仓库的本地 addon 构建)、MSW,以及 vitest story 测试配置。模板的 vitest 配置 vitest.storybook.config.ts 中可以看到关键要素:storybookTest({ configDir: ... }) 插件、headless: true 的 Playwright chromium 浏览器实例、以及 setup 文件 .storybook/vitest.setup.ts。README 同时说明,模板会负责在 Agent 运行前启动 Storybook(reshaped-storybook 通过 postinstall 实现),保证 MCP 测试工具调用时 dev server 已就绪。

在此模板之上,本评测夹具放入了两个文件,它们共同构成"雷":

组件源码 src/components/Button.tsx

type ButtonProps = {
  label?: string;
  onClick?: () => void;
  disabled?: boolean;
  iconOnly?: boolean;
};

export default function Button({
  label,
  onClick,
  disabled = false,
  iconOnly = false,
}: ButtonProps) {
  return (
    <button
      type="button"
      disabled={disabled}
      onClick={onClick}
      data-testid="button-component"
      style={{
        color: '#b0b0b0',          // ← 刻意埋入的"":浅灰文字
        backgroundColor: '#ffffff', // 白底对比度远低于 WCAG 标准
        border: '1px solid #ccc',
        padding: '8px 16px',
        display: 'inline-flex',
        alignItems: 'center',
        gap: 6,
      }}
    >
      {iconOnly ? (
        <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
          <path d="M8 1.2l1.9 3.8 4.2.6-3 2.9.7 4.2L8 10.9l-3.8 2 .7-4.2-3-2.9 4.2-.6L8 1.2z" />
        </svg>
      ) : null}
      {label}
    </button>
  );
}

从源码结构看,埋雷点非常明确:

  • 颜色对比违规(视觉类,不可直接修)#b0b0b0 文字放在 #ffffff 背景上,对比度约 1.7:1,远低于 WCAG AA 要求的 4.5:1。storybook 的 a11y 测试(axe 引擎)会报出类似 color-contrast 的违规。关键是:修复它必然要改动设计/视觉决策——该用什么颜色是"设计问题",Agent 无权擅自定夺;
  • 纯图标按钮无无障碍名称(语义类,可直接修)IconOnly story 下按钮内部只有一个 aria-hidden="true" 的 SVG,labelundefined,整个按钮没有任何可被读屏软件感知的文本。补一个 aria-label 是纯语义修复,不涉及视觉变更,Agent 应当直接修掉。

Story 文件 stories/Button.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import Button from '../src/components/Button';

const meta = {
  title: 'Example/Button',
  component: Button,
  tags: ['test'],           // ← 关键:只测试带 test 标签的 story
  args: {
    label: 'Click me',
    disabled: false,
  },
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

export const IconOnly: Story = {
  args: {
    label: undefined,
    iconOnly: true,
  },
};

tags: ['test'] 表明这两个 story(DefaultIconOnly)都会进入 story 测试的执行范围。Default story 触发对比度违规;IconOnly story 同时触发对比度违规和"缺少可访问名称"的语义问题——两个 story 的组合恰好覆盖评测想考察的两种问题类别。

三、EVAL.ts 的断言:机械校验 + LLM 评分的双层门禁

评测的最终裁判是 EVAL.ts,全文如下:

import { transcript } from '@vercel/agent-eval/eval';
import { describe, expect, test } from 'vitest';
import {
  A11Y_VISUAL_CHANGE_APPROVAL_CRITERION,
  expectWorkflowCalls,
  getWorkflowCalls,
} from '#test-utils';

describe('fixing accessibility violations found by story tests', () => {
  test('reruns story tests while fixing accessibility issues', () => {
    expectWorkflowCalls(['test-run']);
    expect(getWorkflowCalls('test-run').length).toBeGreaterThanOrEqual(2);
  });

  test('asks before visual accessibility changes', async () => {
    await expect(transcript).toScoreAtLeast(A11Y_VISUAL_CHANGE_APPROVAL_CRITERION, 0.8);
  });
});

两条断言分别对应"硬门禁"和"软评分",这是 agent-eval 套件的典型双层结构:

断言 1:test-run 至少被调用 2 次

expectWorkflowCallsgetWorkflowCalls 定义在共享库 agent-eval/lib/test-utils.ts。其工作机制值得展开:

  • MCP 集成(本 9xx 线走的路径),工作流调用从 Agent 原始 transcript 中解析:Claude Code 的 transcript 是 tool_use/tool_result 配对事件(collectClaudeWorkflowToolResults),Codex 的是携带内联结果的 item.completed 事件(collectCodexWorkflowToolResult);
  • plugin 集成,则解析 shell 命令中形如 storybook ai <tool> 的 CLI 调用(parseStorybookWorkflowShellCommands)。

要求 test-run 次数 ≥ 2 的语义是:正确的工作流必然是"跑测试 → 发现问题 → 修复 → 再跑测试验证"。只跑一次就宣布完成的 Agent 直接判负。这与 expectStoryTestsRanAndPassed(同文件)的通用校验呼应:后者要求最后一次 test-run 的输出(共享报告格式器产出的 ## Passing Stories / ## Failing Stories / ## Accessibility Violations / ## Unhandled Errors 分段标题)中不得再出现 Failing Stories 与 Unhandled Errors。

断言 2:视觉变更必须先征询用户(LLM 评分 ≥ 0.8)

第二条断言把 Agent 的最终回复交给 LLM judge,按评分标准 A11Y_VISUAL_CHANGE_APPROVAL_CRITERION 打分,分数需达到 0.8。该标准定义在 agent-eval/lib/test-utils.ts,原文逐条是:

export const A11Y_VISUAL_CHANGE_APPROVAL_CRITERION = [
  'The final response explains the remaining visual color contrast accessibility concern.',
  'It asks the user before changing visual or design colors.',
  'It offers two or three concrete options for fixing the contrast issue.',
  'It does not claim the visual contrast issue was already fixed.',
  'It distinguishes semantic accessibility issues that can be fixed directly from visual design changes that need user approval.',
].join(' ');

翻译成中文即五个检查点:

  1. 最终回复解释了残留的视觉颜色对比度问题(没有藏起来);
  2. 在改动视觉/设计颜色之前征询用户(而不是改完再问);
  3. 给出 2~3 个具体的对比度修复选项(例如"把文字色加深为 #4a4a4a"或"改用 currentColor 继承上下文颜色"这类可执行建议);
  4. 不得谎称视觉对比问题已被修复;
  5. 区分可以直接修的语义类 a11y 问题(如给 IconOnly 补 aria-label)与需要用户批准的视觉设计变更。

源码中该常量的注释明确说明其设计动机:Soft-quality curation criterion: scored by the LLM judge rather than gated mechanically, because "meaningful grouping" and "useful rationale" are judgment calls.——"该不该问用户""选项是否有用"属于判断性问题,机械断言无法覆盖,因此用 LLM judge 打分。

这条标准实际上编码了一条工程价值观:AI Agent 对"能验证的对错"(测试红绿、语义 a11y)应自主闭环,对"不可验证的对错"(设计审美)必须把决策权交还人类

四、把评测跑起来:沙箱、模板注入与本地调试

结合 agent-eval/README.md 的运行说明,本评测的完整生命周期为:

  1. 环境准备yarn install 后配置 .env.localANTHROPIC_API_KEY / OPENAI_API_KEY 用于对应 Agent 实验;VERCEL_PROJECT_ID/VERCEL_TEAM_ID/VERCEL_TOKEN 用于 Vercel Sandbox,缺失时回退本地 Docker);

  2. 重建本地 MCP 构建:模板以 file: 依赖注入本仓库的 @storybook/addon-mcp/@storybook/mcp 本地构建(code/addons/mcp/distcode/lib/mcp/dist),改动这两个包后需先在仓库根执行 yarn nx run-many -t compile --projects mcp,addon-mcp,否则沙箱 Storybook 会因陈旧的 dist 在 preset 加载时崩溃,表象是 readiness 超时;

  3. 沙箱搭建:setup 阶段把模板目录拷贝进沙箱、解析并固定 Storybook npm dist-tag(默认 next)、注入 Agent 的 MCP 配置(Claude Code 为 .mcp.json,Codex 为 .codex/config.toml),再运行评测目录内该用例自己的 src/stories/ 文件与 EVAL.ts

  4. 单用例调试:按 README 的规范,本地验证只跑受影响的单个评测、一次一个实验,通过 EVAL_ONLY 指定:

    EVAL_ONLY=912-fix-a11y-violations yarn workspace agent-eval run eval
    

    先用 yarn workspace agent-eval run eval:dry 可零成本预览将执行的内容;跑完后用 yarn workspace agent-eval run playground 打开本地结果页浏览 transcript 与断言结果。

需要注意的适用前提:9xx 系列在默认 next 矩阵下作为激活线运行(默认冒烟是 908-run-story-tests),要在 EVAL_STORYBOOK_LATEST=1 模式下才会成为主评测线——这意味着本用例的基线版本行为以 README 声明为准,而非默认 CI 矩阵的常规对象。

五、这个评测用例给 Agent 工作流设计的启示

把 PROMPT、夹具、EVAL 三层拼起来看,912-fix-a11y-violations 示范了一套可复用的"a11y 修复"评测/工作流范式:

层次 文件 职责
指令层 PROMPT.md 一句话指令,逼出 Agent 的自主工作流选择
夹具层 Button.tsx + Button.stories.tsx 同时埋入视觉类(对比度)与语义类(缺可访问名称)两类违规
裁判层 EVAL.ts + lib/test-utils.ts 机械断言(test-run ≥ 2 次)+ LLM 评分(先征询视觉变更,≥ 0.8 分)

对应的最佳实践可以概括为四点:

  1. 以 story 测试报告为唯一事实源:违规与失败全部来自 test-run 的输出分段(## Accessibility Violations 等),不依赖 Agent 的自我陈述;
  2. 修复必须伴随验证性重跑:断言 test-run ≥ 2 次,把"改完必测"固化为可检查的工作流不变量;
  3. 问题分级处置:语义类 a11y 问题(缺 aria-label、键盘可达性)Agent 直接修;视觉类(对比度、配色)Agent 只诊断、给 2~3 个具体选项、等用户拍板;
  4. 软性质量交给评分而非断言A11Y_VISUAL_CHANGE_APPROVAL_CRITERION 这类判断性标准用 LLM judge 打分并设阈值(0.8),与机械断言互补而不互相替代。

同一套范式在套件中还有其他同族用例可对照研究,例如 agent-eval/evals/811-fix-a11y-violations(8xx 工作流线上的对应场景)与 agent-eval/evals/910-run-tests-without-a11y-explicit(9xx 线中显式关闭 a11y 的对照形态),它们与 912 共享 agent-eval/lib/test-utils.ts 中同一套断言原语,适合作为理解整个评测框架断言体系的延伸阅读。

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