You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 10:13:12