import { test, expect, Page } from '@playwright/test'; import * as path from 'path'; // Test data const testEmail = `user+profile-${Date.now()}@lab.veza`; const testPassword = `V3za!profile-${Date.now()}`; const avatarPath = path.join(__dirname, '../../data/images/avatar.jpg'); // Helper to register and login async function registerAndLogin(page: Page) { await page.goto('/register'); await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', testPassword); const confirmField = page.locator('input[name="confirmPassword"]'); if (await confirmField.isVisible()) { await confirmField.fill(testPassword); } await page.locator('button[type="submit"]').click(); await expect(page).toHaveURL(/\/(dashboard|home|app|profile)/, { timeout: 10000 }); } test.describe('User Profile Management', () => { test.beforeEach(async ({ page }) => { await registerAndLogin(page); }); test('should load profile page', async ({ page }) => { await page.goto('/profile'); // Check main elements await expect(page.locator('[data-testid="profile-container"], .profile-container, #profile')).toBeVisible(); // Should show user email await expect(page.locator(`text="${testEmail}"`)).toBeVisible(); // Should have edit button await expect(page.locator('button:has-text("Edit"), [data-testid="edit-profile"]')).toBeVisible(); }); test('should display user information', async ({ page }) => { await page.goto('/profile'); // Check for user fields await expect(page.locator('text=/email/i').locator('xpath=following-sibling::*')).toContainText(testEmail); // Check for other profile fields const fields = ['nickname', 'username', 'display name', 'bio', 'about']; for (const field of fields) { const fieldElement = page.locator(`text=/${field}/i`); if (await fieldElement.isVisible({ timeout: 1000 }).catch(() => false)) { // Field exists in profile expect(fieldElement).toBeTruthy(); } } }); test('should edit profile nickname', async ({ page }) => { await page.goto('/profile'); // Click edit button await page.locator('button:has-text("Edit"), [data-testid="edit-profile"]').click(); // Find nickname field const nicknameInput = page.locator('input[name="nickname"], input[placeholder*="nickname"], input[aria-label*="nickname"]'); if (await nicknameInput.isVisible()) { const newNickname = `TestUser${Date.now()}`; await nicknameInput.fill(newNickname); // Save changes await page.locator('button:has-text("Save"), button[type="submit"]').click(); // Should show success message await expect(page.locator('text=/success|updated|saved/i')).toBeVisible({ timeout: 5000 }); // Nickname should be updated await expect(page.locator(`text="${newNickname}"`)).toBeVisible(); } }); test('should upload avatar image', async ({ page }) => { await page.goto('/profile'); // Find avatar upload const avatarSection = page.locator('[data-testid="avatar-section"], .avatar-section'); const uploadButton = avatarSection.locator('button:has-text("Upload"), button:has-text("Change"), input[type="file"]'); if (await uploadButton.isVisible()) { // Upload avatar if (uploadButton.locator('input[type="file"]').isVisible()) { await uploadButton.setInputFiles(avatarPath); } else { await uploadButton.click(); const fileInput = page.locator('input[type="file"][accept*="image"]'); await fileInput.setInputFiles(avatarPath); } // Wait for upload await expect(page.locator('text=/upload.*success|avatar.*updated/i')).toBeVisible({ timeout: 10000 }); // Avatar should be visible const avatarImg = page.locator('img[alt*="avatar"], img[alt*="profile"], .avatar img'); await expect(avatarImg).toBeVisible(); const src = await avatarImg.getAttribute('src'); expect(src).not.toContain('default'); expect(src).not.toContain('placeholder'); } }); test('should validate profile form inputs', async ({ page }) => { await page.goto('/profile'); // Enter edit mode await page.locator('button:has-text("Edit")').click(); // Test bio/about character limit const bioInput = page.locator('textarea[name="bio"], textarea[name="about"]'); if (await bioInput.isVisible()) { // Try to enter very long text const longText = 'a'.repeat(1000); await bioInput.fill(longText); // Should show character count or limit const charCount = page.locator('.char-count, .character-count, text=/\\d+.*\\/.*\\d+/'); if (await charCount.isVisible()) { const count = await charCount.textContent(); expect(count).toMatch(/\d+/); } } // Test URL field validation const websiteInput = page.locator('input[name="website"], input[type="url"]'); if (await websiteInput.isVisible()) { await websiteInput.fill('not-a-url'); await page.locator('button:has-text("Save")').click(); // Should show validation error await expect(page.locator('text=/invalid.*url|valid.*url/i')).toBeVisible(); } }); test('should show account creation date', async ({ page }) => { await page.goto('/profile'); // Look for join date/created date const datePattern = page.locator('text=/joined|created|member since/i'); if (await datePattern.isVisible()) { const dateText = await datePattern.locator('xpath=following-sibling::*').textContent(); expect(dateText).toMatch(/\d{4}|\d{1,2}.*\d{1,2}/); // Year or date pattern } }); test('should handle social media links', async ({ page }) => { await page.goto('/profile'); // Enter edit mode await page.locator('button:has-text("Edit")').click(); // Check for social media fields const socialFields = ['twitter', 'github', 'linkedin', 'facebook']; for (const platform of socialFields) { const input = page.locator(`input[name="${platform}"], input[placeholder*="${platform}"]`); if (await input.isVisible({ timeout: 1000 }).catch(() => false)) { await input.fill(`https://${platform}.com/testuser`); } } // Save await page.locator('button:has-text("Save")').click(); // Links should be saved for (const platform of socialFields) { const link = page.locator(`a[href*="${platform}.com"]`); if (await link.isVisible({ timeout: 1000 }).catch(() => false)) { const href = await link.getAttribute('href'); expect(href).toContain(`${platform}.com/testuser`); } } }); test('should change password', async ({ page }) => { await page.goto('/profile'); // Look for password change section const changePasswordButton = page.locator('button:has-text("Change Password"), a:has-text("Change Password")'); if (await changePasswordButton.isVisible()) { await changePasswordButton.click(); // Fill password change form const currentPasswordInput = page.locator('input[name="currentPassword"], input[name="current_password"]'); const newPasswordInput = page.locator('input[name="newPassword"], input[name="new_password"]'); const confirmPasswordInput = page.locator('input[name="confirmPassword"], input[name="confirm_password"]'); await currentPasswordInput.fill(testPassword); const newPassword = `V3za!new-${Date.now()}`; await newPasswordInput.fill(newPassword); await confirmPasswordInput.fill(newPassword); // Submit await page.locator('button:has-text("Update Password"), button:has-text("Change")').click(); // Should show success await expect(page.locator('text=/password.*changed|password.*updated/i')).toBeVisible({ timeout: 5000 }); } }); test('should show email preferences', async ({ page }) => { await page.goto('/profile'); // Look for email preferences or notifications const preferencesSection = page.locator('text=/email.*preferences|notifications|subscribe/i'); if (await preferencesSection.isVisible()) { // Check for toggles const toggles = page.locator('input[type="checkbox"], [role="switch"]'); const toggleCount = await toggles.count(); if (toggleCount > 0) { // Toggle first preference const firstToggle = toggles.first(); const wasChecked = await firstToggle.isChecked(); await firstToggle.click(); // State should change expect(await firstToggle.isChecked()).toBe(!wasChecked); } } }); test('should handle privacy settings', async ({ page }) => { await page.goto('/profile'); // Look for privacy settings const privacySection = page.locator('text=/privacy|visibility|public.*profile/i'); if (await privacySection.isVisible()) { // Find privacy toggles const privacyToggles = privacySection.locator('xpath=following-sibling::*').locator('input[type="checkbox"], select'); if (await privacyToggles.first().isVisible()) { // Change a privacy setting const firstToggle = privacyToggles.first(); if (await firstToggle.getAttribute('type') === 'checkbox') { await firstToggle.click(); } else { // It's a select await firstToggle.selectOption({ index: 1 }); } // Save if needed const saveButton = page.locator('button:has-text("Save")'); if (await saveButton.isVisible()) { await saveButton.click(); await expect(page.locator('text=/saved|updated/i')).toBeVisible(); } } } }); test('should delete avatar', async ({ page }) => { await page.goto('/profile'); // First upload an avatar const uploadButton = page.locator('button:has-text("Upload"), input[type="file"][accept*="image"]'); if (await uploadButton.isVisible()) { await uploadButton.setInputFiles(avatarPath); await page.waitForTimeout(2000); } // Look for delete avatar option const deleteAvatarButton = page.locator('button:has-text("Remove Avatar"), button:has-text("Delete Avatar")'); if (await deleteAvatarButton.isVisible()) { await deleteAvatarButton.click(); // Confirm if needed const confirmButton = page.locator('button:has-text("Confirm"), button:has-text("Yes")'); if (await confirmButton.isVisible({ timeout: 1000 }).catch(() => false)) { await confirmButton.click(); } // Avatar should be removed await expect(page.locator('text=/avatar.*removed|avatar.*deleted/i')).toBeVisible(); // Should show default avatar const avatarImg = page.locator('.avatar img'); const src = await avatarImg.getAttribute('src'); expect(src).toMatch(/default|placeholder|gravatar/); } }); test('should export user data', async ({ page }) => { await page.goto('/profile'); // Look for export data option const exportButton = page.locator('button:has-text("Export"), a:has-text("Download.*Data")'); if (await exportButton.isVisible()) { // Start download const downloadPromise = page.waitForEvent('download'); await exportButton.click(); try { const download = await downloadPromise; expect(download.suggestedFilename()).toMatch(/export|data|backup/); } catch { // Might need confirmation const confirmExport = page.locator('button:has-text("Download"), button:has-text("Export")').last(); if (await confirmExport.isVisible()) { const downloadPromise = page.waitForEvent('download'); await confirmExport.click(); const download = await downloadPromise; expect(download.suggestedFilename()).toMatch(/export|data|backup/); } } } }); test('should handle account deletion', async ({ page }) => { await page.goto('/profile'); // Look for delete account option (usually in danger zone) const deleteAccountButton = page.locator('button:has-text("Delete Account"), a:has-text("Delete Account")'); if (await deleteAccountButton.isVisible()) { await deleteAccountButton.click(); // Should show confirmation dialog const confirmDialog = page.locator('[role="dialog"], .modal'); await expect(confirmDialog).toBeVisible(); // Should require typing confirmation const confirmInput = confirmDialog.locator('input[placeholder*="DELETE"], input[placeholder*="confirm"]'); if (await confirmInput.isVisible()) { await confirmInput.fill('DELETE'); } // We won't actually delete in test const cancelButton = confirmDialog.locator('button:has-text("Cancel")'); await cancelButton.click(); // Dialog should close await expect(confirmDialog).not.toBeVisible(); } }); });