Some checks failed
Queue Release Build / prepare (push) Successful in 17s
Deploy Web Apps / deploy (push) Has started running
Queue Release Build / build-windows (push) Has been cancelled
Queue Release Build / finalize (push) Has been cancelled
Queue Release Build / build-linux (push) Has been cancelled
135 lines
4.3 KiB
TypeScript
135 lines
4.3 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import { type Page } from '@playwright/test';
|
|
|
|
/**
|
|
* Install RTCPeerConnection monkey-patch on a page BEFORE navigating.
|
|
* Tracks all created peer connections and their remote tracks so tests
|
|
* can inspect WebRTC state via `page.evaluate()`.
|
|
*
|
|
* Call immediately after page creation, before any `goto()`.
|
|
*/
|
|
export async function installWebRTCTracking(page: Page): Promise<void> {
|
|
await page.addInitScript(() => {
|
|
const connections: RTCPeerConnection[] = [];
|
|
|
|
(window as any).__rtcConnections = connections;
|
|
(window as any).__rtcRemoteTracks = [] as { kind: string; id: string; readyState: string }[];
|
|
|
|
const OriginalRTCPeerConnection = window.RTCPeerConnection;
|
|
|
|
(window as any).RTCPeerConnection = function(this: RTCPeerConnection, ...args: any[]) {
|
|
const pc: RTCPeerConnection = new OriginalRTCPeerConnection(...args);
|
|
|
|
connections.push(pc);
|
|
|
|
pc.addEventListener('connectionstatechange', () => {
|
|
(window as any).__lastRtcState = pc.connectionState;
|
|
});
|
|
|
|
pc.addEventListener('track', (event: RTCTrackEvent) => {
|
|
(window as any).__rtcRemoteTracks.push({
|
|
kind: event.track.kind,
|
|
id: event.track.id,
|
|
readyState: event.track.readyState
|
|
});
|
|
});
|
|
|
|
return pc;
|
|
} as any;
|
|
|
|
(window as any).RTCPeerConnection.prototype = OriginalRTCPeerConnection.prototype;
|
|
Object.setPrototypeOf((window as any).RTCPeerConnection, OriginalRTCPeerConnection);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait until at least one RTCPeerConnection reaches the 'connected' state.
|
|
*/
|
|
export async function waitForPeerConnected(page: Page, timeout = 30_000): Promise<void> {
|
|
await page.waitForFunction(
|
|
() => (window as any).__rtcConnections?.some(
|
|
(pc: RTCPeerConnection) => pc.connectionState === 'connected'
|
|
) ?? false,
|
|
{ timeout }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check that a peer connection is still in 'connected' state (not failed/disconnected).
|
|
*/
|
|
export async function isPeerStillConnected(page: Page): Promise<boolean> {
|
|
return page.evaluate(
|
|
() => (window as any).__rtcConnections?.some(
|
|
(pc: RTCPeerConnection) => pc.connectionState === 'connected'
|
|
) ?? false
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get outbound and inbound audio RTP stats from the first peer connection.
|
|
*/
|
|
export async function getAudioStats(page: Page): Promise<{
|
|
outbound: { bytesSent: number; packetsSent: number } | null;
|
|
inbound: { bytesReceived: number; packetsReceived: number } | null;
|
|
}> {
|
|
return page.evaluate(async () => {
|
|
const connections = (window as any).__rtcConnections as RTCPeerConnection[] | undefined;
|
|
|
|
if (!connections?.length)
|
|
return { outbound: null, inbound: null };
|
|
|
|
let outbound: { bytesSent: number; packetsSent: number } | null = null;
|
|
let inbound: { bytesReceived: number; packetsReceived: number } | null = null;
|
|
|
|
for (const pc of connections) {
|
|
if (pc.connectionState !== 'connected')
|
|
continue;
|
|
|
|
const stats = await pc.getStats();
|
|
|
|
stats.forEach((report: any) => {
|
|
const reportMediaType = report.kind ?? report.mediaType;
|
|
|
|
if (report.type === 'outbound-rtp' && reportMediaType === 'audio' && !outbound) {
|
|
outbound = {
|
|
bytesSent: report.bytesSent ?? 0,
|
|
packetsSent: report.packetsSent ?? 0
|
|
};
|
|
}
|
|
|
|
if (report.type === 'inbound-rtp' && reportMediaType === 'audio' && !inbound) {
|
|
inbound = {
|
|
bytesReceived: report.bytesReceived ?? 0,
|
|
packetsReceived: report.packetsReceived ?? 0
|
|
};
|
|
}
|
|
});
|
|
|
|
if (outbound && inbound)
|
|
break;
|
|
}
|
|
|
|
return { outbound, inbound };
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Snapshot audio stats, wait `durationMs`, snapshot again, and return the delta.
|
|
* Useful for verifying audio is actively flowing (bytes increasing).
|
|
*/
|
|
export async function getAudioStatsDelta(page: Page, durationMs = 3_000): Promise<{
|
|
outboundBytesDelta: number;
|
|
inboundBytesDelta: number;
|
|
}> {
|
|
const before = await getAudioStats(page);
|
|
|
|
await page.waitForTimeout(durationMs);
|
|
|
|
const after = await getAudioStats(page);
|
|
|
|
return {
|
|
outboundBytesDelta: (after.outbound?.bytesSent ?? 0) - (before.outbound?.bytesSent ?? 0),
|
|
inboundBytesDelta: (after.inbound?.bytesReceived ?? 0) - (before.inbound?.bytesReceived ?? 0)
|
|
};
|
|
}
|