mcpField
R
record-browser-gif

record-browser-gif

github.com/deepseek-ai创意与媒体264.8k74 次安装1 天前更新
代码仓库

关于 record-browser-gif

record-browser-gif 是用于制作浏览器界面演示 GIF 的技能,面向需要提供可核验录屏的开发者。它涵盖隔离环境录制、编码与检查,并仅在有要求时将 GIF 附加到 Pull Request。

主要功能

  1. 01录制浏览器视频或截图
  2. 02编码并验证 GIF
  3. 03确保每个分镜来自单次隔离运行
  4. 04按需将 GIF 附加到 Pull Request

适用场景

  1. 01为 Pull Request 中的 GUI 改动制作演示
  2. 02创建本地界面演示 GIF
  3. 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 必须演示该拉取请求对应的树,因此应逐个拉取请求进行准备:

  1. 确保工作树干净,使用 git rev-parse HEAD 记录其准确提交,然后构建该提交对应的树——此处使用 pnpm run build && pnpm run build:web。GIF 若基于其他提交的构建录制,就会错误归属证据。
  2. 使用全新的临时 DSH_HOME、DSH_AGENTS_HOME、工作区和会话状态,为该树在每个端口上启动一个服务。同时为浏览器创建全新的隔离上下文或配置文件;如果浏览器工作流无法创建,请在导航前清除此来源的 Cookie 和站点存储,避免持久化的客户端状态影响证据。即使 DEEPSEEK_API_KEY 和根目录的 .env 不存在,~/.dsh/.credentials.yaml 中仍可能有真实 API 凭据。使用临时 DSH_HOME 时,请使用录制覆盖配置,将 dsh-credentials-local 提供方的 path 设置为该现有文件的绝对路径。让应用的常规凭据提供方解析密钥;绝不要回显密钥。
  3. 将一个分镜视为一次完整的证据运行:所有已发布帧都必须来自同一服务、同一组状态根目录、同一工作区、同一会话以及同一次由模型驱动的场景运行。如果录制自动化失败,请丢弃已捕获的帧,并从全新的根目录重新运行;绝不要拼接来自不同运行的帧。
  4. 切换拉取请求时,按 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 仅适用于截图目录。

验证文件

  1. 阅读编码器的 JSON 摘要,并确认输出路径、源视频区间和速度(或截图数量)、编码帧数、尺寸、时长及字节大小。
  2. 检查编码后的 GIF 本身,而不只是源帧。确认过渡清晰易懂、最后一个状态停留时间足够,且没有出现敏感内容。如果查看器只渲染第一帧,请使用 ffmpeg 从编码后的 GIF 中解码具有代表性的帧并检查;编码前的截图无法证明编码后的顺序、调色板或结尾停留效果。
  3. 运行 git status --short,确认原始视频、QA 帧和文件仅位于已忽略的路径下。
  4. 返回 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,同时保留其位置和替代文本:

![<alt text>](<path/to/demo.gif>)

通常以演示用拉取请求作为发布目标。工具拉取请求也可以嵌入来自另一个拉取请求的示例,但必须明确标注;在以下每项检查中,都要注明该源拉取请求并比对其当前 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 的文件页面,而非图像:

![<alt text>](https://github.com/<owner>/<repo>/blob/<assets-branch>/<name>.gif?raw=true)

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

评论

支持 Markdown