23、自动化测试:使用 Puppeteer 编写 WebRTC 自动化测试脚本,模拟多用户连接
说实话,WebRTC 的调试和测试,一直是很多开发者的痛点。
我自己带团队做实时音视频项目时,最头疼的就是「多人同时连麦」的场景。手动开十几个浏览器窗口?太累了。而且每次测试结果还不一致,你根本分不清是代码 bug 还是操作失误。
所以,自动化测试必须上。今天我们就聊聊怎么用 Puppeteer 来搞定这件事。
为什么选 Puppeteer?
Puppeteer 是 Google 官方维护的 Node.js 库,可以控制 Chrome 或 Chromium 浏览器。说白了,它能帮你「假装」是一个真实用户,打开页面、点击按钮、获取视频流。
对于 WebRTC 来说,Puppeteer 有几个天然优势:
- 原生支持 WebRTC:Chrome 内核本身就集成了 WebRTC,不需要额外安装插件。
- 可以模拟多页面:一个 Puppeteer 实例可以打开多个页面,每个页面就是一个「虚拟用户」。
- 能获取统计信息:通过
getStats()API,我们可以拿到实时的网络质量数据。
我的经验: 早期我试过用 Selenium 做 WebRTC 测试,但 Selenium 对 WebRTC 的支持不够好,尤其是获取媒体流的时候经常报错。换成 Puppeteer 后,问题少了很多。
核心思路:模拟多用户连接
我们模拟的场景是这样的:
- 启动一个信令服务器(比如用 Socket.IO 搭建的)。
- 用 Puppeteer 打开多个浏览器页面,每个页面都连接到同一个房间。
- 每个页面创建本地媒体流(可以用假视频流,避免依赖摄像头)。
- 触发信令交换,建立 PeerConnection。
- 验证连接是否成功,以及媒体流是否正常传输。
嗯,这里有个关键点:假视频流。你想想看,如果每次测试都要摄像头,那自动化就没法在服务器上跑了。所以我们要用 Chrome 的 --use-fake-device-for-media-stream 参数。
避坑指南: 我曾经在 CI 环境里跑测试,发现视频流一直黑屏。后来排查了半天,才发现是没加
--use-fake-ui-for-media-stream 参数。这个参数可以自动授权媒体权限,否则 Puppeteer 会卡在权限弹窗上。
代码实战:一个简单的多用户测试脚本
下面是一个完整的示例。我会逐步解释每一段的作用。
const puppeteer = require('puppeteer');
async function createUser(browser, roomId, userId) {
const page = await browser.newPage();
// 注入假媒体流
await page.goto('http://localhost:3000/room/' + roomId);
// 等待页面加载完成
await page.waitForSelector('#local-video');
// 模拟用户点击「加入房间」
await page.click('#join-btn');
// 等待远程视频出现
await page.waitForSelector('#remote-video', { timeout: 10000 });
console.log(`用户 ${userId} 已加入房间`);
return page;
}
async function runTest() {
const browser = await puppeteer.launch({
headless: true,
args: [
'--use-fake-device-for-media-stream',
'--use-fake-ui-for-media-stream',
'--no-sandbox'
]
});
const roomId = 'test-room-001';
// 创建 3 个虚拟用户
const user1 = await createUser(browser, roomId, 'Alice');
const user2 = await createUser(browser, roomId, 'Bob');
const user3 = await createUser(browser, roomId, 'Charlie');
// 等待几秒,让连接稳定
await new Promise(resolve => setTimeout(resolve, 5000));
// 检查每个用户的连接状态
const stats1 = await user1.evaluate(() => {
const pc = window.localPeerConnection;
return pc.getStats().then(stats => {
let result = {};
stats.forEach(report => {
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
result.connected = true;
result.rtt = report.currentRoundTripTime;
}
});
return result;
});
});
console.log('Alice 的连接状态:', stats1);
await browser.close();
}
runTest();
关键点解析
| 参数/方法 | 作用 | 我的建议 |
|---|---|---|
--use-fake-device-for-media-stream |
使用假摄像头和假麦克风 | 必加,否则在无头模式下会报错 |
--use-fake-ui-for-media-stream |
自动授权媒体权限 | 必加,否则弹窗会卡住脚本 |
page.evaluate() |
在浏览器上下文中执行 JS | 用来获取 WebRTC 内部状态 |
waitForSelector() |
等待 DOM 元素出现 | 比固定延时更可靠 |
SVG 流程图:多用户连接测试的核心逻辑
进阶技巧:处理 ICE 连接状态
在实际项目中,我发现很多自动化测试脚本只检查「视频是否显示」,但忽略了 ICE 连接状态。这其实不够。
我建议你在测试中加入以下检查:
// 检查 ICE 连接状态
const iceState = await page.evaluate(() => {
const pc = window.localPeerConnection;
return pc.iceConnectionState;
});
if (iceState !== 'connected' && iceState !== 'completed') {
console.error(`ICE 连接异常: ${iceState}`);
// 可以在这里截图保存现场
await page.screenshot({ path: `ice-error-${userId}.png` });
}
注意: 在无头模式下,
page.screenshot() 可能截不到视频流内容(因为 GPU 渲染不同)。如果你需要验证视频画面,建议使用 --headless=new 模式,或者直接检查 getStats() 中的 framesReceived 字段。
模拟网络条件变化
你想想看,真实场景下网络不会一直稳定。所以我们的自动化测试也要能模拟弱网环境。
Puppeteer 可以通过 Chrome DevTools Protocol 来模拟网络限速:
const client = await page.target().createCDPSession();
await client.send('Network.emulateNetworkConditions', {
offline: false,
latency: 200, // 延迟 200ms
downloadThroughput: 500 * 1024, // 下载 500kbps
uploadThroughput: 300 * 1024 // 上传 300kbps
});
我个人习惯在测试脚本中准备几组预设:
- 正常网络:延迟 10ms,带宽 10Mbps
- 弱网:延迟 200ms,带宽 500kbps
- 极差网络:延迟 500ms,带宽 100kbps
然后循环测试,看看 WebRTC 的码率自适应机制是否正常工作。
常见问题与解决方案
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 页面打开后视频黑屏 | 没有正确注入假媒体流 | 检查 --use-fake-device-for-media-stream 参数 |
| ICE 连接一直卡在 checking | 信令服务器没有正确转发 | 在 Puppeteer 中打印信令日志 |
| 多个用户同时加入时崩溃 | 浏览器资源不足 | 限制并发数,或者使用 --disable-gpu |
| getStats() 返回空 | PeerConnection 已关闭 | 在调用 getStats() 前检查连接状态 |
我的小技巧: 调试时不要用
headless: true,改成 headless: false 并加上 slowMo: 100,这样你可以亲眼看到浏览器在做什么。等脚本稳定了再切回无头模式。
好了,以上就是用 Puppeteer 做 WebRTC 自动化测试的核心内容。说白了,就是让浏览器替我们干活,而且干得比人更稳定、更可重复。你只要把信令流程和连接检查写清楚,剩下的交给脚本跑就行了。