
Metabase 可视化回归测试实战基于 Loki 与 Storybook 的 Visual Tests 完整指南【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase导读Visual Tests视觉回归测试是 Metabase 前端质量保障体系中不可或缺的一环用于捕捉组件和图表在代码改动后出现的“肉眼可见但单元测试无法发现”的样式回归。Metabase 选用 Loki 为骨架结合仓库中的 loki.config.js、package.json、.storybook/main.ts、.storybook/preview.tsx 以及 GitHub Actions 工作流系统讲解本地运行、CI 集成、新增测试与排障全流程读完即可在 Metabase 仓库中独立开展视觉回归测试。一、技术栈与运行原理1.1 为什么是 Loki StorybookMetabase 的前端体量巨大frontend/src/metabase下约 9800 个文件图表可视化frontend/src/metabase/visualizations等模块极易在重构、主题定制或样式调整时产生细微渲染差异。Loki 的设计思路是以 Storybook 为渲染容器用真实浏览器引擎截取页面快照再与历史参考图做像素级 diff。其核心工作流为Storybook 按 story 描述渲染组件含 mock 数据与全局样式装饰器Loki 通过 ChromeDocker 容器内逐故事截图输出到.loki/current与.loki/reference中的 PNG 参考图比对差异写入.loki/difference比对引擎与容差由 loki.config.js 控制。1.2 三个关键目录目录作用.loki/reference已批准的参考截图基线由 CI 或人工确认后维护.loki/current本次运行新截取的截图.loki/difference与参考图不一致的差异图用于人工复核从 frontend/test/generate-loki-report-json.js 的源码可以看到报告脚本正是分别读取reference、current、difference三个目录将其中的差异项写入.loki/report.json再交给reg-cli渲染成 HTML 报告。二、本地运行 Visual Tests2.1 前置条件在本地运行前需要同时满足Storybook 正在运行Loki 默认向本地 Storybook 实例发起截图请求Docker 正在运行Loki 使用chrome.docker作为截图目标见 loki.config.js 中chrome.laptop配置对target: chrome.docker的引用。2.2 常用命令仓库在 package.json 中预置了整套 npm scripts核心命令如下# 1. 本地运行视觉测试开发环境 NODE_ENVdevelopment bun run test-visual:loki # 2. 以 CI 方式运行先构建 Storybook 静态站再对静态站截图 bun run test-visual:loki:ci # 3. 将失败的截图.loki/difference复制为新的参考图更新基线 bun run test-visual:loki-approve-diff # 4. 仅生成 HTML 报告不重新运行测试 bun run test-visual:loki-report # 5. 先运行测试若失败则生成并自动打开 HTML 报告 bun run test-visual:loki-report-open各命令底层实现来自 package.jsontest-visual:loki: NODE_ENVdevelopment loki test --chromeFlags--headless --disable-gpu, test-visual:loki:ci: bun run build-storybook bun run test-visual:loki --reactUri file:./storybook-static --verboseRenderer, test-visual:loki-approve-diff: ls .loki/difference | xargs -I _ find .loki/current -name _ | xargs -I _ cp _ .loki/reference/, test-visual:loki-prune: ls .loki/reference | grep -v \$(ls .loki/current)\ | xargs -I {} rm .loki/reference/{}, test-visual:loki-report: node frontend/test/generate-loki-report-json.js reg-cli --from .loki/report.json --report .loki/report.html, test-visual:loki-report-open: bun run test-visual:loki || (echo Visual test failed, opening report... bun run test-visual:loki-report open-cli .loki/report.html)要点说明test-visual:loki使用开发环境构建NODE_ENVdevelopment并显式传入--headless --disable-gpu保证无头截图稳定test-visual:loki:ci是官方推荐的基线生成方式原文档特别提示本地运行得到的截图不要直接提交应使用 CI 变体生成参考图因为 CI 环境的字体、渲染栈更一致参考图更稳定test-visual:loki-approve-diff将差异图对应位置的当前截图覆盖到reference目录实现“一键批准”test-visual:loki-prune反向清理删除参考图中已不存在的多余基线报告最终落盘为.loki/report.html可离线打开审查。2.3 本地差异审查流程bun run test-visual:loki-report-open该命令先跑一轮测试若全部通过则无事发生若存在差异自动生成报告并调用open-cli在浏览器打开.loki/report.html即可在红绿对比中逐项确认是真实回归还是合理变更。三、CI 集成Pull Request 自动触发3.1 工作流概览视觉测试在 Pull Request 上自动触发核心工作流为 .github/workflows/loki.ymlfiles-changed阶段通过dorny/paths-filter依据 .github/file-paths.yaml 判断本次改动是否涉及前端源码、Loki 相关文件.github/workflows/loki.yml、.loki/**或前端 CI 基础设施visual-test 阶段仅在满足frontend_ci、frontend_sources或frontend_loki_ci任一条件时执行启动 Docker 服务容器docker:19.03.12privileged 模式供 Loki 的 Chrome 使用依次准备前端/后端环境、编译 CLJSNODE_ENVdevelopment bun run build-pure:cljs运行bun run test-visual:loki:ci失败时生成视觉报告并上传loki-reportartifact包含.loki/整个目录含隐藏文件。3.2 失败时如何查看差异当 PR 上出现 Loki Visual Regression Testing 检查失败时打开失败 Job 页面进入Summary区域下载loki-report构件解压后打开其中的report.html逐条比对difference目录中的差异截图。3.3 如何更新参考图批准差异若差异是有意为之如设计改版或偶发不稳定flake无需手动下载构件只需给 PR 打上loki-update标签CI 便会以当前截图更新参考基线。这是团队推荐的“批量批准”方式避免人工逐个复制差异图。3.4 智能裁剪受影响的 story 才会被跑值得注意的细节仓库通过 .github/scripts/create-test-plan.ts 和 .github/scripts/affected-tests.ts 构建“测试计划”。其中 Loki 相关的 story 列表来自frontend/**/*.stories.{js,jsx,ts,tsx}与enterprise/frontend/**/*.stories.{js,jsx,ts,tsx}见 create-test-plan.ts再结合依赖图dependency-cruiser与改动文件推断本次应执行的 Loki story 子集loki_stories_to_run并将结果输出供工作流消费。这意味着 CI 并非每次全量截图而是基于改动影响面做智能裁剪从而显著缩短反馈周期。四、新增 Visual Test写一个 story 即可4.1 最小实践新增视觉测试不需要额外测试代码本质就是新增 Storybook story。原文档明确指出当前视觉测试仅用于图表但任何 story 都可纳入。唯一要求是确保loki.config.js中的storiesFilter覆盖到目标 story。以仓库真实示例 BarChart.stories.tsx 为模板import type { StoryFn } from storybook/react; import { VisualizationWrapper } from __support__/storybook; import { NumberColumn, StringColumn } from __support__/visualizations; import Visualization from metabase/visualizations/components/Visualization; import { registerVisualization } from metabase/viz-core; import type { Series } from metabase-types/api; import { createMockCard } from metabase-types/api/mocks; import { BarChart } from ./BarChart; export default { title: viz/BarChart, component: BarChart, }; registerVisualization(BarChart); const MOCK_SERIES [ { card: createMockCard({ name: Card, display: bar }), data: { cols: [StringColumn({ name: Dimension }), NumberColumn({ name: Count })], rows: [[foo, 1], [bar, 2]], }, }, ] as Series; const DefaultTemplate: StoryFn () ( VisualizationWrapper Box h{500} Visualization rawSeries{MOCK_SERIES} width{500} / /Box /VisualizationWrapper ); export const Default { render: DefaultTemplate, parameters: { loki: { skip: true }, // 需要纳入 Loki 时移除该参数 }, };同时在 loki.config.js 的storiesFilter中加入对应的 story 标题模式例如^viz/BarChart该字段是一个以|连接的正则表达式数组代码中通过.join(|)合并支持前缀锚定与精确匹配。4.2 按需跳过loki: { skip: true }并非所有 story 都适合截图。仓库中BarChart的Default与Watermark两个 story 均设置了parameters.loki.skip true见 BarChart.stories.tsx原因通常是图表含动态动画/异步加载截图不稳定依赖用户交互态hover、滚动涉及外部字体、图片等非确定性渲染。此类 story 通过 Storybook 的parameters.loki字段在渲染侧被 Loki 跳过无需改动loki.config.js。4.3 让截图确定性的工程细节视觉测试最怕“时好时坏”。Metabase 在 Storybook 预览层做了大量确定性保障见 .storybook/preview.tsx去掉人为延迟window.METABASE_REMOVE_DELAYS true跳过 story 中的可跳过 delay同步加载 ECharts注释明确指出 EChartsRenderer 在应用中按需分包加载若不在 Storybook 中强制同步引入快照会拍到“懒加载骨架屏闪烁”字体预加载在预览加载时同步注入font-face并通过fontsReadyloader 等待所有字体load()完成。注释解释若不等待表格列宽自动计算依赖字体度量会在不同机器上产生不同结果导致截图不一致同步加载全部可视化组件通过loadVisualizationComponents()loader 确保图表组件注册完成避免截图时组件仍处于 Suspense 骨架状态。而 .storybook/main.ts 则支持环境变量STORYBOOK_STORIES_FILTER逗号分隔的 story 文件路径用于只构建指定的 story 文件——这是下方压力测试工作流的核心依赖。4.4 渲染到图片的场景如何配合 Loki部分 story 需要把图表导出为图片如 PDF/PNG 导出场景仓库提供了openImageBlobOnStorybook工具frontend/src/metabase/utils/loki-utils.ts它将导出的 blob 生成img挂到#storybook-root并添加data-testidimage-downloaded标记直到图片完全加载后才触发就绪信号从而保证 Loki 截图时画面上呈现的是完整导出的图片而非空白或半加载状态。该工具被 save-chart-image.ts 与 save-dashboard-pdf.ts 在 Storybook/Loki 环境下复用。五、合并前必做Loki Visual Stress Test5.1 为什么需要压力测试视觉测试天然受字体、GPU 渲染、时序影响单次通过不代表稳定。原文档明确要求合并 PR 前运行 Loki Visual Stress Test 工作流验证新增测试不 flaky。5.2 工作流用法工作流为 .github/workflows/loki-stress-test-flake-fix.yml支持两种触发方式PR 自动触发当 PR 改动frontend/**/*.stories.tsx或enterprise/frontend/**/*.stories.tsx时自动运行detect-changed-stories会调用 GitHub API 找出本次变更的 story 文件手动触发workflow_dispatch填写两个输入项——story_files相对于仓库根目录、逗号分隔的 story 文件路径必须匹配frontend/或enterprise/frontend/前缀且包含.stories.否则会校验报错burn_in重复运行次数例如10默认10。原文档提到的“运行 50 次”即通过burn_in输入实现。5.3 工作流内部逻辑其核心stress-test-lokiJob 展示了官方判定 flake 的标准流程用STORYBOOK_STORIES_FILTER环境变量只构建变更的 story 文件复用 .storybook/main.ts 的过滤逻辑循环seq 1 $BURN_IN每轮执行bun run test-visual:loki --reactUri file:./storybook-static --verboseRenderer注意set -o pipefail防止管道吞掉 Loki 的退出码注释明确说明tee成功后管道会误报成功单轮失败时把.loki/difference拷入.loki/failures/run-$i留存现场任何一轮失败都会导致整个 Job 失败并输出X out of N runs failed错误特殊情况若日志中出现No stories were found说明改动文件里的 story 全部被 Loki 跳过直接以成功退出。若压力测试通过率不达标说明 story 存在渲染不确定性应回到 4.3 节的确定性保障手段排查字体、异步、动画、懒加载等而不是直接放宽容差。六、配置详解loki.config.js.loki.config.js 是 Loki 行为的唯一事实来源当前仓库配置如下module.exports { diffingEngine: looks-same, storiesFilter: [ DataGrid, static-viz, viz, Patterns/Upsells, ^visualizations/shared, ^app/embed, ^design system, ^Components/Overlays/Menu Hover state, ^Components/Overlays/Popover Opened, ^Components/Overlays/Modal Opened, ^Components/Overlays/HoverCard Opened, ^Components/Utils/Paper Shadow matrix, ^Components/Data display/Card Shadow matrix, ^Components/Inputs/Checkbox (Overview|Checkbox\\.Card)$, ^Components/Inputs/DatePicker Dates range, ^Components/Inputs/Radio (Overview|Radio\\.Card)$, ^Components/Inputs/Switch (Overview|Switch\\.Group)$, ^Components/Parameters/DatePicker, ^Components/Buttons/Button Compact size, custom color, ^Components/Overlays/Tooltip, ^Components/Documents, ^Components/Feedback/Alert, ^Components/Feedback/Loader Overview, ^Components/Ask Before Using/Chip Overview, ^Components/Data display/Badge Sizes and variants, ^Components/Navigation/NavLink Overview, ^Components/Data display/KeyboardShortcut Overview, ^Components/Table, ^App/Palette, ^viz/GridMapPdfExport, ParameterValueWidget, ^Explorations/ExplorationGroupVisualization, ].join(|), configurations: { chrome.laptop: { target: chrome.docker, width: 1366, height: 768, deviceScaleFactor: 1, mobile: false, }, }, looks-same: { strict: false, antialiasingTolerance: 9, tolerance: 9, }, };逐项解读diffingEngine: looks-same选用looks-sameYandex 出品的像素比对库作为 diff 引擎storiesFilter正则数组^表示以某前缀开头的 story 标题如^viz/GridMapPdfExport未加锚的条目如DataGrid则按子串/前缀语义匹配。新增测试时修改此数组是最常见的操作同时注意 .storybook/preview.tsx 的注释story 名称变更可能影响 Loki 测试任何重命名都要同步更新storiesFilterconfigurations.chrome.laptop定义截图视口为 1366×768笔记本分辨率deviceScaleFactor: 1保证 1:1 像素输出target: chrome.docker说明实际渲染由 Docker 内的 Chrome 完成——这正是本地运行要求 Docker 的原因looks-same容差strict: false关闭严格模式antialiasingTolerance与tolerance均为 9允许亚像素级的抗锯齿差异存在从而容忍不同平台的字体渲染差异避免高频误报。容差参数的影响tolerance是像素级颜色差异阈值值越大越宽松。Metabase 设为 9 属于“相对严格但容忍抗锯齿”的折中antialiasingTolerance单独处理边缘像素的混色差异若你的图表频繁出现“时有时无”的失败优先排查渲染确定性而不是盲目调大tolerance否则会漏掉真实回归。七、常见问题与排障路径现象排查方向本地报错要求 Docker检查 Docker daemon 是否运行Loki 的chrome.dockertarget 依赖 DockerStorybook 未启动导致截图失败先启动 Storybook dev server或改用test-visual:loki:ci自建静态站本地通过但 CI 失败本地截图与 CI 渲染环境不一致按官方建议以 CI 生成的参考图为准勿提交本地截图新增 story 未被截图检查loki.config.js的storiesFilter是否覆盖该 story 标题测试偶发失败flake运行压力测试工作流定位检查字体预加载、动画/延迟、懒加载与异步图表渲染参考图过期story 已删用bun run test-visual:loki-prune清理多余基线有意改版需更新基线PR 打loki-update标签批量更新或本地test-visual:loki-approve-diff修改了 story 名称同步更新 loki.config.js 的storiesFilter否则截图对不上基线八、总结Metabase 的 Visual Tests 体系由三层构成渲染层Storybook 提供确定性的组件渲染环境字体预加载、同步图表、去延迟详见 .storybook/preview.tsx截图与比对层Loki 借助 Docker 内 Chrome 在 1366×768 视口下截图由 loki.config.js 的storiesFilter决定覆盖范围、looks-same容差决定敏感度CI 与流程层.github/workflows/loki.yml 在 PR 上自动执行并以loki-report构件交付差异报告loki-stress-test-flake-fix.yml 通过多轮重复运行如 50 次burn_in把 flake 扼杀在合并之前.github/scripts/create-test-plan.ts 则保证只运行受改动影响的 story兼顾覆盖与效率。对开发者而言日常涉及的三条黄金规则是本地只做快速验证、参考图一律以 CI 生成为准、合并前跑压力测试。遵循这套流程即可在保证 Metabase 图表与组件视觉一致性的同时把误报和 flake 控制在可接受范围内。关联阅读docs/developers-guide/visual-tests.md本文依据、frontend/test/generate-loki-report-json.js报告生成实现、.github/file-paths.yamlLoki 相关文件变更判定。【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考