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

356 lines
No EOL
13 KiB
TypeScript

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