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(); } }); });