Storybook MCP test-run 工具实战:运行 Button 故事测试并显式关闭 a11y 检查
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
本文以 Storybook 官方仓库中的 Agent 评测用例910-run-tests-without-a11y-explicit为核心,讲解如何用 Storybook 的 MCP 测试工具test-run针对指定组件故事运行自动化测试,以及如何通过{ a11y: false }参数显式关闭 a11y(无障碍)检查。读完后你将掌握test-run工具的完整调用方式(MCP 工具名与 CLI 形态)、a11y参数的作用机制,以及官方评测如何校验 Agent 是否正确地"带参调用"了该工具。
一、评测任务原文:三句话构成的明确指令
该评测的指令文档位于 agent-eval/evals/910-run-tests-without-a11y-explicit/PROMPT.md,全文仅三条指令,但信息密度很高:
- 使用 Storybook MCP 测试工具,为 Button 组件的故事运行测试;
- 必须使用
test-run工具,并汇报哪些故事通过、哪些失败; - 通过给
test-run传入{ a11y: false }来忽略 a11y 违规。
从评测命名可以看出设计意图:without-a11y-explicit表示"显式要求关闭 a11y"。这条指令的考点不在于"能否跑测试",而在于Agent 是否把a11y: false作为工具输入参数显式传出去——这正是区别于默认行为的"显式调用"能力。
二、评测工作区:一个最小化的 React Storybook 项目
评测项目由三部分组成,整体基于reshaped-storybook模板搭建(见 package.json 中的"template": "reshaped-storybook",模板源文件位于 agent-eval/templates/reshaped-storybook)。
2.1 被测组件 Button
Button.tsx 是一个极简的受控按钮组件:
type ButtonProps = { label: string; onClick?: () => void; disabled?: boolean; }; export default function Button({ label, onClick, disabled = false }: ButtonProps) { return ( <button type="button" onClick={onClick} disabled={disabled}>import type { Meta, StoryObj } from '@storybook/react'; import { expect, fn, userEvent, within } from 'storybook/test'; import Button from '../src/components/Button'; const meta = { title: 'Example/Button', component: Button, tags: ['test'], args: { label: 'Click me', onClick: fn(), disabled: false, }, } satisfies Meta<typeof Button>; export default meta; type Story = StoryObj<typeof meta>; export const Default: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); const button = canvas.getByRole('button', { name: 'Click me' }); await userEvent.click(button); await expect(args.onClick).toHaveBeenCalledTimes(1); }, }; export const Disabled: Story = { args: { label: 'Disabled', disabled: true, }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); const button = canvas.getByRole('button', { name: 'Disabled' }); await userEvent.click(button); await expect(args.onClick).not.toHaveBeenCalled(); }, };两个故事各有一个play函数:
- Default:在 canvas 内按可访问性角色
button定位名为 "Click me" 的按钮,用userEvent.click模拟点击,断言onClick(由fn()创建的 mock 函数)恰好被调用 1 次; - Disabled:将按钮设为禁用态,模拟点击后断言
onClick没有被调用——验证disabled属性确实阻止了事件派发。
这里的expect、fn、userEvent、within全部来自storybook/test,是 Storybook 内置的测试原语组合:fn()创建带调用记录的 mock,userEvent模拟真实用户交互,within(canvasElement)把查询范围限定在当前故事的渲染区域。
三、核心工具 test-run:调用方式与 a11y 参数
3.1 工具的多种调用形态
test-run是 Storybook MCP 工具集中的故事测试工具(MCP 插件本体位于 code/addons/mcp)。从评测基础设施的代码可以确认它在不同环境下有三种等价的调用形态:
- MCP 工具调用:在 Agent 会话中作为 MCP 工具直接调用,工具名为
test-run(在 Claude 类客户端中呈现为mcp__storybook-dev-mcp__test-run形式); - CLI 直调:
storybook ai test-run --json '{"stories":[{"storyId":"example-button--primary"}]}',其中--json后的对象就是工具输入参数; - npx 拉起:
npx storybook ai --port 6006 test-run,--port指向已运行的 Storybook 开发服务器。
这些形态均出自评测的解析器与单测,可参见 agent-eval/lib/shell-parse.ts(其中test-run被列为受识别的工作流命令)以及 agent-eval/lib/test-utils.test.ts 中的用例,例如:
'storybook ai test-run --json \'{"stories":[{"storyId":"example-button--primary"}],"a11y":false}\''3.2{ a11y: false }参数的作用
test-run的输入参数中,a11y字段控制是否在故事测试执行时同时进行 a11y 检查。传{ a11y: false }即只运行故事本身的断言(play 函数、交互断言),跳过无障碍规则检测。
为什么评测场景要显式关闭 a11y?结合上文第 2.1 节:Disabled故事渲染出一个原生 disabled 按钮,这类控件容易触发 a11y 规则告警(如禁用控件的可聚焦性/可达性问题)。在只关心"功能行为是否正确"的测试中,把 a11y 维度显式关掉可以让测试结果聚焦于交互断言,避免 a11y 违规掩盖或混淆功能失败的判定。这也解释了评测标题中 "without-a11y" 的用意。
在 Storybook 的测试实现层,a11y 结果被建模为独立的状态类型。例如 code/addons/vitest/src/constants.ts 中定义了STATUS_TYPE_ID_A11Y = 'storybook/a11y',并给出了a11y: false、a11yStatuses: []、a11yReports: {}等默认空态结构——从源码结构看,a11y 维度与功能测试维度是分开存储和汇报的,关闭 a11y 后相关状态即保持空态,这为"只报功能结果"提供了实现层面的印证。
四、评测如何校验 Agent 的行为:EVAL.ts 逐行解析
真正的"考题答案"校验逻辑在 EVAL.ts 中,它基于 vitest 断言 Agent 会话的工作流调用记录:
import { describe, expect, test } from 'vitest'; import { expectWorkflowCalls, getWorkflowCalls, type StorybookWorkflowCall } from '#test-utils'; describe('running Button story tests with a11y disabled via an explicit prompt', () => { function disablesA11y(call: StorybookWorkflowCall): boolean { return call.input.a11y === false; } test('runs Storybook story tests with a11y disabled', () => { expectWorkflowCalls(['test-run']); expect(getWorkflowCalls('test-run').some(disablesA11y)).toBe(true); }); });关键断言有两条:
expectWorkflowCalls(['test-run'])——要求 Agent 的会话记录中存在对工作流命令test-run的调用;getWorkflowCalls('test-run').some(disablesA11y)——要求至少有一次test-run调用的输入参数满足call.input.a11y === false(严格等于false,而不是未传该字段)。
第二条是精髓:如果 Agent 只运行了test-run但没传a11y: false,断言a11y === false会因为字段为undefined而失败。也就是说,评测区分了"默认行为下 a11y 恰好没报错"与"按指令显式声明关闭 a11y"两种情况,后者才是符合 PROMPT.md 要求的正确做法。这套解析工具(#test-utils,源码见 agent-eval/lib/test-utils.ts)能从多种会话日志形式(shell 命令、MCP 工具调用行等)中还原出结构化的工作流调用及输入参数,是评测判定"带参调用"的事实来源。
五、实操要点与常见误区
基于该评测及其周边源码,整理出使用test-run时的要点:
- 指定故事范围:通过
stories参数传入storyId(如example-button--primary格式),而不是全量运行,这在评测用例中被一致采用; - 显式传参:需要改变默认测试维度(如 a11y)时,必须把参数写进工具输入(
{ a11y: false })或 CLI 的--json载荷中;"默认不触发 a11y" 与 "显式a11y: false" 在评测判定中是两回事; - 结果汇报:PROMPT.md 要求"汇报哪些故事通过/失败",即 Agent 的职责不止于触发测试,还包括解析测试输出并给出逐故事结论;
- 前置条件:
test-run依赖一个可连接的 Storybook 服务(CLI 形态下通过--port指定,如--port 6006),工作区需像本评测的模板一样已配置好storybook/test所需依赖与tags: ['test']标记的故事。
六、小结
910-run-tests-without-a11y-explicit虽然指令只有三句话,却完整覆盖了 Agent 使用 Storybook 测试工具链的一条关键路径:定位目标故事 → 调用test-run→ 按指令显式注入a11y: false参数 → 汇报逐故事结果。配合 agent-eval/evals/910-run-tests-without-a11y-explicit 的断言实现与 agent-eval/lib/test-utils.ts 的调用解析机制,它既是理解 Storybook MCP 测试工具参数语义的范例,也是观察官方如何精确校验"显式参数调用"行为的参考实现。
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考