346 lines
No EOL
12 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
}); |