news 2026/9/17 14:40:41

Sanity Studio 视觉回归测试的 Storybook 基座:从 Chromatic 快照到 Vercel 部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sanity Studio 视觉回归测试的 Storybook 基座:从 Chromatic 快照到 Vercel 部署

Sanity Studio 视觉回归测试的 Storybook 基座:从 Chromatic 快照到 Vercel 部署

【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity

本篇技术指南聚焦 Sanity Studio 官方 monorepo 中的sanity-storybook包(dev/storybook),讲解它如何作为整个仓库的视觉回归测试基座:通过 Storybook 承载组件故事、交给 Chromatic 在每次 PR 上自动做像素级差异对比,并部署到 Vercel 提供稳定的浏览地址。读完本文,你将掌握这个 monorepo 中 Storybook 的命令体系、story 组织约定、浏览器级测试与故事之间的职责划分、Chromatic 工作流配置,以及 Vercel 部署的完整初始化步骤。

为什么 Sanity Studio 需要一套 Storybook

Sanity Studio 是一个大型 pnpm monorepo(pnpm-workspace.yaml),其 UI 层正在经历两场大规模的样式体系迁移:

  • styled-components → vanilla-extract:把运行时 CSS-in-JS 替换为编译期提取的样式方案;
  • @sanity/uiui5:升级底层 UI 组件库面。

这类迁移最危险的地方在于:逻辑可能完全正确,但一个像素的间距、颜色或圆角变化就会破坏整个 Studio 的视觉一致性。因此 dev/storybook/README.md 明确写道,sanity-storybook的核心使命就是用自动化的视觉 diff 守护这两场迁移——它被 Chromatic 在每次 PR 上截图快照,并部署到 Vercel 供团队审阅。

整个视觉回归工作流(包括如何新增覆盖)记录在 .agents/skills/sanity-visual-regression/SKILL.md 中,本文则聚焦于该包本身的工程设施。

命令速查:本地开发、构建与手动发布

dev/storybook/package.json 定义了四个脚本,分别对应四类使用场景:

# 从仓库根目录执行 pnpm dev:storybook # Storybook 开发服务器,地址 http://localhost:6006 pnpm build:storybook # 通过 turbo 做静态构建(产物输出到 dev/storybook/storybook-static) # 从 dev/storybook 目录执行 pnpm test # 用 vitest 浏览器模式运行每一个 story(基于 @storybook/addon-vitest) pnpm chromatic # 手动发布并截图快照(需要环境变量 CHROMATIC_PROJECT_TOKEN)

细节解读:

  • dev脚本对应storybook dev --port 6006 --no-open,固定端口 6006,且启动时不自动打开浏览器;
  • build对应storybook build,产物目录为storybook-static
  • test对应vitest run,其行为由 dev/storybook/vitest.config.mts 定义(下文详解);
  • chromatic对应 Chromatic CLI,可用于手动触发一次快照发布。

