27、屏幕共享测试:单元测试、端到端测试、模拟屏幕流、自动化测试框架

屏幕共享这个功能,写起来挺爽,测起来是真头疼。

我记得第一次给客户演示屏幕共享,结果一共享就黑屏,当时那个尴尬啊……后来我学乖了,测试必须前置,而且得分层测。今天我就把这几年的测试心得掰开揉碎讲给你听。

为什么屏幕共享测试这么特殊?

说白了,屏幕共享涉及的东西太多了。采集、编码、传输、解码、渲染,哪一环出问题都不行。而且它不像普通视频通话,屏幕内容变化快、分辨率高、帧率要求也高。

你想想看,普通摄像头30帧就够了,屏幕共享有时候得60帧,不然鼠标移动都卡顿。这就对测试提出了更高的要求。

核心观点:屏幕共享测试必须分层进行,从最小的单元测试,到集成测试,再到端到端测试,每一层都不能跳过。

单元测试:把每个零件单独测

单元测试,就是测试最小的代码单元。比如一个编码器初始化函数、一个分辨率切换逻辑、一个帧率计算器。

我个人习惯用 Jest 配合 sinon 来做。为什么?因为屏幕共享涉及很多浏览器 API,比如 getDisplayMedia,这些在 Node 环境里没有,必须 mock 掉。

// 测试分辨率切换逻辑
describe('ResolutionManager', () => {
  it('should switch to lower resolution when CPU overload', () => {
    const manager = new ResolutionManager();
    manager.setCurrentCPUUsage(85); // 模拟CPU高负载
    const result = manager.getTargetResolution();
    expect(result.width).toBe(1280); // 期望降级到720p
    expect(result.height).toBe(720);
  });

  it('should keep high resolution when idle', () => {
    const manager = new ResolutionManager();
    manager.setCurrentCPUUsage(30);
    const result = manager.getTargetResolution();
    expect(result.width).toBe(1920);
  });
});

我的经验:单元测试里最难测的是时间相关的逻辑。比如帧率控制、超时重连。我一般会用 sinon 的 fake timers 来模拟时间流逝,这样测试跑起来飞快,不用真的等几秒钟。

模拟屏幕流:没有真屏幕也能测

做自动化测试的时候,总不能每次都开个真屏幕吧?那太慢了。我们需要模拟屏幕流。

我常用的方案有两种:

  • Canvas 模拟流:用 Canvas 画一些动态内容,比如移动的方块、变化的数字,然后通过 captureStream() 转成 MediaStream。
  • 预录视频模拟:用一段预录的屏幕录制视频,通过 HTMLVideoElement 播放并 captureStream。
// 用 Canvas 模拟屏幕流
function createMockScreenStream(width = 1920, height = 1080) {
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');

  // 画一个移动的方块,模拟屏幕变化
  let x = 0;
  function drawFrame() {
    ctx.clearRect(0, 0, width, height);
    ctx.fillStyle = 'blue';
    ctx.fillRect(x, 100, 200, 200);
    x = (x + 5) % width;
    requestAnimationFrame(drawFrame);
  }
  drawFrame();

  return canvas.captureStream(30); // 30fps
}

注意:Canvas 模拟流虽然方便,但它没有真实的屏幕内容变化模式。比如鼠标移动、滚动页面这些细节,Canvas 模拟不出来。所以它适合做性能测试和稳定性测试,不适合做画质测试。

端到端测试:模拟真实用户操作

端到端测试,就是模拟两个用户真实地建立连接、共享屏幕、接收屏幕流。这个环节我推荐用 Puppeteer 或者 Playwright。

为什么?因为它们可以控制真实的 Chrome 浏览器,能调用 getDisplayMedia。不过有个坑——getDisplayMedia 会弹出系统级的选择窗口,这个 Puppeteer 默认搞不定。

我的解决方案是这样的:

  1. 在测试环境中,用 Chrome 的 --auto-select-desktop-capture-source 启动参数,自动选择屏幕源。
  2. 或者,在代码里加一个测试开关,当检测到测试环境时,用模拟流替代真实采集。
// Playwright 端到端测试示例
test('screen sharing should work end-to-end', async ({ browser }) => {
  const context1 = await browser.newContext();
  const context2 = await browser.newContext();

  const page1 = await context1.newPage();
  const page2 = await context2.newPage();

  // 页面1发起屏幕共享
  await page1.goto('https://your-app.com/room/123');
  await page1.click('#start-sharing');
  
  // 页面2加入同一房间
  await page2.goto('https://your-app.com/room/123');
  
  // 验证页面2收到了屏幕流
  const videoElement = await page2.waitForSelector('#remote-screen video');
  const hasVideo = await videoElement.evaluate(el => el.srcObject !== null);
  expect(hasVideo).toBe(true);
});

自动化测试框架:把测试跑起来

单元测试、模拟流、端到端测试都写好了,怎么把它们组织起来?我建议用这样的分层架构:

屏幕共享自动化测试分层架构 单元测试层 Jest + sinon · 测试编码器、分辨率管理、帧率控制等独立模块 模拟流测试层 Canvas captureStream · 预录视频 · 模拟不同分辨率和帧率场景 端到端测试层 Playwright / Puppeteer · 真实浏览器 · 模拟用户共享与接收 每层测试覆盖不同维度,层层递进,确保屏幕共享功能万无一失

这个架构的好处是:

  • 单元测试跑得最快,每次提交代码都能跑,几分钟就出结果。
  • 模拟流测试稍慢,但不需要真实屏幕,可以在 CI 环境里跑。
  • 端到端测试最慢,但最接近真实场景,适合在合并代码前跑一次。

我的建议:在 CI 里,把单元测试和模拟流测试设为必须通过的门禁。端到端测试可以设为定时任务,比如每天凌晨跑一次。这样既保证了质量,又不会拖慢开发节奏。

避坑指南:我曾经踩过的坑

做屏幕共享测试这几年,我踩过不少坑,挑几个典型的说说:

  • 分辨率不匹配:我曾经遇到过,发送端采集的是 4K 分辨率,接收端解码后显示不出来。后来发现是接收端的解码器不支持 4K。解决方案是在测试里加入分辨率协商的验证。
  • 帧率波动:屏幕共享的帧率波动比摄像头大得多。我见过从 60 帧突然掉到 5 帧的情况。后来我在测试里加入了帧率监控,低于阈值就报警。
  • 内存泄漏:屏幕共享如果长时间运行,内存会不断增长。我写过一个测试,让屏幕共享跑 24 小时,监控内存变化。结果发现每 10 分钟泄漏 2MB,跑一天就泄漏了 288MB。

特别提醒:屏幕共享测试一定要考虑多显示器场景。很多用户有双屏甚至三屏,共享其中一个屏幕时,采集逻辑和坐标计算都可能出问题。我建议在测试矩阵里加入多显示器模拟。

总结一下

屏幕共享测试,说白了就是三个字:分层测。单元测试保基础,模拟流测试保性能,端到端测试保体验。每一层都有它的价值,缺一不可。

我个人习惯把测试用例写成表格,方便追踪覆盖情况:

测试类型 覆盖内容 运行频率 工具
单元测试 编码器、分辨率管理、帧率控制 每次提交 Jest + sinon
模拟流测试 不同分辨率、帧率、内容变化模式 每次提交 Canvas captureStream
端到端测试 真实共享、接收、渲染流程 每日定时 Playwright
压力测试 长时间运行、多用户并发 每周一次 自定义脚本

嗯,这套测试体系我用了两年多,帮我在上线前抓到了不少问题。希望你也能用上,少走一些弯路。

公众号:蓝海资料掘金营,微信deep3321