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 { 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; } 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); }); }); });