从根目录运行pnpm dev:storybookpnpm build:storybook,依赖的是根 package.json 中的对应 turbo 任务;pnpm build:storybook会经由 dev/storybook/turbo.json 声明dependsOn: ["^build"],保证上游 workspace 包先构建完成,再产出storybook-static/**

story 的组织约定:包拥有、就近放置

dev/storybook本身不存放任何 CSF 故事文件,它只负责 Storybook、Chromatic 和 addon-vitest 的基础设施。故事的归属原则是:

Stories are package-owned and co-located.每个 story 属于它覆盖的那个 workspace 包。

具体约定(依据 dev/storybook/README.md 与 .agents/skills/sanity-visual-regression/SKILL.md):

  1. Storybook 自动发现workspace 包src目录下的*.stories.tsx文件;
  2. story 与组件就近放置:通常把*.stories.tsx放在组件或 harness 所在的同一个__tests__目录里。真实示例见 packages/sanity/src/ui-components/button/tests/Button.stories.tsx 与 packages/sanity/src/ui-components/dialog/tests/Dialog.stories.tsx;
  3. 使用包内局部导入,不要跨 workspace 边界引用远端模块;
  4. 不要dev/storybook/stories/下新增 CSF 文件。

浏览器测试不重复导出为 story

这是一个容易踩坑的关键分工:vitest 浏览器模式套件(*.browser.test.tsx)本身就是独立的 Chromatic 快照来源@chromatic-com/vitest会在测试结束时自动归档其最终状态(对应 Chromatic 工作流中的vitest-visualjob)。因此:

  • 每个浏览器测试内联保留自己的 harness 组件function FooHarness()写在测试文件里),不再为它单独写 story;
  • 仓库中每一个*Story.tsx都是被某个*.stories.tsx引用的 Storybook harness;
  • story 只覆盖浏览器测试没有渲染到的状态
  • 禁止把浏览器测试重新导出成 story,也禁止为覆盖某个已由测试快照的状态而再写一个重复的 story——那会得到同一批像素的重复快照。

Playwright 留在 e2e/,不在 Storybook 里

e2e 套件有自己独立的 Chromatic 项目(e2e/studio-visual-test.ts,由.github/workflows/e2e.yml通过chromaui/action上传)。因此sanity-storybookplaywright依赖仅仅是@storybook/addon-vitest渲染 story 所用的浏览器 runner,该包内不承载任何 spec、fixture 或@chromatic-com/playwright接线。不要为了拿快照而把 e2e spec 改写成 story(或反过来)。

迁移哨兵故事(migration sentinels)

针对前面提到的两场样式迁移,组件本地的 story 专门覆盖测试无法捕获的视觉状态:

  • ui-components包装组件变体:即@sanity/uiui5的迁移表面,优先覆盖 card 与 tone 相关组件(tone 会级联影响所有组件);
  • 已完成 vanilla-extract 迁移的组件,如 change indicators、DocumentLayout,作为迁移哨兵。

需要 Studio 上下文(workspace/i18n/layers)的状态,则复用浏览器测试同款 mock studio 包装器TestWrapper(表单输入再加TestForm),它们来自 packages/sanity/test/browser,确定性、无网络,通过 story-only 的*Story.tsxharness 接入。交互后才会出现的浮层(tooltip、菜单等)则用play函数配合storybook/testuserEvent+waitFor/expect(...).toBeVisible()驱动,查询within(document.body)访问 portal 内容——Chromatic 和 addon-vitest 都会先跑play再截图,因此快照中能看到打开的浮层。

每个 story 都可浏览

所有故事都面向"被阅读"而编写,构成组件用法的活文档:不要用tags把 story 从侧边栏(!dev)或文档(!autodocs)隐藏起来。曾经用来隐藏 vitest 派生故事的!dev/!autodocs/vrt-only标签,已随那些故事一起消失——浏览器测试改为就地快照后,这里只保留"给人看"的故事。如果一个状态不值得人看,就不该进 Storybook,而应放进浏览器测试。只想保留可浏览性、不希望被快照的 story,设置parameters: {chromatic: {disableSnapshot: true}}即可。

构建配置:让 story 渲染得和真实 Studio 一模一样

dev/storybook/README.md 特别强调:.storybook/main.ts的 Vite 配置镜像了 packages/sanity/vitest.browser.config.mts,具体包含三部分:

  1. monorepoexports 条件:把 workspace 包解析到 TypeScript 源码,而不是已构建产物;
  2. vanilla-extract 插件:保证迁移后的组件样式正确编译;
  3. React Compiler transform:与 Studio 运行时保持一致。

这样 story 渲染效果与真实 Studio、与浏览器测试完全一致,快照才有意义。而 dev/storybook/vitest.config.mts 则补充了浏览器测试侧的细节:

  • 通过@storybook/addon-vitest/vitest-pluginstorybookTest()注册,configDir指向.storybook
  • storybookScript: 'pnpm dev'用于 vitest watch 模式把测试失败关联回 story UI;
  • 因为 story harness 会启动完整的 Studio 表单构建器,testTimeout放宽到 30 秒,retry: 1expect.poll超时 10 秒;
  • 浏览器用@vitest/browser-playwright,headless chromium,视口 1280×900——这与 .agents/skills/sanity-visual-regression/SKILL.md 中提到的.storybook/preview.tsx全局桌面模式视口一致,保证 story 与测试的截图尺寸统一。

注意该工程刻意没有注册进根 vitest.config.mts 的多项目运行——因为它需要真实浏览器,只能在pnpm --filter sanity-storybook test下单独运行。

确定性规则

视觉回归最怕随机性。skill 文档明确规定:harness story 靠 mock client/workspace 天然确定、无网络;绝不渲染实时时间戳、随机 id 或未完成的加载态;Chromatic 会自动暂停 CSS 动画。需要微调时使用parameters.chromatic旋钮:delay(截图前等待毫秒数,Portable Text 故事用 300ms 等待编辑器启动)、diffThresholddisableSnapshotmodes(视口/主题矩阵)。

Chromatic 集成:每次 PR 的自动快照

dev/storybook/chromatic.config.json 只有两个关键字段:

{ "$schema": "https://www.chromatic.com/config-file.schema.json", "buildScriptName": "build", "onlyChanged": true }
  • buildScriptName告诉 Chromatic 用build脚本构建 Storybook;
  • onlyChanged: true即启用TurboSnap——只对本次变更影响的 story 截图,大幅节省快照预算。

工作流:storybook 与 vitest-visual 双 job

.github/workflows/chromatic.yml 在pull_requestpushmain时触发,包含两个 job:

storybook(Storybook visual tests)

  • actions/checkout@v7使用fetch-depth: 0拉取完整 git 历史,这是 Chromatic 找基线构建、追溯 TurboSnap 变更文件的前提;
  • checkout 用 PR 分支而非 GitHub 的合并提交,保证 Chromatic 能定位它对比的 commits;
  • 先用detect-code-changes判断是否有变更,有变更才执行pnpm build构建全部包,再运行chromaui/action,token 用CHROMATIC_PROJECT_TOKEN_STORYBOOKworkingDir: dev/storybook
  • exitZeroOnChanges: true:烧录期(burn-in)内检查不阻断合并,差异只作为报告;autoAcceptChanges: main:合并到main时自动接受新基线。

vitest-visual(Vitest browser visual tests)

  • 同样拉全量历史,额外做 Playwright 浏览器版本探测、缓存与安装(playwright install-deps chromium/playwright install chromium);
  • 设置CHROMATIC=1SANITY_VITEST_BROWSER=chromium后运行pnpm --filter sanity test:browser,把每个*.browser.test.tsx的结束态归档;
  • 校验packages/sanity/.vitest/chromatic/preview-stats.json存在(TurboSnap 需要它);
  • 再经chromaui/actionvitest: true模式上传到独立的 "sanity studio vitest" 项目(token:CHROMATIC_PROJECT_TOKEN_VITEST)。

两个快照来源、两个 Chromatic 项目、各自独立的 token——skill 文档强调"一个集成类型对应一个项目",不要把某一来源的输出上传到另一个项目的 job。此外还有一个受控的第三来源:Playwright e2e 的takeSnapshot(),上传到 "sanity studio playwright" 项目(token:CHROMATIC_PROJECT_TOKEN_E2E),由于 e2e 跑在每 PR 的 staging 数据集上(实时时间戳、presence、并发写入),全局自动快照只会产生纯 diff 噪声,因此 e2e/studio-test.ts 全局禁用自动快照,仅在确定性时刻按 spec 显式takeSnapshot()接入。

浏览器测试内的快照控制

*.browser.test.tsx内部,@chromatic-com/vitest提供三种粒度:

  • 自动快照:每个测试结束自动归档,Chromatic 中的命名是describe 链 / it 标题 / Snapshot #n
  • 按作用域退出configure({disableAutoSnapshot: true}),在文件顶层调用作用于整个文件,在describe()内作用于该套件,在test()内仅作用于该测试——适合纯交互检查或仅清理的状态;
  • 定向快照await takeSnapshot('状态名'),抓取测试中途经过但不结束于的状态(如关闭前的菜单、拖拽中途),必须await,未 await 的调用会导致测试失败。

两个 helper 在普通运行中都是 no-op(firefox/webkit 上亦然),只有CHROMATIC=1才真正开启捕获;普通运行只会在被 gitignore 的.vitest/chromatic下留下显式takeSnapshot()的归档。

Vercel 部署:稳定的浏览地址与 PR 预览

Storybook 部署到sanity-sandboxVercel 团队,项目名studio-storybook,生产地址为https://studio-storybook.sanity.dev,并通过 Git 集成自动为每个 PR 生成预览部署。

一次性项目初始化(维护者从仓库根目录执行)

vercel是根 devDependency,初始化分四步(依据 dev/storybook/README.md):

# 1. 认证(一次性) pnpm vercel login # 2. 创建项目 + 设置 Root Directory + 首次预览部署(交互式): # - Set up and deploy? 选 yes # - Scope: sanity-sandbox # - Project name: studio-storybook # - "Code directory?" -> 填 ./dev/storybook # - Vercel 会把框架误判为 Vite;没关系,vercel.json 已钉死 # buildCommand/outputDirectory 并覆盖这一误判 # - 按提示连接检测到的 Git 仓库(origin) # - monorepo 超出 Vercel 1.5 万文件的上传上限,因此必须加 --archive=tgz pnpm vercel --scope sanity-sandbox --archive=tgz # 3. 验证一次生产部署 pnpm vercel --prod --scope sanity-sandbox --archive=tgz # 4. 把生产域名指向项目 pnpm vercel domains add studio-storybook.sanity.dev studio-storybook --scope sanity-sandbox

几个关键注意点:

  • 首次部署的交互式提示正是把 Root Directory(dev/storybook)持久化到项目上的时机;setup 阶段连接 Git 仓库即可自动获得 PR 预览与main生产部署,无需再单独执行vercel git connect
  • dev/storybook/vercel.json 钉死了构建方式:cd ../.. && pnpm exec turbo run build --filter=sanity-storybook,输出目录storybook-static,并加了 SPA 路由回退(/(.*)/index.html)。这样上游 workspace 包会先被构建,Vercel 误判的框架预设也就无关紧要了;
  • Git 集成构建会从根目录克隆 monorepo 并安装整个 pnpm workspace(保证workspace:*catalog:协议可解析),行为与test-studio-preview-iframe项目一致;
  • 在该包合入main之前,推送到main触发的生产部署会因缺 Root Directory 而失败——这是 PR 合并前可预期的噪声;
  • .vercel/链接元数据与其他 dev 应用一样被 gitignore;
  • 可选(仅仪表盘设置):把项目的 Ignored Build Step 设为npx turbo-ignore sanity-storybook,让不影响 Storybook 的提交跳过部署;
  • Chromatic 会给每个发布的 Storybook 构建生成永久链接,因此 Vercel 部署承担的是稳定 URL + PR 预览的职责,两者互补。

小结:一整套"人机分流"的视觉回归分工

回看整个设计,sanity-storybook的价值不在于"多一个 Storybook",而在于它用清晰的职责划分把三类快照来源组织成互补体系:

状态来源归属快照项目
仅凭 props/fixtures 或一次play交互可达*.stories.tsx(包内就近放置)sanity studio
需要驱动 UI(输入、拖拽、剪贴板、视口变化)*.browser.test.tsx(结束态就地快照)sanity studio vitest
完整 Studio 外观 + 真实部署与数据集Playwright spec(e2e/studio-visual-test.ts,只读状态)sanity studio playwright

dev/storybook只做基建:stories 全部由各 workspace 包"包拥有"并就近存放,浏览器测试就地快照,Playwright 留在 e2e/,而 Storybook 只保留"给人看"的组件状态文档。配合 Chromatic 的 TurboSnap 与双项目工作流,Sanity Studio 得以在 styled-components → vanilla-extract 和@sanity/ui→ ui5 两场迁移中持续获得像素级的回归保护。

【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 14:40:33

MongoDB聚合管道:查询统计与性能优化实战

第一次被聚合管道教做人,是在一个"订单看板"的需求上。当时订单集合里也就七八万条数据,我用了最朴素的做法:find({status: "paid"})把全部订单捞回应用层,然后用一个 for 循环累加出总额、订单数&#xff0c…

作者头像 李华
网站建设 2026/9/17 14:38:46

MCP 服务里的 DeepSeek 请求走 TaoToken,uv 天气查询流程不用改

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 14:38:17

Notepad-- 文件对比:文本逐行高亮差异,10M 内二进制也能比

Notepad-- 文件对比:文本逐行高亮差异,10M 内二进制也能比 【免费下载链接】notepad-- 一个支持windows/linux/mac的文本编辑器,目标是做中国人自己的编辑器,来自中国。 项目地址: https://gitcode.com/GitHub_Trending/no/note…

作者头像 李华
网站建设 2026/9/17 14:37:26

自然连接⋈的真相:不是自动匹配,而是隐式多条件陷阱

1. 项目概述:为什么“自然连接”是数据库里最常被误解、也最该被吃透的操作?“土话笔记:数据库——自然连接(符号⋈)”这个标题,乍看像学生课后随手记的潦草笔记,但恰恰是这种带点烟火气的命名,戳中了数据库…

作者头像 李华