让 AI 按独立状态生成组件测试故事
将 AI 生成的 Storybook 故事拆成可独立验证的交互或状态,避免用一个复杂故事冒充完整覆盖。空态、加载、错误和键盘焦点分别呈现,有助于明确视觉回归基线并暴露遗漏。
AI 深度解读
让 AI 给组件补 Storybook,最容易得到的是一个看起来什么都能切换的 Playground:默认、报错、加载、禁用,全塞进 Controls。作者遇到的问题是,评审点几下就合并了,后来 Playwright 却在没有独立呈现过的空状态上失败。
作者给出的办法很直接:每个 story 只承载一个有意义的状态或交互。重点不是增加 story 数量,而是让每个场景打开就能检查,失败时能准确定位。
一个 story 应该覆盖多大范围
作者说的「一个交互」,不是「一个 prop」,而是人可以单独验收的一件事。例如:
Default展示正常路径,不叠加额外状态。Empty展示零条数据或空白页面。Loading只检查骨架屏或加载指示器。Error展示可以恢复的失败提示。Disabled检查控件无法被激活。KeyboardFocus检查没有 hover 时,键盘焦点环是否可见。WithLongLabel检查长文本的截断或换行。
这些边界来自用户看到的行为,不必机械地按组件参数拆分。如果 hover 会改变布局或显示额外控件,也应该独立成 story,而不是藏在 Controls 的状态选择器里。
每个需要验收的场景,都应该在打开 story 时直接出现。
为什么 Controls 齐全,不等于覆盖齐全
作者认为,模型容易追求「看起来完整」:把每个 arg 都接到 Control,一个文件就显得很全面。但这种完整感会掩盖三个问题。
首先,视觉回归缺少清晰基线。同一个 story 能切换多个状态,截图到底代表哪一个?其次,评审者很少遍历所有 Control 组合。最后,复现问题还得附带操作步骤:「打开页面,再切到 Error」,交接就不够稳定。
作者希望失败场景能用「URL + story 名称」表达清楚。为此,他给 agent 的约束既包括拆分方式,也包括修改范围:
Add Storybook stories for `ShippingForm` in `ShippingForm.stories.tsx` only.
Rules:
- One story export per meaningful state/interaction (Default, Empty, Error, Loading, Disabled, KeyboardFocus).
- No mega-story with a `state` enum Control that switches everything.
- Prefer `args` / `render` per story over nested conditionals in one template.
- Each story must render without touching Controls.
- Max ~120 lines for the stories file. No new addons.
- Do not invent props the component doesn’t have — grep the component first.
这里最值得保留的是最后一行:先用 grep 查看组件,再生成 stories,避免 AI 为了凑齐状态而发明不存在的 props。约 120 行和不新增 addons,则是作者给这次生成任务设置的范围约束。
Controls 仍然有用途。作者允许把 theme、locale 这类确有需要的共享 Control 放在 meta 层,但不接受让每个 story 都变成一个状态切换器。
拆开之后,评审和截图才能指向同一个问题
作者会要求拆分这样的提交:只有一个 Playground,通过 Controls 切换 empty、error、loading;在 render 里写巨大的 switch (args.variant),重复组件已有分支;或者必须先拨动三个开关,才能看到目标场景。
把六套 UI 塞进同一画布的 AllStates,可以作为文档附录,但不该成为唯一覆盖。
共享初始化则没有问题。providers 可以通过 decorators 复用,play functions 可以只验证当前 story 的一项交互,例如通过 Tab 到达提交按钮,或检查错误文字。
在 Chromatic / Playwright 的 story 截图中,独立状态还能形成清晰的文件对应关系:ShippingForm--Empty.png、ShippingForm--Error.png。按作者的描述,修改空状态文案时应能直接看到 Empty 截图变化,修改正常路径时则看 Default,方便判断究竟影响了哪条分支。
我的判断
这条规则最有价值的地方,是把「AI 生成了多少配置」转成「评审能直接检查哪些行为」。相比依赖人工切换 Controls,独立命名的 stories 更适合交接问题,也更方便定位截图差异。
但这是作者的实践经验,原文没有给出失败率、评审耗时或维护成本的对照数据,不能据此断言拆分后一定能减少多少缺陷。
独立 story 也不自动等于测试完成。比如 Disabled 渲染出灰色按钮,还需要断言验证它确实无法激活。原文同样没有交代如何让 KeyboardFocus 稳定进入焦点状态,实际项目需要补齐对应操作。
约 120 行适合作为控制 AI 输出规模的提示,不宜直接变成所有组件的硬性标准。复杂表单需要更多准备代码时,应该先看能否复用初始化,再决定是否拆文件。
能拿来做什么
- 给 AI 补 stories 时,列出组件真实支持的状态,并明确要求每个导出无需操作 Controls 就能呈现目标场景。
- 评审现有 Playground 时,优先把空态、报错、加载和键盘焦点拆成独立 stories,让遗漏的分支更容易被发现。
- 已经使用 Chromatic 或 Playwright 截图的项目,可以检查每个重要状态是否都有明确的截图入口,避免只截到默认画面。
- 为需要验证的行为添加聚焦的
playfunctions,同时用 decorators 复用 providers,减少重复准备代码。