录制演示视频
编写一个临时的 Playwright 分镜脚本,然后从任何工作目录运行自包含引擎。
除非用户明确要求,否则不要将分镜脚本添加到应用仓库中。
保持分镜脚本自包含;运行器会在 Playwright 加载之前将该文件暂存到引擎内部,因此不要使用分镜脚本相对路径的帮助导入。
<skill-dir> 表示克隆此仓库的目录,例如 ~/.claude/skills/record-demo。
编写分镜脚本
使用以下可移植导入来引入 DSL:
import { demo } from "record-demo/storyboard";
运行器在暂存时会将此说明符重写为引擎的实际路径,因此分镜脚本文件可以位于任何位置。
编辑器对 record-demo/storyboard 的类型解析来自 engine/tsconfig.json 中的 paths——在仓库外部,请将 record-demo/storyboard 映射到你的 tsconfig paths 中的绝对 <skill-dir>/engine/lib/storyboard.ts 路径,或者将文件放在 engine/scripts/ 中。
使用 engine/scripts/example-site.demo.ts 作为参考。优先使用 getByRole 选择器,使用 d.click(locator) 而不是 locator.click(),使用 d.hold(...) 来控制动作节奏,并断言或等待最终状态。
demo("storyboard-title", async (d) => {
await d.seed(async (page) => {
await page.getByRole("heading", { name: "Example Domain" }).waitFor();
});
await d.step("Read the page", async (page) => {
const heading = page.getByRole("heading", { name: "Example Domain" });
await d.zoomTo(heading, { padding: 48 });
await d.hold(2_000);
d.zoomOut();
await heading.waitFor({ state: "visible" });
});
});
引擎会使用 --name 或脚本的基本名称覆盖时间轴名称,因此传递给 demo(...) 的字符串不必与输出别名匹配。
预检(在任何捕获之前)
当针对本地服务的检出进行录制时:在服务的检出目录中运行 git fetch origin 并断言 HEAD == origin/main(或用户指定的分支)——过时的检出差错会录制过时的设计。如果过时,请先重建/重启服务器。将服务的 SHA 打印到录制笔记中,以便每个录制都可追溯。
录制
需要 Node >= 22.18(使用 nvm 或系统),ffmpeg,Xvfb,和 xdpyinfo。在 engine/ 内部使用 pnpm install 安装一次依赖。
node <skill-dir>/engine/record.mjs \
--script /absolute/or/cwd-relative/demo.demo.ts \
--url https://example.org \
[--name output-slug] [--formats 16x9,1x1,9x16] \
[--chrome none|macos|browser] [--storage-state <path>] [--raw]
使用 --formats 选择一个或多个构图预设(默认为 16x9),使用 --chrome 选择渲染的窗口框架(默认为 none)。格式按请求顺序渲染,第一个格式将用于编码 GIF。
9x16 格式是一种激进的中心裁剪(在广角镜头中,页面宽度约有 44% 可见),因此在为竖屏创作时请保持动作居中。
使用 --raw 在干净捕获后停止。不使用该标志,预期会得到以下交付物:
engine/out/<name>-raw.mp4- 每个请求格式的
engine/out/<name>-<format>.mp4 - 从主要格式编码的
engine/out/<name>.gif
运行器在完成时会打印绝对交付物路径。将现场站点可重现性视为目标站点的责任,并且绝不要在分镜脚本中嵌入真实凭据。
认证站点
仅使用临时或测试账户录制已认证的流程;切勿使用生产凭据。智能体应首先使用自己的工具,通过一个无头 Playwright 脚本登录一次。如果登录需要通过电子邮件发送的 OTP 或魔法链接,首选途径是智能体自己的邮件访问——Claude Code 上的连接 Gmail 连接器/MCP,或其他运行时的邮件 CLI——以便它自己读取验证码;否则请用户粘贴。登录后,使用 await context.storageState({ path: storageStatePath }) 保存会话,其中 storageStatePath 是一个一次性临时路径,创建为 storageStatePath="$(mktemp -d)/<account>.storage-state.json"。然后使用 --storage-state "$storageStatePath" 运行录制命令。
Playwright 的 storageState 仅恢复 cookies 和 localStorage。在 IndexedDB 或 sessionStorage 中保存认证的应用会静默录制到一个已登出的屏幕,因此在捕获之前验证已认证的页面。storage-state 文件是一个秘密:切勿打印或提交它。录制器在加载后会立即删除它;这是一次性交接,因此重新录制需要重新登录,并且磁盘上不会留下任何与认证相关的内容。一次性的登录辅助脚本也包含凭据:保持其临时性,切勿提交。
在 Remotion Studio 中编辑录制的演示
录制后导出一个自包含的项目,然后安装并打开 Studio:
node <skill-dir>/engine/scripts/export-project.mjs <name>
cd <skill-dir>/engine/out/<name>-project
pnpm install
pnpm studio
Studio 的时间轴面板显示编辑器风格轨道(屏幕底片,每个字幕一个命名片段,聚光灯,缩放移动,和片尾)——拖动播放头进行浏览,点击片段的行以查找其事件。使用属性面板或 fixtures/timeline.json 编辑字幕、缩放时间(保持缩放移动不低于 1650ms 的下限)、聚光灯时间和片尾标题。编辑 remotion/theme.ts 来设置颜色、排版、间距和运动。使用 pnpm render 渲染最终的 16:9 视频。
运动和分镜规则(是什么让一个片段可发布)
- 每个场景一次相机移动,而不是每个字段。 放大到该区域一次,然后在其内部操作。每次输入的缩放读起来像生硬的剪辑。
- 在页面过渡时保持广角,在内容稀疏的页面上保持广角。 在导航的点击之前调用
d.zoomOut();绝不要在几乎空白的页面上放大到一个状态药丸。 - 靠近页面底部的部分:
d.zoomTo([...locators], { scroll: "center", padding: 48 })——相机夹子无法居中页面边缘的矩形;滚动居中在根级别解决了这个问题。Locator[]通过联合边界框框定整个部分。 d.spotlight([...locators])= Apple 风格的焦点变暗(抠图 + 环)。使用慷慨的holdMs,在步骤结束前显式调用d.spotlightOff()+hold(600)结束。聚光灯矩形必须适合视口(强制执行);缩放矩形可以自由溢出。- Seed 等待真实数据,而不仅仅是触发元素——否则视频在骨架屏上打开。等待一个已知的内容节点 + 约 600ms。
- 在结果字幕之前调用
d.settle(locator)。 提交后重新获取数据的视图会短暂空白;简单的waitFor会在首次渲染时解析,结果字幕会在空白页面之上触发。 - 字幕是祈使句,与光标动作相关联;在关闭场景上将结果字幕(
d.caption(...)在步骤后期)放置;修剪步骤字幕,以便祈使句永远不会悬停在已完成的操作上。 d.outro(title)关闭可发布的片段,在最后一个场景之后以平静的结束画面结束。- 始终断言最终状态——没有它,管线会渲染出一个错误流程的精美视频。
- 切勿发布带有 localhost URL、内部主机名、提供商名称或屏幕上真实凭据的片段。在 seed 中屏蔽仅用于演示的字符串(使用 window.open 补丁和 MutationObserver 输入重写)。
在报告完成前验证
- 在
engine/中运行pnpm typecheck && pnpm test。 node scripts/score-motion.mjs out/timeline.json——FAIL 阻塞;WARN 缩放速率约 0.78/s 是接受的稳定状态;任何不连续性判定都是相机错误;死区 WARN 意味着静态拉伸 >= 3.5 秒且光标移动少于 40px——重新调整速度或添加字幕。- 引擎更改后运行
node scripts/prove-composition.mjs。 - 观看实际渲染:
ffmpeg -i out/<name>-16x9.mp4 -vf fps=1 out/frames/%03d.png并读取帧。缩放裁剪、字幕延迟、抠图不同步和屏幕上的开发 URL 在绿色测试运行中是不可见的。
注意事项
| 症状 | 原因 / 修复 |
|---|---|
Invalid demo viewport ... 中止 | 几何体偏移——修复启动标志,绝不要放宽断言。 |
| 光标垂直偏移约 174px | Chrome 以选项卡模式运行,而不是 --app。 |
| 找不到场记板帧 | 检测器容差为 64(外部捕获平均不纯);如果仍然错过,检查捕获是否为黑色(Chrome 在错误的 DISPLAY 上)。 |
| 字幕/光标在片段中漂移 | 场记板同步——不要使用墙上时钟时间相减。 |
| 聚光灯抠图偏离目标 | 矩形与平滑滚动竞速——measureTarget 的稳定性循环应能保持;如果复发,增加其超时时间。 |
| 裁剪边缘处单词被从中切掉 | 对于模态框,使用 zoomTo(locator, { padding: 120 })。 |
| 缩放将内容裁剪出帧 | maximumScale 品味问题(1.45 有效),不是夹子错误。 |
验证引擎更改
从 engine/ 运行:
pnpm typecheck && pnpm test
node scripts/prove-composition.mjs