Playwright Video 类详解recordVideo 之下的视频录制 API 与 ffmpeg 编码管线【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright当使用recordVideo选项创建浏览器上下文后Playwright 会为每个页面自动关联一个Video对象用于获取、保存或删除该页面录制的视频文件。本文以 Playwright 官方 API 文档 class-video.md 为主体完整讲解Video类的path()、saveAs()、delete()三个方法及其在多语言下的调用方式并结合仓库中 客户端实现 与 服务端录制器 的源码深入剖析视频从屏幕截帧到.webm文件落盘的完整链路。读完本文你可以掌握如何在测试与自动化脚本中可靠地收集、归档测试视频并理解path()在远程连接下抛错、saveAs()等待语义背后的实现原因。一、Video 类概述什么时候会存在 Video 对象Video类自 v1.8 起可用。官方文档的核心描述是当浏览器上下文以recordVideo选项创建时每个页面都会有一个与之关联的 video 对象。四语言下的标准用法是console.log(await page.video().path());System.out.println(page.video().path());# async print(await page.video.path()) # sync print(page.video.path())Console.WriteLine(await page.Video.GetPathAsync());需要注意的是page.video()并非总是返回对象。从 page.ts 的客户端实现看Page在构造时依据协议initializer.video是否下发来决定是否创建Video实例initializer.video存在时执行new Video(this._connection, Artifact.from(initializer.video))而video()方法在未关联视频时返回null。这一点也被测试明确验证video.spec.ts 中在未配置录制的上下文里断言expect(page.video()).toBeNull()。换句话说Video对象的存在性完全由上下文的recordVideo配置驱动。启用录制的标准写法参见 videos.md是// JS 库模式通过 newContext 传入 recordVideo const context await browser.newContext({ recordVideo: { dir: videos/ } }); // 务必 await close视频才会真正落盘 await context.close();// Java context browser.newContext(new Browser.NewContextOptions().setRecordVideoDir(Paths.get(videos/))); context.close();# Pythonasync / sync 写法一致仅 await 差异 context await browser.new_context(record_video_dirvideos/) await context.close()// C# var context await browser.NewContextAsync(new() { RecordVideoDir videos/ }); await context.CloseAsync();官方文档特别强调了一条语义视频在浏览器上下文关闭时才被保存。如果你手动创建了上下文一定要 awaitbrowserContext.close()否则拿不到完整的视频文件。这条规则直接解释了Video.path()/saveAs()的诸多等待行为。二、async method: Video.pathsince: v1.8官方定义返回该视频将被写入的文件系统路径。视频保证在关闭浏览器上下文时被写入文件系统。远程连接时该方法会抛错。多语言调用const path await page.video().path();String path page.video().path();# async path await page.video.path() # sync path page.video.path()var path await page.Video.PathAsync();2.1 参数与返回值项说明返回path字符串视频将要写入的绝对文件系统路径写入时机浏览器上下文关闭时保证写入完成异常远程连接如connect()到远端 Playwright Server时抛出错误录制尚未启动时抛出Video recording has not been started.2.2 源码印证为什么远程连接会抛错client/video.ts 中path()的实现非常直白async path(): Promisestring { if (this._isRemote) throw new Error(Path is not available when connecting remotely. Use saveAs() to save a local copy.); if (!this._artifact) throw new Error(Video recording has not been started.); return this._artifact._initializer.absolutePath; }三个关键事实可以确认_isRemote在构造函数中取自connection.isRemote()即只要客户端是通过connect()等方式远程连接path()一律抛错并明确提示请改用saveAs()保存本地副本。这是因为路径指向的是远端服务器的文件系统对本地进程没有意义path()返回的是_artifact._initializer.absolutePath——即录制启动时就已分配好的目标路径。这与Video类文档返回视频将will be写入的路径的措辞一致文件句柄在上下文关闭时才真正完成写入但路径在开始时就是确定的未启用录制时_artifact为空调用会抛出Video recording has not been started.。测试 video.spec.ts 中对path()的覆盖相当充分包括recordVideo.dir指定目录、默认artifactsDir、多页面 / popup 各自持有独立Video对象popup.video()等场景。三、async method: Video.saveAssince: v1.11官方定义JS/Python 异步将视频保存到用户指定的路径。即使视频仍在录制中或页面已关闭调用该方法都是安全的。该方法会等待页面关闭且视频完整保存后才返回。await page.video().saveAs(test-results/my-video.webm);参数只有一个pathstring视频应保存到的目标路径。3.1 各语言的语义差异重要原文档对saveAs按语言分别给出说明这一点容易被忽略Java同步 API必须在page.close()或browserContext.close()之后调用否则抛出错误。调用后会等待视频完整保存。page.close(); page.video().saveAs(Paths.get(my-video.webm));Python 同步 API同样必须在page.close()/context.close()之后调用否则抛错Python 异步 API 则与 JS 一致录制进行中或页面关闭后调用都是安全的方法会等待页面关闭且视频完整保存。JS / C# / Python 异步 API录制进行中或页面关闭后均可安全调用Promise 会在页面关闭且视频完全写盘后 resolve。这种差异的根源在于同步 API 无法在调用线程上等待异步的上下文关闭事件因此把先关闭作为前置约束而异步 API 可以把等待关闭 等待落盘折叠进 Promise 中。3.2 实现链路saveAs 委托给 Artifact从 client/video.ts 看saveAs本身极薄async saveAs(path: string): Promisevoid { if (!this._artifact) throw new Error(Video recording has not been started.); return await this._artifact.saveAs(path); }它把等待与拷贝逻辑全部委托给Artifactartifact.ts。从源码结构看Artifact在录制开始时即注册为目标产物并在服务端完成reportFinished后把文件流式传回客户端写入用户指定路径——这正对应文档中等待页面关闭且视频完整保存的语义。测试 video.spec.ts 中有should saveAs video用例验证saveAs后目标文件确实存在expect(fs.existsSync(saveAsPath)).toBeTruthy()同时也有在录制尚未完成/页面已关闭等边界条件下调用saveAs的断言。对 CI 场景的典型用法是远端执行测试后用saveAs()把视频拉回本地供 HTML Reporter 或制品归档使用——这正是path()在远程模式下抛错时给出的官方替代方案。四、async method: Video.deletesince: v1.11官方定义删除视频文件。如视频仍在录制会先等待视频录制结束再删除。await page.video().delete();page.video().delete();await page.video.delete() # async / sync 写法一致await page.Video.DeleteAsync();从客户端实现看delete()对未启动的录制是静默的_artifact为空时直接返回否则透传给this._artifact.delete()async delete(): Promisevoid { if (this._artifact) await this._artifact.delete(); }等待视频结束再删除的保证由Artifact服务端逻辑提供。测试中对 delete 的覆盖包括先持有delete()的 Promise、再关闭上下文确认文件最终被移除。delete()适合在确认不需要该视频时主动清理磁盘避免artifactsDir中视频文件堆积。五、服务端纵深VideoRecorder 与 ffmpeg 编码管线Video对象背后的真实工作发生在服务端。videoRecorder.ts 中可以看到完整实现几个关键事实如下5.1 启动时机录制先于页面恢复startAutomaticVideoRecording(page)在上下文配置了recordVideo时被调用它读取recordVideo.dir缺省回退到browser.options.artifactsDir以page.guid .webm作为文件名创建Artifact并赋给page.video。源码注释明确指出顺序约束必须先启动视频录制器再发送 Screencast.startScreencast之后再 Target.resume保证首帧不丢失。showActions选项则通过page.screencast.showActions(...)注入元素高亮标注。5.2 ffmpeg 进程与编码参数FfmpegVideoRecorder通过registry.findExecutable(ffmpeg)找到随 Playwright 分发的 ffmpeg 可执行文件并以stdin管道方式向其喂帧。关键常量与参数videoRecorder.ts 及_launch内的参数列表帧率固定fps 25-r 25ffmpeg 基于输入时间戳自动复制帧容器与编码WebM VP8-c:v vp8恒定质量模式-crf 8质量区间-qmin 0 -qmax 50码率-b:v 1M低延迟与稳定性-deadline realtime -speed 8不过度占用 CPU 以跟上帧率、-threads 1CPU 超订时显著降低卡顿、-an无音频输入帧被封装进最小 Matroska 流见 ebml.ts 的writeHeader/writeClusterHeader每帧携带显式时间戳让 ffmpeg 直接读取帧时序-f matroska -i pipe:0 -fpsprobesize 0 -probesize 32 -analyzeduration 0尺寸适配默认用pad${w}:${h}:0:0:gray,crop${w}:${h}:0:0滤镜把视口帧居中裁剪/补边到目标尺寸——这解释了文档中视口画面被放在输出视频左上角、必要时等比缩小的行为输出文件必须以.webm结尾构造函数中assert校验视频元数据写入creation_time特殊处理若整个会话没有任何帧_lastFrame为空停止时会写入一帧白图保证 ffmpeg 产出非空文件停止时还会补一帧尾帧并追加至少 1 秒时长避免视频在编码器缓冲区还有数据时戛然而止。5.3 尺寸规则默认 800x800 缩放start()中videoSize options.size ?? sizescreencast 实际尺寸注释写明对视频文件无论实际像素数据如何优先编码为指定尺寸。对应 types.d.ts 中recordVideo.size的文档未指定时尺寸等于viewport缩小到 800x800 以内若viewport未显式配置视频尺寸默认为 800x450。recordVideo.dir未指定时视频存入artifactsDir即browserType.launch()选项这也与Video.path()返回路径在录制启动时即确定的实现一致。六、配置参数速查recordVideo 选项汇总 videos.md 与类型定义 types.d.ts 中的recordVideo配置browser.newContext()选项选项类型 / 取值默认值说明dirstringartifactsDirlaunch 选项视频保存目录size{ width, height }视口缩放到 800x800 以内未配置视口时为 800x450视频帧尺寸页面画面必要时等比缩小showActions{ duration, position, fontSize, cursor }关闭交互元素视觉标注duration默认 500msposition默认top-rightfontSize默认 24cursor默认pointerPlaywright Test 场景下则由配置文件的use.video控制off/on/retain-on-failure/on-first-retry视频文件默认出现在test-results测试输出目录多页面场景通过page.video()即本文的Video类拿到与页面绑定的视频对象// 多页面场景每个 page含 popup有独立 video 对象 const path await page.video().path(); // 注意视频仅在页面或浏览器上下文关闭后才可用七、最佳实践小结务必 awaitcontext.close()视频在上下文关闭时才保证写盘这是path()/saveAs()/delete()一切等待语义的前提本地用path()远程用saveAs()path()在connect()远程模式下必然抛错官方推荐路径是saveAs()拉回本地副本Java / Python 同步 API 的saveAs()必须放在 close 之后异步 API 则任意时机调用均可安全等待不需要视频时调用delete()它会等待录制结束后删除文件且不要求录制一定已启动未启动时静默返回控制成本可调size尺寸直接决定 VP8 编码开销与文件体积源码中的-deadline realtime -speed 8 -threads 1表明 Playwright 已针对实时性做了保守调参但仍建议为批量测试设置合理分辨率。参考文件API 文档、视频录制指南、客户端 Video 实现、服务端 VideoRecorder、Artifact 实现、类型定义、视频测试。【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考