关于 record-browser-gif
record-browser-gif 是用于制作浏览器界面演示 GIF 的技能,面向需要提供可核验录屏的开发者。它涵盖隔离环境录制、编码与检查,并仅在有要求时将 GIF 附加到 Pull Request。
主要功能
- 01录制浏览器视频或截图
- 02编码并验证 GIF
- 03确保每个分镜来自单次隔离运行
- 04按需将 GIF 附加到 Pull Request
适用场景
- 01为 Pull Request 中的 GUI 改动制作演示
- 02创建本地界面演示 GIF
- 03发布前检查录制内容
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ deepseek-ai/ deepseek-harness --skill record-browser-gif安装后在 Claude Code 中输入/record-browser-gif确认已加载。
附带文件scripts/encode_gif.pyscripts/test_encode_gif.py
详细介绍
录制浏览器 GIF
生成一段简短、真实的 UI 演示,并保存为本地 GIF;只有当任务要求将其附加到拉取请求时,才在本技能末尾通过附加工作流发布。仍优先使用可用的浏览器控制工作流。当该工作流支持以更高帧率连续录制时,请使用 Playwright Videos;剪辑、调整播放速度、设置结尾停留时间、尺寸和文件大小时,请使用随附的编码器。
已归档的证据链决策记录了为何一个分镜应来自一次隔离运行,以及为何发布时要重新验证制品和演示所对应的拉取请求头部。
每个 GUI 拉取请求都必须包含 GIF
凡是会改变产品用户可见 GUI 行为的拉取请求,都必须包含使用本技能录制的演示 GIF,并通过附加工作流嵌入拉取请求正文。
录制本身就是证据:使用从该拉取请求的分支树启动的真实服务、真实 API 密钥和真实模型轮次。除非用户明确要求使用固定数据录制,否则绝不能用固定数据查询、模拟传输、合成事件注入或仅供测试的钩子替代。请在 GIF 嵌入内容旁注明演示所对应的准确提交 SHA、提供该内容的树和来源、任何模式标记或浏览器状态例外,以及是否运行了真实模型轮次,以便审核者确切了解录制内容所能证明的事项。
将录制与发布分开
- 录制只会生成本地视频或截图以及一个
.gif制品;绝不会修改远程状态。 - 发布是单独的最后一步:通过
gh --attach将 GIF 附加到拉取请求正文;若无法使用附加方式,则将其推送到 assets 分支并嵌入其 URL。只有任务要求将 GIF 附加到拉取请求时才执行发布。发布绝不会触及拉取请求自身的分支。 - 遵守指定的录制条件。真实服务或真实 API 演示不得使用固定数据查询、模拟传输、合成事件注入或仅供测试的钩子。如果凭据或服务不可用,请报告这一限制,不要改用固定数据。
- 绝不要读取或暴露凭据值。使用应用的常规配置路径和无害的演示提示词。
准备应用
针对特定拉取请求的 GIF 必须演示该拉取请求对应的树,因此应逐个拉取请求进行准备:
- 确保工作树干净,使用
git rev-parse HEAD记录其准确提交,然后构建该提交对应的树——此处使用pnpm run build && pnpm run build:web。GIF 若基于其他提交的构建录制,就会错误归属证据。 - 使用全新的临时
DSH_HOME、DSH_AGENTS_HOME、工作区和会话状态,为该树在每个端口上启动一个服务。同时为浏览器创建全新的隔离上下文或配置文件;如果浏览器工作流无法创建,请在导航前清除此来源的 Cookie 和站点存储,避免持久化的客户端状态影响证据。即使DEEPSEEK_API_KEY和根目录的.env不存在,~/.dsh/.credentials.yaml中仍可能有真实 API 凭据。使用临时DSH_HOME时,请使用录制覆盖配置,将dsh-credentials-local提供方的path设置为该现有文件的绝对路径。让应用的常规凭据提供方解析密钥;绝不要回显密钥。 - 将一个分镜视为一次完整的证据运行:所有已发布帧都必须来自同一服务、同一组状态根目录、同一工作区、同一会话以及同一次由模型驱动的场景运行。如果录制自动化失败,请丢弃已捕获的帧,并从全新的根目录重新运行;绝不要拼接来自不同运行的帧。
- 切换拉取请求时,按 PID 或精确匹配其命令行来停止旧服务。宽泛的
pkill -f模式可能会匹配并终止启动服务的 Shell,包括你自己的 Shell。
记录流程
遵循可用的浏览器控制工作流中的设置、交互和清理说明。当其提供 recordVideo 时,请在同一受控上下文中启用视频,以捕获更多中间帧。否则,请在该工作流中使用截图捕获;视频是否可用,不决定应使用哪种浏览器控制工作流。用户现有的浏览器状态仍是明确的隔离例外。
只有在无法使用浏览器控制时,才使用仓库声明的 Playwright 依赖,在隔离的无头浏览器中操作,并在捕获说明中注明此回退方案。在此仓库中,该依赖来自 apps/web/package.json;请勿安装其他驱动程序,也勿打开用户的浏览器。
录制前,请确定来源、构建或开发服务器、传输方式以及任何模式覆盖项。当生产环境默认设置会打开自动化无法操控的原生界面时,请通过常规应用配置选择官方支持、可通过浏览器操作的生产后端,并说明此覆盖项。
将脚本、原始视频、时间记录、QA 帧和 GIF 存放在仓库中被 git 忽略的 .playwright-mcp/ 目录下。先创建运行目录。
捕获视频
明确匹配 viewport 和 recordVideo.size:否则 Playwright 会将视频缩小以适应 800×800,可能导致界面文字难以辨认。
通过所选的浏览器控制工作流配置视频。独立的 Playwright 回退方案使用:
const { chromium } = createRequire(join(repo, 'apps/web/package.json'))('playwright')
const browser = await chromium.launch()
const size = { width: 1440, height: 900 }
const context = await browser.newContext({
viewport: size,
recordVideo: { dir: join(runDir, 'videos'), size },
})
try {
const page = await context.newPage()
const video = page.video()
// Navigate and exercise the real application here.
await context.close()
await video.saveAs(join(runDir, 'demo.webm'))
} finally {
await context.close()
await browser.close()
}
在录制脚本中从 node:module 导入 createRequire,并从 node:path 导入 join;将 repo 和一个新建的 runDir 设为绝对路径。关闭页面前,保留页面的视频句柄。在调用 video.saveAs() 或进行编码之前,请等待 context.close() 完成;仅关闭浏览器并不能保证视频已写入完成。每个页面都有自己的视频:请明确选择演示中使用的页面,不要拼接无关页面或不同运行的视频。失败的运行仅用于诊断。
选择一个包含三到六个有意义状态的简短演示流程。操作前等待唯一的语义定位器;对可访问名称匹配使用 exact: true,并使用不会匹配提示词回显的精确文本完成条件。状态确认后,可以固定等待一段时间以便阅读,但绝不能用固定等待来判断是否就绪。捕获视频时,请保留动画和滚动效果。
演示工具调用、拒绝或恢复过程时,请打开其详情或执行轨迹,让视频展示工具身份、状态或稳定的错误代码,以及后续结果。如果瞬时运行状态很重要,请提示模型执行较慢的前台操作,并观察其具体 DOM 标记;连续视频会捕获其间的帧。给模型一个简短的最终哨兵,以标记完成。真实 API 运行耗时过长时,只要演示状态已显示,就应停止运行。
不要捕获机密、个人数据、无关标签页或通知。浏览器视频只包含页面内容,不包含浏览器界面;避免在应用中显示包含凭据的 URL。检查所选时间段的全部内容,包括中间状态。全程保持同一视口。
编码 GIF
需要 python3、ffmpeg 和 ffprobe。如果缺少媒体二进制文件,应报告依赖项,不要未经授权安装软件。使用前,先单独一行导出 GIF_SKILL_DIR;内联赋值不会影响同一命令中的参数展开。
export GIF_SKILL_DIR=/absolute/path/to/this/skill
python3 "$GIF_SKILL_DIR/scripts/encode_gif.py" \
/absolute/path/to/demo.webm \
/absolute/path/to/demo.gif \
--start 2 --end 32 --speed 2 --final-hold 3 \
--fps 10 --max-width 1200 --colors 128
--start 和 --end 用于选择一个连续的源视频区间,单位为秒。默认设置会保留完整视频,播放速度为 1×,并在结尾停留两秒。--speed 会改变播放速度;请在 GIF 旁注明该设置和所选区间,以免演示被误认为是实测响应延迟。使用视频中观察到的时间,而非猜测的实际时间偏移,并保留所演示行为的完整起因和结果。结尾停留会重复显示最后一帧。--fps 用于设置编码后的 GIF 帧率;提高帧率无法恢复源录制中未捕获的运动。保留原始 WebM 以供 QA;不要拼接不同运行过程,也不要合成缺失状态。
编码器会探测 WebM 容器时长,在调色板转换前应用裁剪和速度设置,并检查编码后的时长、动画、宽度和字节大小。如果区间为空或超出范围、所选时长不足两个输出帧、标志不适用于当前模式,或会意外覆盖文件,编码器将拒绝执行。对于过大的文件,先降低 --max-width,再降低 --colors 或 --fps;同时保持文本清晰可读。只有在确认具体输出路径后,才使用 --force。
截图采集
如果无法获取连续视频,或用户要求制作分镜,请遵循可用的浏览器控制工作流。在一次独立运行中,使用浏览器的截图 API 捕获三到六个经过验证的状态。将返回的图像字节直接保存到同一个运行目录下,命名为 00-initial.png、01-typed.png 等;所有截图使用相同的尺寸和裁剪范围。对于瞬时状态,在同一次浏览器脚本调用中轮询其 DOM 标记并截图。
python3 "$GIF_SKILL_DIR/scripts/encode_gif.py" \
/absolute/path/to/frames /absolute/path/to/demo.gif \
--durations 1.5,1.5,1.5,3.5 --fps 10 --max-width 1200 --colors 128
一个时长值会应用于所有截图;否则,请为每帧提供一个正时长,并让最终稳定状态停留最久。目录输入少于两帧,或尺寸不一致、时长数量不匹配时,都会被拒绝。视频计时标志仅适用于视频文件;--durations 和 --pattern 仅适用于截图目录。
验证文件
- 阅读编码器的 JSON 摘要,并确认输出路径、源视频区间和速度(或截图数量)、编码帧数、尺寸、时长及字节大小。
- 检查编码后的 GIF 本身,而不只是源帧。确认过渡清晰易懂、最后一个状态停留时间足够,且没有出现敏感内容。如果查看器只渲染第一帧,请使用
ffmpeg从编码后的 GIF 中解码具有代表性的帧并检查;编码前的截图无法证明编码后的顺序、调色板或结尾停留效果。 - 运行
git status --short,确认原始视频、QA 帧和文件仅位于已忽略的路径下。 - 返回 GIF 的绝对路径;如果客户端支持本地媒体,则渲染该 GIF,并说明录制使用的是真实 API、fixture 还是其他传输方式。如果任务不要求将 GIF 附加到拉取请求中,到此为止。
编码器维护:安装媒体前置依赖后,运行 python3 -m unittest discover -s "$GIF_SKILL_DIR/scripts" -p 'test_*.py' -v。这些本地媒体测试不会在代码库的 CI 中运行。
发布 GIF
仅当任务要求将 GIF 附加到拉取请求时,才执行此步骤。
绝不要将 GIF 提交到拉取请求所在分支,或任何会合并到长期分支的分支:提交二进制媒体会增大代码库历史,影响今后的每次克隆。优先使用 gh --attach,它会将 GIF 上传到 GitHub,并通过一条命令重写正文中的引用,因此媒体不会保留在任何分支上。
使用 gh 附加
gh --attach 要求 gh 版本为 v2.99.0 或更高版本(gh --version)、仓库位于 github.com 上(不支持 GitHub Enterprise Server)、拥有仓库写入权限,且 GIF 不超过 10 MB。确认已验证的产物符合此限制;若超出,请先使用 --max-width 缩小,再调整 --colors 或 --fps,然后再附加。
将 GIF 写入正文文件,并将其作为普通的本地路径引用,使用与传给 --attach 相同的路径;gh 会原地将引用改写为上传后的 URL,同时保留其位置和替代文本:

