feat: Add pm
This commit is contained in:
@@ -22,7 +22,7 @@ export class ServerSearchPage {
|
||||
readonly dialogCancelButton: Locator;
|
||||
|
||||
constructor(private page: Page) {
|
||||
this.searchInput = page.getByPlaceholder('Search servers...');
|
||||
this.searchInput = page.getByPlaceholder('Search servers and users...');
|
||||
this.railCreateServerButton = page.locator('button[title="Create Server"]');
|
||||
this.searchCreateServerButton = page.getByRole('button', { name: 'Create New Server' });
|
||||
this.createServerButton = this.searchCreateServerButton;
|
||||
|
||||
117
e2e/tests/chat/dm-flow.spec.ts
Normal file
117
e2e/tests/chat/dm-flow.spec.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
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<Client>): Promise<DmScenario> {
|
||||
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<void> {
|
||||
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)}`;
|
||||
}
|
||||
@@ -88,7 +88,7 @@ test.describe('Connectivity warning', () => {
|
||||
|
||||
await register.goto();
|
||||
await register.register(`alice_${suffix}`, 'Alice', 'TestPass123!');
|
||||
await expect(alice.page.getByPlaceholder('Search servers...')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(alice.page.getByPlaceholder('Search servers and users...')).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
await test.step('Register Bob', async () => {
|
||||
@@ -96,7 +96,7 @@ test.describe('Connectivity warning', () => {
|
||||
|
||||
await register.goto();
|
||||
await register.register(`bob_${suffix}`, 'Bob', 'TestPass123!');
|
||||
await expect(bob.page.getByPlaceholder('Search servers...')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(bob.page.getByPlaceholder('Search servers and users...')).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
await test.step('Register Charlie', async () => {
|
||||
@@ -104,7 +104,7 @@ test.describe('Connectivity warning', () => {
|
||||
|
||||
await register.goto();
|
||||
await register.register(`charlie_${suffix}`, 'Charlie', 'TestPass123!');
|
||||
await expect(charlie.page.getByPlaceholder('Search servers...')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(charlie.page.getByPlaceholder('Search servers and users...')).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
// ── Create server and have everyone join ──
|
||||
|
||||
@@ -9,7 +9,7 @@ test.describe('ICE server settings', () => {
|
||||
|
||||
await register.goto();
|
||||
await register.register(`user_${suffix}`, 'IceTestUser', 'TestPass123!');
|
||||
await expect(page.getByPlaceholder('Search servers...')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(page.getByPlaceholder('Search servers and users...')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByTitle('Settings').click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10_000 });
|
||||
await page.getByRole('button', { name: 'Network' }).click();
|
||||
|
||||
@@ -89,7 +89,7 @@ test.describe('STUN/TURN fallback behaviour', () => {
|
||||
|
||||
await register.goto();
|
||||
await register.register(`alice_${suffix}`, 'Alice', 'TestPass123!');
|
||||
await expect(alice.page.getByPlaceholder('Search servers...')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(alice.page.getByPlaceholder('Search servers and users...')).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
await test.step('Register Bob', async () => {
|
||||
@@ -97,7 +97,7 @@ test.describe('STUN/TURN fallback behaviour', () => {
|
||||
|
||||
await register.goto();
|
||||
await register.register(`bob_${suffix}`, 'Bob', 'TestPass123!');
|
||||
await expect(bob.page.getByPlaceholder('Search servers...')).toBeVisible({ timeout: 30_000 });
|
||||
await expect(bob.page.getByPlaceholder('Search servers and users...')).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
await test.step('Alice creates a server', async () => {
|
||||
|
||||
@@ -556,7 +556,7 @@ async function installDeterministicVoiceSettings(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
async function openSearchView(page: Page): Promise<void> {
|
||||
const searchInput = page.getByPlaceholder('Search servers...');
|
||||
const searchInput = page.getByPlaceholder('Search servers and users...');
|
||||
|
||||
if (await searchInput.isVisible().catch(() => false)) {
|
||||
return;
|
||||
@@ -567,7 +567,7 @@ async function openSearchView(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
async function joinRoomFromSearch(page: Page, roomName: string): Promise<void> {
|
||||
const searchInput = page.getByPlaceholder('Search servers...');
|
||||
const searchInput = page.getByPlaceholder('Search servers and users...');
|
||||
|
||||
await expect(searchInput).toBeVisible({ timeout: 20_000 });
|
||||
await searchInput.fill(roomName);
|
||||
|
||||
@@ -319,7 +319,7 @@ async function installDeterministicVoiceSettings(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
async function openSearchView(page: Page): Promise<void> {
|
||||
const searchInput = page.getByPlaceholder('Search servers...');
|
||||
const searchInput = page.getByPlaceholder('Search servers and users...');
|
||||
|
||||
if (await searchInput.isVisible().catch(() => false)) {
|
||||
return;
|
||||
@@ -330,7 +330,7 @@ async function openSearchView(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
async function joinRoomFromSearch(page: Page, roomName: string): Promise<void> {
|
||||
const searchInput = page.getByPlaceholder('Search servers...');
|
||||
const searchInput = page.getByPlaceholder('Search servers and users...');
|
||||
|
||||
await expect(searchInput).toBeVisible({ timeout: 20_000 });
|
||||
await searchInput.fill(roomName);
|
||||
|
||||
Reference in New Issue
Block a user