Files
Toju/e2e/tests/screen-share/outside-voice-live-indicator.spec.ts
T
myxelium 3266581d3c feat(presence): live stream badges, honest ring delivery, cross-signal DMs
- Camera and screen-share badges are decided by `shouldShowStreamIndicator`,
  so a live stream is visible from outside the voice channel and clicking the
  badge joins the streamer's channel before focusing the stream.
- `ringParticipants` reports whether a `direct-call` ring reached anyone; a
  call that reached nobody shows `call.errors.ringUndelivered` instead of
  sitting in "calling" as if it were live.
- Direct messages resolve every local identity alias, so a conversation
  opened from a foreign roster entry lands in the same thread.
2026-08-14 03:19:29 +02:00

96 lines
3.6 KiB
TypeScript

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<Client>,
role: string
): Promise<Client> {
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;
}