Files
Toju/e2e/tests/voice/voice-mute-state-reset.spec.ts
Myx 29032b5a36 fix: Bug - Voice states doesn't get cleared for all users on leave
Broadcast a cleared voice_state when voice-active sockets drop and reset mute/deafen flags on disconnect or reconnect so stale session state cannot leak to other clients.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 01:00:01 +02:00

128 lines
4.8 KiB
TypeScript

import { test, expect } from '../../fixtures/multi-client';
import {
MULTI_DEVICE_PASSWORD,
MULTI_DEVICE_VOICE_CHANNEL,
closeClient,
loginSecondDeviceIntoServer,
uniqueMultiDeviceName
} from '../../helpers/multi-device-session';
import { RegisterPage } from '../../pages/register.page';
import { ServerSearchPage } from '../../pages/server-search.page';
import { ChatRoomPage } from '../../pages/chat-room.page';
async function waitForVoiceMuteState(
page: import('@playwright/test').Page,
displayName: string,
expectedMuted: boolean,
timeout = 45_000
): Promise<void> {
await page.waitForFunction(
({ expectedDisplayName, expectedMuted: muted }) => {
interface VoiceStateShape { isMuted?: boolean }
interface UserShape { displayName: string; voiceState?: VoiceStateShape }
interface ChannelShape { id: string; type: 'text' | 'voice' }
interface RoomShape { channels?: ChannelShape[] }
interface AngularDebugApi {
getComponent: (element: Element) => Record<string, unknown>;
}
const host = document.querySelector('app-rooms-side-panel');
const debugApi = (window as { ng?: AngularDebugApi }).ng;
if (!host || !debugApi?.getComponent) {
return false;
}
const component = debugApi.getComponent(host);
const currentRoom = (component['currentRoom'] as (() => RoomShape | null) | undefined)?.() ?? null;
const voiceChannel = currentRoom?.channels?.find((channel) => channel.type === 'voice');
if (!voiceChannel) {
return false;
}
const roster = (component['voiceUsersInRoom'] as ((roomId: string) => UserShape[]) | undefined)?.(voiceChannel.id) ?? [];
const entry = roster.find((userEntry) => userEntry.displayName === expectedDisplayName);
return entry?.voiceState?.isMuted === muted;
},
{ expectedDisplayName: displayName, expectedMuted },
{ timeout }
);
}
test.describe('Voice mute state reset', () => {
test.describe.configure({ timeout: 300_000, retries: 1 });
test('clears stale mute state after abrupt disconnect and voice rejoin', async ({ createClient }) => {
const suffix = uniqueMultiDeviceName('voice-mute-reset');
const hostCredentials = {
username: `host_${suffix}`,
displayName: 'Voice Host',
password: MULTI_DEVICE_PASSWORD
};
const guestCredentials = {
username: `guest_${suffix}`,
displayName: 'Voice Guest',
password: MULTI_DEVICE_PASSWORD
};
const serverName = `Voice Mute Reset ${suffix}`;
let hostClient = await createClient();
const guestClient = await createClient();
await test.step('host creates the shared server', async () => {
const registerPage = new RegisterPage(hostClient.page);
await registerPage.goto();
await registerPage.register(hostCredentials.username, hostCredentials.displayName, hostCredentials.password);
await expect(hostClient.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
const search = new ServerSearchPage(hostClient.page);
await search.createServer(serverName, { description: 'Voice mute reset coverage' });
await expect(hostClient.page).toHaveURL(/\/room\//, { timeout: 15_000 });
});
const hostRoom = new ChatRoomPage(hostClient.page);
await hostRoom.ensureVoiceChannelExists(MULTI_DEVICE_VOICE_CHANNEL);
await test.step('guest joins the server', async () => {
const registerPage = new RegisterPage(guestClient.page);
await registerPage.goto();
await registerPage.register(guestCredentials.username, guestCredentials.displayName, guestCredentials.password);
await expect(guestClient.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
const search = new ServerSearchPage(guestClient.page);
await search.joinServerFromSearch(serverName);
await expect(guestClient.page).toHaveURL(/\/room\//, { timeout: 20_000 });
});
await test.step('host joins voice muted and guest observes the muted state', async () => {
await hostRoom.joinVoiceChannel(MULTI_DEVICE_VOICE_CHANNEL);
await expect(hostRoom.voiceControls).toBeVisible({ timeout: 20_000 });
await hostRoom.muteButton.click();
await waitForVoiceMuteState(guestClient.page, hostCredentials.displayName, true);
});
await test.step('abrupt host disconnect clears stale mute before rejoin', async () => {
await closeClient(hostClient);
hostClient = await createClient();
await loginSecondDeviceIntoServer(hostClient.page, hostCredentials, serverName);
const reopenedRoom = new ChatRoomPage(hostClient.page);
await reopenedRoom.joinVoiceChannel(MULTI_DEVICE_VOICE_CHANNEL);
await expect(reopenedRoom.voiceControls).toBeVisible({ timeout: 20_000 });
await waitForVoiceMuteState(guestClient.page, hostCredentials.displayName, false);
});
});
});