如何在Playwright测试中切换多个视频作为模拟摄像头
在Playwright单测试中动态切换模拟摄像头视频文件
Chrome的--use-file-for-fake-video-capture是浏览器启动时生效的参数,无法在运行过程中动态修改。要实现单测试内切换模拟摄像头视频,有两种可行方案:
方案1:使用新的浏览器上下文(Context)
每个Playwright上下文可以独立配置启动参数,完成第一个视频的测试步骤后,关闭当前上下文,创建一个使用新视频参数的上下文继续测试,可通过storageState继承登录状态。
修改后的测试代码:
import { expect, test } from '@playwright/test'; import { getFakeVideoOptions } from './utils'; test.describe('Test Suite', () => { test.describe('Test Scenario', () => { test('Normal Case', async ({ browser }) => { // 第一个上下文:使用video1 const context1 = await browser.newContext({ ...getFakeVideoOptions('/path/to/video1.mjpeg'), storageState: './auth.json' }); const page1 = await context1.newPage(); await page1.goto('https://example.com'); await context1.grantPermissions(['camera']); await page1.getByRole('button', { name: 'Button 1' }).click(); await expect(page1.getByText('Message 1')).toBeVisible(); // 保存当前登录状态,用于新上下文复用 const storageState = await context1.storageState(); await context1.close(); // 第二个上下文:使用video2 const context2 = await browser.newContext({ ...getFakeVideoOptions('/path/to/video2.mjpeg'), storageState: storageState }); const page2 = await context2.newPage(); await page2.goto('https://example.com'); await context2.grantPermissions(['camera']); await page2.getByRole('button', { name: 'Button 2' }).click(); await expect(page2.getByText('Message 2')).toBeVisible(); await context2.close(); }); }); });
方案2:动态注入自定义视频流(无需切换上下文)
通过Playwright的page.evaluate直接替换页面的摄像头媒体流,无需重启上下文,适合需要保持页面状态的场景。
第一步:定义注入视频流的辅助函数
async function injectFakeVideo(page, videoPath) { await page.evaluate(async (path) => { // 创建隐藏视频元素加载目标视频 const video = document.createElement('video'); video.src = path; video.muted = true; video.loop = true; await video.play(); // 创建Canvas捕获视频帧 const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); // 定期绘制视频帧到Canvas setInterval(() => { ctx.drawImage(video, 0, 0); }, 1000/30); // 保持30fps帧率 // 捕获Canvas的媒体流 const stream = canvas.captureStream(30); // 替换原生getUserMedia方法,返回自定义流 const originalGetUserMedia = navigator.mediaDevices.getUserMedia; navigator.mediaDevices.getUserMedia = async (constraints) => { if (constraints.video) return stream; return originalGetUserMedia(constraints); }; }, videoPath); }
第二步:修改测试代码使用该函数
import { expect, test } from '@playwright/test'; test.use({ storageState: './auth.json' }); test.use({ args: ['--use-fake-ui-for-media-stream'], // 跳过摄像头授权弹窗 }); test.describe('Test Suite', () => { test.describe('Test Scenario', () => { test('Normal Case', async ({ page, context }) => { await page.goto('https://example.com'); await context.grantPermissions(['camera']); // 注入第一个视频 await injectFakeVideo(page, '/path/to/video1.mjpeg'); await page.getByRole('button', { name: 'Button 1' }).click(); await expect(page.getByText('Message 1')).toBeVisible(); // 切换注入第二个视频 await injectFakeVideo(page, '/path/to/video2.mjpeg'); await page.getByRole('button', { name: 'Button 2' }).click(); await expect(page.getByText('Message 2')).toBeVisible(); }); }); });
方案对比
- 方案1:依赖浏览器原生模拟,兼容性好,但需要切换上下文,可能需重新加载页面。
- 方案2:无需切换上下文,保持页面状态,但需自行处理视频加载和流注入,需确保视频路径可被页面访问(可使用绝对路径或
page.route映射本地文件)。
内容的提问来源于stack exchange,提问作者Ken Watanabe
相关产品推荐
相关产品推荐

