feat: Add TURN server support
All checks were successful
Queue Release Build / prepare (push) Successful in 15s
Deploy Web Apps / deploy (push) Successful in 5m35s
Queue Release Build / build-linux (push) Successful in 24m45s
Queue Release Build / build-windows (push) Successful in 13m52s
Queue Release Build / finalize (push) Successful in 23s
All checks were successful
Queue Release Build / prepare (push) Successful in 15s
Deploy Web Apps / deploy (push) Successful in 5m35s
Queue Release Build / build-linux (push) Successful in 24m45s
Queue Release Build / build-windows (push) Successful in 13m52s
Queue Release Build / finalize (push) Successful in 23s
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { test, type Client } from '../../fixtures/multi-client';
|
||||
import {
|
||||
installTestServerEndpoints,
|
||||
type SeededEndpointInput
|
||||
} from '../../helpers/seed-test-endpoint';
|
||||
import { installTestServerEndpoints, type SeededEndpointInput } from '../../helpers/seed-test-endpoint';
|
||||
import { startTestServer } from '../../helpers/test-server';
|
||||
import {
|
||||
dumpRtcDiagnostics,
|
||||
@@ -28,11 +25,11 @@ const USER_COUNT = 8;
|
||||
const EXPECTED_REMOTE_PEERS = USER_COUNT - 1;
|
||||
const STABILITY_WINDOW_MS = 20_000;
|
||||
|
||||
type TestUser = {
|
||||
interface TestUser {
|
||||
username: string;
|
||||
displayName: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
|
||||
type TestClient = Client & {
|
||||
user: TestUser;
|
||||
@@ -64,7 +61,6 @@ test.describe('Dual-signal multi-user voice', () => {
|
||||
status: 'online'
|
||||
}
|
||||
];
|
||||
|
||||
const users = buildUsers();
|
||||
const clients = await createTrackedClients(createClient, users, endpoints);
|
||||
|
||||
@@ -86,12 +82,14 @@ test.describe('Dual-signal multi-user voice', () => {
|
||||
description: 'Primary signal room for 8-user voice mesh',
|
||||
sourceId: PRIMARY_SIGNAL_ID
|
||||
});
|
||||
|
||||
await expect(clients[0].page).toHaveURL(/\/room\//, { timeout: 20_000 });
|
||||
|
||||
await searchPage.createServer(SECONDARY_ROOM_NAME, {
|
||||
description: 'Secondary signal room for dual-socket coverage',
|
||||
sourceId: SECONDARY_SIGNAL_ID
|
||||
});
|
||||
|
||||
await expect(clients[0].page).toHaveURL(/\/room\//, { timeout: 20_000 });
|
||||
});
|
||||
|
||||
@@ -141,7 +139,7 @@ test.describe('Dual-signal multi-user voice', () => {
|
||||
waitForAudioStatsPresent(client.page, 30_000)
|
||||
));
|
||||
|
||||
// Allow the mesh to settle — voice routing, allowed-peer-id
|
||||
// Allow the mesh to settle - voice routing, allowed-peer-id
|
||||
// propagation and renegotiation all need time after the last
|
||||
// user joins.
|
||||
await clients[0].page.waitForTimeout(5_000);
|
||||
@@ -173,6 +171,7 @@ test.describe('Dual-signal multi-user voice', () => {
|
||||
timeout: 10_000,
|
||||
intervals: [500, 1_000]
|
||||
}).toBe(EXPECTED_REMOTE_PEERS);
|
||||
|
||||
await expect.poll(async () => await getConnectedSignalManagerCount(client.page), {
|
||||
timeout: 10_000,
|
||||
intervals: [500, 1_000]
|
||||
@@ -236,7 +235,7 @@ test.describe('Dual-signal multi-user voice', () => {
|
||||
|
||||
await room.deafenButton.click();
|
||||
await client.page.waitForTimeout(500);
|
||||
// Un-deafen does NOT restore mute – the user stays muted
|
||||
// Un-deafen does NOT restore mute - the user stays muted
|
||||
await waitForVoiceStateAcrossPages(clients, client.user.displayName, {
|
||||
isMuted: true,
|
||||
isDeafened: false
|
||||
@@ -245,7 +244,7 @@ test.describe('Dual-signal multi-user voice', () => {
|
||||
});
|
||||
|
||||
await test.step('Unmute all users and verify audio flows end-to-end', async () => {
|
||||
// Every user is left muted after deafen cycling — unmute them all
|
||||
// Every user is left muted after deafen cycling - unmute them all
|
||||
for (const client of clients) {
|
||||
const room = new ChatRoomPage(client.page);
|
||||
|
||||
@@ -256,7 +255,7 @@ test.describe('Dual-signal multi-user voice', () => {
|
||||
});
|
||||
}
|
||||
|
||||
// Final audio flow check on every peer — confirms the full
|
||||
// Final audio flow check on every peer - confirms the full
|
||||
// send/receive pipeline still works after mute+deafen cycling
|
||||
for (const client of clients) {
|
||||
try {
|
||||
@@ -284,7 +283,7 @@ function buildUsers(): TestUser[] {
|
||||
async function createTrackedClients(
|
||||
createClient: () => Promise<Client>,
|
||||
users: TestUser[],
|
||||
endpoints: ReadonlyArray<SeededEndpointInput>
|
||||
endpoints: readonly SeededEndpointInput[]
|
||||
): Promise<TestClient[]> {
|
||||
const clients: TestClient[] = [];
|
||||
|
||||
@@ -384,9 +383,11 @@ async function waitForCurrentRoomName(page: Page, roomName: string, timeout = 20
|
||||
}
|
||||
|
||||
async function openVoiceWorkspace(page: Page): Promise<void> {
|
||||
const viewButton = page.locator('app-rooms-side-panel').getByRole('button', { name: /view|open/i }).first();
|
||||
const viewButton = page.locator('app-rooms-side-panel').getByRole('button', { name: /view|open/i })
|
||||
.first();
|
||||
|
||||
if (await page.locator('app-voice-workspace').isVisible().catch(() => false)) {
|
||||
if (await page.locator('app-voice-workspace').isVisible()
|
||||
.catch(() => false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -396,6 +397,7 @@ async function openVoiceWorkspace(page: Page): Promise<void> {
|
||||
|
||||
async function joinVoiceChannelUntilConnected(page: Page, channelName: string, attempts = 3): Promise<void> {
|
||||
const room = new ChatRoomPage(page);
|
||||
|
||||
let lastError: unknown;
|
||||
|
||||
for (let attempt = 1; attempt <= attempts; attempt++) {
|
||||
@@ -559,7 +561,7 @@ async function getVoiceJoinDiagnostics(page: Page, channelName: string): Promise
|
||||
const realtime = component['realtime'] as {
|
||||
connectionErrorMessage?: () => string | null;
|
||||
signalingTransportHandler?: {
|
||||
getConnectedSignalingManagers?: () => Array<{ signalUrl: string }>;
|
||||
getConnectedSignalingManagers?: () => { signalUrl: string }[];
|
||||
};
|
||||
} | undefined;
|
||||
|
||||
@@ -596,7 +598,7 @@ async function waitForConnectedSignalManagerCount(page: Page, expectedCount: num
|
||||
const component = debugApi.getComponent(host);
|
||||
const realtime = component['realtime'] as {
|
||||
signalingTransportHandler?: {
|
||||
getConnectedSignalingManagers?: () => Array<{ signalUrl: string }>;
|
||||
getConnectedSignalingManagers?: () => { signalUrl: string }[];
|
||||
};
|
||||
} | undefined;
|
||||
const countValue = realtime?.signalingTransportHandler?.getConnectedSignalingManagers?.().length ?? 0;
|
||||
@@ -624,7 +626,7 @@ async function getConnectedSignalManagerCount(page: Page): Promise<number> {
|
||||
const component = debugApi.getComponent(host);
|
||||
const realtime = component['realtime'] as {
|
||||
signalingTransportHandler?: {
|
||||
getConnectedSignalingManagers?: () => Array<{ signalUrl: string }>;
|
||||
getConnectedSignalingManagers?: () => { signalUrl: string }[];
|
||||
};
|
||||
} | undefined;
|
||||
|
||||
@@ -647,7 +649,7 @@ async function waitForVoiceWorkspaceUserCount(page: Page, expectedCount: number)
|
||||
}
|
||||
|
||||
const component = debugApi.getComponent(host);
|
||||
const connectedUsers = (component['connectedVoiceUsers'] as (() => Array<unknown>) | undefined)?.() ?? [];
|
||||
const connectedUsers = (component['connectedVoiceUsers'] as (() => unknown[]) | undefined)?.() ?? [];
|
||||
|
||||
return connectedUsers.length === count;
|
||||
},
|
||||
@@ -688,7 +690,7 @@ async function waitForVoiceRosterCount(page: Page, channelName: string, expected
|
||||
return false;
|
||||
}
|
||||
|
||||
const roster = (component['voiceUsersInRoom'] as ((roomId: string) => Array<unknown>) | undefined)?.(channelId) ?? [];
|
||||
const roster = (component['voiceUsersInRoom'] as ((roomId: string) => unknown[]) | undefined)?.(channelId) ?? [];
|
||||
|
||||
return roster.length === expected;
|
||||
},
|
||||
@@ -698,7 +700,7 @@ async function waitForVoiceRosterCount(page: Page, channelName: string, expected
|
||||
}
|
||||
|
||||
async function waitForVoiceStateAcrossPages(
|
||||
clients: ReadonlyArray<TestClient>,
|
||||
clients: readonly TestClient[],
|
||||
displayName: string,
|
||||
expectedState: { isMuted: boolean; isDeafened: boolean }
|
||||
): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user