import { expect } from '@playwright/test'; import { test, type Client } from '../../fixtures/multi-client'; import { installDeterministicVoiceSettings } from '../../helpers/voice-session'; import { installAutoResumeAudioContext, installWebRTCTracking } from '../../helpers/webrtc-helpers'; import { ChatRoomPage } from '../../pages/chat-room.page'; import { RegisterPage } from '../../pages/register.page'; import { ServerSearchPage } from '../../pages/server-search.page'; const USER_PASSWORD = 'TestPass123!'; const VOICE_CHANNEL = 'General'; /** * A user sharing alone in a voice channel used to look idle to everyone else, * because the LIVE badge was gated on the observer's own voice connection. The * observer here never joins voice, so the badge can only appear if sharing * presence reaches a non-participant. */ test.describe('Screen share visibility from outside the channel', () => { test('a user who is not in voice sees the LIVE badge of someone sharing alone', async ({ createClient }) => { test.setTimeout(240_000); const serverName = `Outside Share ${Date.now()}`; const sharer = await createVoiceClient(createClient, 'sharer'); const observer = await createVoiceClient(createClient, 'observer'); const sharerRoom = new ChatRoomPage(sharer.page); const observerRoom = new ChatRoomPage(observer.page); await test.step('Both users register and join the server', async () => { await new ServerSearchPage(sharer.page).createServer(serverName, { description: 'Live badge visibility test' }); await expect(sharer.page).toHaveURL(/\/room\//, { timeout: 20_000 }); await new ServerSearchPage(observer.page).joinServerFromSearch(serverName); await expect(observer.page).toHaveURL(/\/room\//, { timeout: 20_000 }); }); await test.step('The sharer shares while alone in the voice channel', async () => { await sharerRoom.ensureVoiceChannelExists(VOICE_CHANNEL); await sharerRoom.joinVoiceChannel(VOICE_CHANNEL); await expect(sharerRoom.voiceControls).toBeVisible({ timeout: 20_000 }); await sharerRoom.startScreenShare(); await expect(sharerRoom.isScreenShareActive).toBeVisible({ timeout: 15_000 }); }); await test.step('The observer stays out of voice and still sees the badge', async () => { await expect(observerRoom.channelsSidePanel).toBeVisible({ timeout: 20_000 }); // Proves the observer never joined: the disconnect control only shows in voice. await expect(observerRoom.disconnectButton).toBeHidden(); await expect(liveBadge(observerRoom)) .toBeVisible({ timeout: 60_000 }); }); await test.step('The badge disappears when the share stops', async () => { await sharerRoom.stopScreenShare(); await expect(liveBadge(observerRoom)) .toBeHidden({ timeout: 60_000 }); }); }); }); function liveBadge(room: ChatRoomPage) { return room.channelsSidePanel .locator('[data-testid="voice-user-live"]') .first(); } async function createVoiceClient( createClient: () => Promise, role: string ): Promise { const client = await createClient(); await installDeterministicVoiceSettings(client.page); await installWebRTCTracking(client.page); await installAutoResumeAudioContext(client.page); const registerPage = new RegisterPage(client.page); await registerPage.goto(); await registerPage.register( `outside_share_${role}_${Date.now()}`, `Outside Share ${role}`, USER_PASSWORD ); await expect(client.page).toHaveURL(/\/dashboard/, { timeout: 20_000 }); return client; }