Remotion web-renderer 视觉快照测试实战:为 Web 端视频渲染器新增测试用例
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
本文围绕 Remotion 仓库中 web-renderer 的视觉快照(visual snapshot)测试体系展开:介绍packages/web-renderer测试套件的运行方式、fixture 与测试文件的配对结构、testImage像素对比工具的实现细节,以及新增一条测试用例的完整五步流程。读完本文,你能够照着仓库源码独立地为 Web 渲染器补充针对某个 CSS 属性或已知 Issue 的回归测试,并同步更新官方限制文档。
测试体系在哪里、怎么跑
Web 渲染器的源码位于packages/web-renderer,测试套件位于packages/web-renderer/src/test。这套测试使用 vitest 的浏览器模式做视觉快照对比——渲染结果不比对 DOM 或数值,而是直接比对渲染出来的图像像素。
运行单个测试文件的方式:
bunx vitest src/test/video.test.tsx在 package.json 中可以看到两个相关脚本:
"testwebrenderer": "vitest src/test --browser --run", "studio": "cd ../example && bunx remotion studio ../web-renderer/src/test/studio.ts --public-dir=../example-videos/videos"testwebrenderer会一次性以--browser --run模式跑完整个测试目录;studio脚本则复用测试目录中的入口文件启动 Remotion Studio,方便人工预览 fixture。
从源码结构看,测试目录按功能属性命名,每个 CSS 特性或回归场景一个文件,例如 background-color.test.tsx、border-radius.test.tsx、clip-path.test.tsx、text.test.tsx,此外还有大量以issue-前缀命名的复现用例(如 issue-9901-rotated-drop-shadow.test.tsx),用于钉住历史 bug 不再复现。
每个测试由一个 fixture 驱动
测试目录下的fixtures/子目录存放各测试的"画面定义",例如 fixtures/background-color.tsx。一个 fixture 是一个自包含的 React 组件加一组渲染参数:
import {AbsoluteFill} from 'remotion'; const Component: React.FC = () => { return ( <AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center', }} > <div style={{ backgroundColor: 'red', width: 100, height: 100, borderRadius: 20, }} /> </AbsoluteFill> ); }; export const backgroundColor = { component: Component, id: 'background-color', width: 200, height: 200, fps: 25, durationInFrames: 1, } as const;注意几点结构约定:
- 导出的是一个
as const对象,字段同时满足两个用途:作为合成(composition)配置喂给渲染器,以及作为<Composition>的 props 用于 Studio 预览; - 快照测试通常
durationInFrames: 1,只需渲染第 0 帧即可验证静态样式; id既是合成 id,也是快照图片的文件名标识。
从 fixtures 目录的文件命名可以推断,当前覆盖的视觉特性面相当宽:border-radius-*(椭圆、百分比、钳制、嵌套 overflow-hidden 等十余种变体)、clip-path-*(polygon/circle/ellipse/inset/path 五种)、transforms/子目录(scale/rotate/translate/orthographic 及各类 shorthand 组合)、text/子目录(letter-spacing、text-decoration、webkit-text-stroke 等),以及过渡效果的transition-wipe、transition-clock-wipe、transition-iris。
对应的测试长什么样
fixture 与测试文件一一对应。以下测试调用 Web 渲染器渲染单帧,再用testImage与基线截图比对:
import {test} from 'vitest'; import {renderStillOnWeb} from '../render-still-on-web'; import {backgroundColor} from './fixtures/background-color'; import {testImage} from './utils'; test('should render background-color', async () => { const blob = await renderStillOnWeb({ licenseKey: 'free-license', composition: backgroundColor, frame: 0, inputProps: {}, imageFormat: 'png', }); await testImage({blob, testId: 'background-color'}); });需要说明的是,以上是技能文档中的示例写法;当前仓库中的 background-color.test.tsx 实际写法是先调用renderStillOnWeb拿到渲染结果,再通过其.blob({format: 'png'})取出 PNG Blob,并且额外导入了../symbol-dispose用于资源清理:
const blob = await ( await renderStillOnWeb({ licenseKey: 'free-license', composition: backgroundColor, frame: 0, inputProps: {}, }) ).blob({format: 'png'}); await testImage({blob, testId: 'background-color'});也就是说,向renderStillOnWeb(实现位于 render-still-on-web.tsx)传参时,composition直接复用 fixture 导出的对象,frame: 0指定渲染第 0 帧。
testImage:像素对比工具的源码细节
utils.ts 中的testImage是整个快照体系的比对核心:
export const testImage = async ({ blob, testId, threshold = 0.15, allowedMismatchedPixelRatio = 0.001, }: { blob: Blob; testId: string; threshold?: number; allowedMismatchedPixelRatio?: number; }) => { const img = document.createElement('img'); img.src = URL.createObjectURL(blob); img.dataset.testid = testId; document.body.appendChild(img); // ... 等待 img.onload 后: await expect(page.getByTestId(testId)).toMatchScreenshot(testId, { comparatorOptions: {threshold, allowedMismatchedPixelRatio}, }); };工作机制可以拆成三步:
- 将渲染产物 Blob 通过
URL.createObjectURL转成<img>挂到测试页面的 DOM 上,并用data-testid标记; - 通过 vitest browser 模式的
page.getByTestId(testId)定位该元素; - 调用
toMatchScreenshot与基线截图做像素比对,基线图片存放在src/test/__screenshots__/目录。
两个默认容差参数值得注意:threshold默认 0.15,控制单像素颜色差异的容忍度;allowedMismatchedPixelRatio默认 0.001,即允许最多 0.1% 的像素不一致。这个比例容差用于吸收字体渲染、抗锯齿在不同环境下的细微抖动。对噪声更大的 fixture(如文字排版、SVG 渐变),测试可以在调用testImage时覆盖这两个值来调宽容差。
新增一条测试的完整流程
按技能文档的约定,为 Web 渲染器新增测试需要五步:
- 添加 fixture:在
packages/web-renderer/src/test/fixtures新建文件,按backgroundColor示例的结构导出组件与component/id/width/height/fps/durationInFrames字段; - 注册预览(重要):把 fixture 导入并添加进 Root.tsx。该文件以
<Composition {...fixture} />的形式列出全部 fixture,并用<Folder>分组(Opacity、Text、border、clip-path、Transitions、Projects 等),配合bun run studio脚本可以在 Studio 中逐个人工核对画面。漏注册 fixture 会导致无法可视化预览; - 添加测试文件:在
packages/web-renderer/src/test新建.test.tsx,套用上面"调用renderStillOnWeb+testImage"的模板; - 运行测试:
bunx vitest src/test/video.test.tsx(按你的测试文件名替换)。首次运行时 vitest 会生成基线截图,之后每次运行都与基线比对,出现回归即测试失败; - 同步文档(重要):更新 packages/docs/docs/client-side-rendering/limitations.mdx,把新支持的 CSS 属性标记为 supported。
为什么限制文档和测试必须同步
第 5 步的必要性来自 Web 渲染器的工作原理。limitations.mdx 中说明:与服务端渲染的整屏截图不同,客户端渲染是在 canvas 上模拟布局与样式,"not feasible to support all CSS properties",因此只支持最重要的样式原语;文档同时指出浏览器必须支持 WebCodecs API。
该文档按类别列出支持矩阵,例如:
- 定位与布局类(
margin、left、display、width、height、flex)受支持,因为 Remotion 用getBoundingClientRect()获取元素位置与尺寸; overflow、object-fit受支持,object-position不受支持(内容始终居中);- 变换类中
transform、transform-origin、opacity、scale/rotate/translate、backface-visibility受支持,而perspective、perspective-origin、transform-style不受支持; - 背景类中
background-color、线性渐变、background-size/background-position(渐变场景)受支持,其他background-image取值不受支持; - 边框类中
border、border-radius(含横竖不同半径)、outline受支持,corner-shape不受支持。
正因为"受支持"是一组由测试用例逐条钉住的承诺,每当 Web 渲染器新增支持一个 CSS 属性,就应该:新增 fixture + 测试固化该行为,同时在 limitations.mdx 中把对应属性从红色(不支持)改为绿色(支持)。反之,limitations.mdx 中标注为"supported"的每条声明,理论上都能在src/test下找到对应的快照用例。从 fixtures 中border-radius-*的十余个变体文件和多个issue-*回归文件可以推断,团队正是以这种"每个边界条件一个 fixture"的粒度来维护这张支持矩阵的。
小结
web-renderer 的测试体系可以概括为三层:fixtures/提供最小可渲染画面,*.test.tsx调用renderStillOnWeb生成第 0 帧图像,testImage用toMatchScreenshot做带容差的像素级快照比对;Root.tsx则把同一批 fixture 复用到 Studio 预览中。新增测试时遵循"fixture → 注册 Root.tsx → 写测试 → 跑 vitest → 更新 limitations.mdx"的流程,就能让 Web 端渲染能力的每一次扩展都同时获得自动化回归保护与文档层面的对外声明。
【免费下载链接】remotion🎥 Make videos programmatically with React项目地址: https://gitcode.com/GitHub_Trending/re/remotion
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考