veza/tools/tests/e2e/specs/chat.spec.ts
2025-12-03 22:56:50 +01:00

346 lines
No EOL
12 KiB
TypeScript

import { test, expect, Page, BrowserContext } from '@playwright/test';
// Test data
const testEmail1 = `user+chat1-${Date.now()}@lab.veza`;
const testEmail2 = `user+chat2-${Date.now()}@lab.veza`;
const testPassword = `V3za!chat-${Date.now()}`;
// Helper to register and login
async function registerAndLogin(page: Page, email: string, password: string) {
await page.goto('/register');
await page.fill('input[type="email"]', email);
await page.fill('input[type="password"]', password);
const confirmField = page.locator('input[name="confirmPassword"]');
if (await confirmField.isVisible()) {
await confirmField.fill(password);
}
await page.locator('button[type="submit"]').click();
await expect(page).toHaveURL(/\/(dashboard|home|app|chat)/, { timeout: 10000 });
}
test.describe('Chat Functionality', () => {
let context1: BrowserContext;
let context2: BrowserContext;
let page1: Page;
let page2: Page;
test.beforeAll(async ({ browser }) => {
// Create two browser contexts for two users
context1 = await browser.newContext();
context2 = await browser.newContext();
page1 = await context1.newPage();
page2 = await context2.newPage();
// Register both users
await registerAndLogin(page1, testEmail1, testPassword);
await registerAndLogin(page2, testEmail2, testPassword);
});
test.afterAll(async () => {
await context1.close();
await context2.close();
});
test('should load chat interface', async () => {
await page1.goto('/chat');
// Check main chat elements
await expect(page1.locator('[data-testid="chat-container"], .chat-container, #chat')).toBeVisible();
await expect(page1.locator('[data-testid="message-input"], input[placeholder*="message"], textarea[placeholder*="message"]')).toBeVisible();
await expect(page1.locator('[data-testid="send-button"], button:has-text("Send")')).toBeVisible();
// Check for rooms/channels list
await expect(page1.locator('[data-testid="rooms-list"], .rooms-list, .channels-list')).toBeVisible();
});
test('should join default room', async () => {
await page1.goto('/chat');
// Look for general/default room
const generalRoom = page1.locator('text=/general|default|lobby/i');
if (await generalRoom.isVisible()) {
await generalRoom.click();
// Should show room is active
await expect(generalRoom).toHaveClass(/active|selected/, { timeout: 5000 });
}
// Should show room name in header
await expect(page1.locator('h1, h2, .room-name').filter({ hasText: /general|default|lobby/i })).toBeVisible();
});
test('should send and receive messages', async () => {
// Both users go to chat
await page1.goto('/chat');
await page2.goto('/chat');
// Join same room
const room = 'general';
await page1.locator(`text=${room}`).click();
await page2.locator(`text=${room}`).click();
// User 1 sends a message
const testMessage = `Test message ${Date.now()}`;
await page1.fill('[data-testid="message-input"], input[placeholder*="message"]', testMessage);
await page1.locator('[data-testid="send-button"], button:has-text("Send")').click();
// Message should appear for user 1
await expect(page1.locator(`text="${testMessage}"`)).toBeVisible({ timeout: 5000 });
// Message should appear for user 2
await expect(page2.locator(`text="${testMessage}"`)).toBeVisible({ timeout: 5000 });
});
test('should show message metadata', async () => {
await page1.goto('/chat');
const testMessage = `Metadata test ${Date.now()}`;
await page1.fill('[data-testid="message-input"]', testMessage);
await page1.locator('[data-testid="send-button"]').click();
// Find the message container
const messageContainer = page1.locator(`text="${testMessage}"`).locator('xpath=ancestor::*[contains(@class, "message")]');
// Should show timestamp
await expect(messageContainer.locator('time, .timestamp, .message-time')).toBeVisible();
// Should show sender name
await expect(messageContainer.locator('.sender, .username, .author')).toBeVisible();
});
test('should handle emoji in messages', async () => {
await page1.goto('/chat');
const emojiMessage = `Hello 👋 World 🌍 ${Date.now()}`;
await page1.fill('[data-testid="message-input"]', emojiMessage);
await page1.locator('[data-testid="send-button"]').click();
// Emoji should be displayed correctly
await expect(page1.locator(`text="${emojiMessage}"`)).toBeVisible({ timeout: 5000 });
});
test('should show typing indicators', async () => {
await page1.goto('/chat');
await page2.goto('/chat');
// User 1 starts typing
await page1.fill('[data-testid="message-input"]', 'Typing...');
// User 2 should see typing indicator (if implemented)
const typingIndicator = page2.locator('text=/typing|is typing/i, .typing-indicator');
// This might not be implemented, so we make it optional
if (await typingIndicator.isVisible({ timeout: 2000 }).catch(() => false)) {
expect(typingIndicator).toBeTruthy();
}
});
test('should handle message editing', async () => {
await page1.goto('/chat');
// Send a message
const originalMessage = `Original ${Date.now()}`;
await page1.fill('[data-testid="message-input"]', originalMessage);
await page1.locator('[data-testid="send-button"]').click();
// Wait for message to appear
const message = page1.locator(`text="${originalMessage}"`);
await expect(message).toBeVisible();
// Try to edit (right-click or edit button)
const messageContainer = message.locator('xpath=ancestor::*[contains(@class, "message")]');
// Look for edit option
await messageContainer.hover();
const editButton = messageContainer.locator('button:has-text("Edit"), [data-testid="edit-message"]');
if (await editButton.isVisible({ timeout: 2000 }).catch(() => false)) {
await editButton.click();
// Edit the message
const editInput = page1.locator('input[value*="Original"], textarea:has-text("Original")');
await editInput.fill(`Edited ${Date.now()}`);
await editInput.press('Enter');
// Should show edited indicator
await expect(messageContainer.locator('text=/edited/i')).toBeVisible();
}
});
test('should handle message deletion', async () => {
await page1.goto('/chat');
// Send a message
const deleteMessage = `To delete ${Date.now()}`;
await page1.fill('[data-testid="message-input"]', deleteMessage);
await page1.locator('[data-testid="send-button"]').click();
// Wait for message
const message = page1.locator(`text="${deleteMessage}"`);
await expect(message).toBeVisible();
// Try to delete
const messageContainer = message.locator('xpath=ancestor::*[contains(@class, "message")]');
await messageContainer.hover();
const deleteButton = messageContainer.locator('button:has-text("Delete"), [data-testid="delete-message"]');
if (await deleteButton.isVisible({ timeout: 2000 }).catch(() => false)) {
await deleteButton.click();
// Confirm if needed
const confirmButton = page1.locator('button:has-text("Confirm"), button:has-text("Yes")');
if (await confirmButton.isVisible({ timeout: 1000 }).catch(() => false)) {
await confirmButton.click();
}
// Message should be gone or marked as deleted
await expect(message).not.toBeVisible({ timeout: 5000 });
}
});
test('should load message history', async () => {
await page1.goto('/chat');
// Send multiple messages
for (let i = 0; i < 5; i++) {
await page1.fill('[data-testid="message-input"]', `History message ${i}`);
await page1.locator('[data-testid="send-button"]').click();
await page1.waitForTimeout(100); // Small delay between messages
}
// Reload page
await page1.reload();
// Messages should still be visible
for (let i = 0; i < 5; i++) {
await expect(page1.locator(`text="History message ${i}"`)).toBeVisible();
}
});
test('should handle file uploads in chat', async () => {
await page1.goto('/chat');
// Look for file upload button
const uploadButton = page1.locator('button[aria-label="Upload"], [data-testid="upload-file"], input[type="file"]');
if (await uploadButton.isVisible({ timeout: 2000 }).catch(() => false)) {
// Create a test file
const buffer = Buffer.from('Test file content');
await uploadButton.setInputFiles({
name: 'test.txt',
mimeType: 'text/plain',
buffer,
});
// Should show file in chat
await expect(page1.locator('text=test.txt')).toBeVisible({ timeout: 5000 });
}
});
test('should handle room switching', async () => {
await page1.goto('/chat');
// Get list of rooms
const rooms = await page1.locator('.room-item, [data-testid^="room-"]').all();
if (rooms.length > 1) {
// Click second room
await rooms[1].click();
// Should update UI
await expect(rooms[1]).toHaveClass(/active|selected/);
// Messages should update
const messagesContainer = page1.locator('.messages, [data-testid="messages-container"]');
await expect(messagesContainer).toBeVisible();
}
});
test('should handle room creation', async () => {
await page1.goto('/chat');
// Look for create room button
const createButton = page1.locator('button:has-text("Create"), button:has-text("New Room"), [data-testid="create-room"]');
if (await createButton.isVisible({ timeout: 2000 }).catch(() => false)) {
await createButton.click();
// Fill room name
const roomNameInput = page1.locator('input[placeholder*="room name"], input[name="roomName"]');
const newRoomName = `Test Room ${Date.now()}`;
await roomNameInput.fill(newRoomName);
// Submit
await page1.locator('button:has-text("Create"), button:has-text("Submit")').click();
// New room should appear in list
await expect(page1.locator(`text="${newRoomName}"`)).toBeVisible({ timeout: 5000 });
}
});
test('should handle private messages', async () => {
await page1.goto('/chat');
await page2.goto('/chat');
// Look for users list
const usersList = page1.locator('.users-list, [data-testid="online-users"]');
if (await usersList.isVisible({ timeout: 2000 }).catch(() => false)) {
// Find user 2
const user2Item = usersList.locator(`text=/${testEmail2}|user.*2/i`);
if (await user2Item.isVisible({ timeout: 2000 }).catch(() => false)) {
await user2Item.click();
// Should open DM
const dmInput = page1.locator('[data-testid="message-input"]');
await dmInput.fill('Private message test');
await page1.locator('[data-testid="send-button"]').click();
// User 2 should receive notification or message
await expect(page2.locator('text="Private message test"')).toBeVisible({ timeout: 5000 });
}
}
});
test('should show online/offline status', async () => {
await page1.goto('/chat');
// Look for status indicators
const statusIndicators = page1.locator('.status-indicator, .online-status, [data-status]');
if (await statusIndicators.first().isVisible({ timeout: 2000 }).catch(() => false)) {
// Should have online class or green color
const firstIndicator = statusIndicators.first();
const classes = await firstIndicator.getAttribute('class');
const style = await firstIndicator.getAttribute('style');
expect(classes || style).toMatch(/online|green|#0f0|rgb\(0,\s*255,\s*0\)/i);
}
});
test('should handle message search', async () => {
await page1.goto('/chat');
// Send searchable message
const searchableMessage = `Searchable ${Date.now()}`;
await page1.fill('[data-testid="message-input"]', searchableMessage);
await page1.locator('[data-testid="send-button"]').click();
// Look for search
const searchInput = page1.locator('input[placeholder*="Search"], [data-testid="search-messages"]');
if (await searchInput.isVisible({ timeout: 2000 }).catch(() => false)) {
await searchInput.fill('Searchable');
await searchInput.press('Enter');
// Should highlight or filter messages
await expect(page1.locator(`text="${searchableMessage}"`)).toBeVisible();
}
});
});