import { type Page } from '@playwright/test'; import { test, expect, type Client } from '../../fixtures/multi-client'; import { RegisterPage } from '../../pages/register.page'; import { ServerSearchPage } from '../../pages/server-search.page'; import { ChatMessagesPage } from '../../pages/chat-messages.page'; test.describe('Direct message flow', () => { test.describe.configure({ timeout: 180_000 }); test('opens a DM from a user card and queues messages while offline', async ({ createClient }) => { const scenario = await createDmScenario(createClient); const offlineMessage = `Offline DM ${uniqueName('msg')}`; await test.step('Alice opens Bob from the room user list', async () => { const bobUserCard = scenario.alice.page.locator('[data-testid^="room-user-card-"]', { hasText: 'Bob' }).first(); await expect(bobUserCard).toBeVisible({ timeout: 20_000 }); await bobUserCard.getByRole('button', { name: 'Message Bob' }).click(); await expect(scenario.alice.page).toHaveURL(/\/dm\//, { timeout: 15_000 }); await expect(scenario.alice.page.getByRole('heading', { name: 'Bob' })).toBeVisible({ timeout: 10_000 }); }); await test.step('Offline send persists locally as queued', async () => { await scenario.alice.page.evaluate(() => window.simulateOffline?.()); await scenario.alice.page.getByTestId('dm-input').fill(offlineMessage); await scenario.alice.page.getByTestId('dm-input').press('Enter'); await expect(scenario.alice.page.locator('app-dm-chat').getByText(offlineMessage)).toBeVisible({ timeout: 10_000 }); await expect(scenario.alice.page.getByTestId('message-status').last()).toContainText('QUEUED'); }); }); test('shows friend and message actions on the search people list', async ({ createClient }) => { const scenario = await createDmScenario(createClient); await scenario.alice.page.goto('/search', { waitUntil: 'domcontentloaded' }); await expect(scenario.alice.page).toHaveURL(/\/search/, { timeout: 20_000 }); await expect(scenario.alice.page.locator('app-server-search')).toBeVisible({ timeout: 20_000 }); await expect(scenario.alice.page.locator('app-user-search-list')).toBeVisible({ timeout: 20_000 }); const bobPeopleCard = scenario.alice.page.locator(`app-user-search-list [data-testid="user-card-${scenario.bobUserId}"]`); await expect(bobPeopleCard).toBeVisible({ timeout: 15_000 }); const friendButton = bobPeopleCard.locator(`[data-testid="friend-button-${scenario.bobUserId}"]`); const messageButton = bobPeopleCard.locator(`[data-testid="message-user-${scenario.bobUserId}"]`); await expect(friendButton).toBeVisible({ timeout: 15_000 }); await expect(messageButton).toBeVisible({ timeout: 15_000 }); }); }); interface DmScenario { alice: Client; bob: Client; bobUserId: string; aliceSearch: ServerSearchPage; } async function createDmScenario(createClient: () => Promise): Promise { const suffix = uniqueName('dm'); const serverName = `DM Server ${suffix}`; const alice = await createClient(); const bob = await createClient(); await registerUser(alice.page, `alice_${suffix}`, 'Alice'); await registerUser(bob.page, `bob_${suffix}`, 'Bob'); const aliceSearch = new ServerSearchPage(alice.page); await aliceSearch.createServer(serverName, { description: 'E2E direct message discovery server' }); await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 }); await new ChatMessagesPage(alice.page).waitForReady(); const bobSearch = new ServerSearchPage(bob.page); await bobSearch.searchInput.fill(serverName); await bob.page.locator('button', { hasText: serverName }) .first() .click(); await expect(bob.page).toHaveURL(/\/room\//, { timeout: 15_000 }); await new ChatMessagesPage(bob.page).waitForReady(); const bobRoomCard = alice.page.locator('[data-testid^="room-user-card-"]', { hasText: 'Bob' }).first(); await expect(bobRoomCard).toBeVisible({ timeout: 20_000 }); const bobUserCardTestId = await bobRoomCard.getAttribute('data-testid'); const bobUserId = bobUserCardTestId?.replace('room-user-card-', ''); if (!bobUserId) { throw new Error('Expected Bob room user card to expose a stable test id.'); } return { alice, bob, bobUserId, aliceSearch }; } async function registerUser(page: Page, username: string, displayName: string): Promise { const registerPage = new RegisterPage(page); await registerPage.goto(); await registerPage.register(username, displayName, 'TestPass123!'); await expect(page).toHaveURL(/\/search/, { timeout: 15_000 }); } function uniqueName(prefix: string): string { return `${prefix}-${Date.now()}-${Math.random().toString(36) .slice(2, 8)}`; }