通常以演示用拉取请求作为发布目标。工具拉取请求也可以嵌入来自另一个拉取请求的示例,但必须明确标注;在以下每项检查中,都要注明该源拉取请求并比对其当前 head。绝不要将该示例归于工具分支。
附加前,立即重新读取演示用拉取请求的当前 head;对于新建的演示用拉取请求,则读取已推送分支的最新提交,并将其与 GIF 旁记录的提交进行比对。若提交已变动,请停止并重新记录。然后进行附加:
gh pr create --body-file <body.md> --attach <path/to/demo.gif> # new pull request
gh pr edit <pr> --body-file <body.md> --attach <path/to/demo.gif> # existing pull request
--attach 可以重复使用,但会拒绝重复附加同一文件。若正文未引用某个 GIF,该 GIF 会追加到正文末尾,并使用标志中设置的替代文本(--attach '<path>#<alt text>');改写后的引用则会保留正文中的替代文本。附加后,重新读取演示用拉取请求的当前 head,并确认其仍为记录的提交。重新读取当前正文,确认引用现已指向上传后的 URL;通过 GitHub 的 Markdown API 渲染正文,并确认预期的 <img> 存在;然后请求一次上传后的 URL,确认返回 200 且内容类型为 image/gif。
回退到 assets 分支
仅当 gh --attach 无法使用时,才使用 assets 分支工作流:GIF 仍超过 10 MB、gh 版本低于 v2.99.0,或仓库不在 github.com 上。此时,GIF 存放在专用的孤立 assets 分支上——该分支没有父提交,且只包含媒体文件。一个 assets 分支服务于整个拉取请求系列(命名为 <series>-assets;使用 git ls-remote --heads origin '*assets*' 列出现有分支)。
使用以下任一工作流推送之前,请确认 assets 分支仅包含媒体文件,并确认暂存的 GIF 校验和与已验证的本地产物一致。
对于现有 assets 分支,请在浅克隆的单分支临时克隆中操作,以免发布操作影响工作树:
git clone --branch <assets-branch> --single-branch --depth 1 <repo-url> /tmp/assets-checkout
cp /absolute/path/to/demo.gif /tmp/assets-checkout/<name>.gif
cd /tmp/assets-checkout
git add <name>.gif
git commit -m "assets: <what it shows> gif (#<pr>)"
git push origin <assets-branch>
对于新系列,创建一个全新的浅克隆(git clone --depth 1 <repo-url> /tmp/assets-checkout),使用 git switch --orphan <assets-branch> 创建孤立分支,然后以相同方式添加 GIF、提交并推送。
推送后,使用经过身份验证的 GitHub API 或原始请求,确认远程路径、字节大小、校验和、200 响应和 image/gif 内容类型。匿名请求返回 404 并不能证明私有仓库中的资源不可用;请使用身份验证进行验证。这只能证明仓库成员的审查路径可用,并不代表公众可以访问。
编辑拉取请求正文之前,立即重新读取演示用拉取请求的当前 head,并将其与 GIF 旁记录的提交进行比对。若提交已变动,请停止并重新记录。编辑后,重新读取演示用拉取请求的当前 head,并确认其仍为记录的提交。另请通过 GitHub 的 Markdown API 渲染正文,并确认预期的 <img> 存在。
在拉取请求正文中使用原始 blob URL 嵌入 GIF;必须添加 ?raw=true 后缀,因为普通 blob URL 会显示 GitHub 的文件页面,而非图像:

绝不要删除或重写 assets 分支,也绝不要强制推送:已合并的拉取请求正文会永久引用其中的 URL。只能追加新提交。