import { test, expect } from './fixtures'; import { exec as cp_exec } from 'child_process'; import { promisify } from 'util'; const exec = promisify(cp_exec); test.describe('V. User & System Management', () => { test('5.1. A. User Profile - Update Personal Information', async ({ page, createUniqueUser }) => { // Seed: Log in as a regular user const user = await createUniqueUser(); await page.goto('/'); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password); await page.getByRole('button', { name: 'Login' }).click(); // Handle potential first-time login try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { // Ignore timeout if it proceeds } await expect(page.getByText('Free Workout')).toBeVisible(); // 2. Navigate to the 'Profile' section. await page.getByRole('button', { name: 'Profile' }).click(); // 3. Modify 'Weight', 'Height', 'Birth Date', and 'Gender'. await page.getByTestId('profile-weight-input').fill('75'); await page.getByTestId('profile-height-input').fill('180'); await page.getByTestId('profile-birth-date').fill('1990-01-01'); await page.getByTestId('profile-gender').selectOption('FEMALE'); // 4. Click 'Save Profile'. await page.getByRole('button', { name: 'Save Profile' }).click(); await expect(page.getByText('Profile saved successfully')).toBeVisible(); // Verify persistence await page.reload(); // After reload, we might be on dashboard or profile depending on app routing, but let's ensure we go to profile if (!await page.getByRole('heading', { name: 'Profile' }).isVisible()) { await page.getByRole('button', { name: 'Profile' }).click(); } // Verify values await expect(page.getByTestId('profile-weight-input')).toHaveValue('75'); await expect(page.getByTestId('profile-height-input')).toHaveValue('180'); await expect(page.getByTestId('profile-birth-date')).toHaveValue('1990-01-01'); await expect(page.getByTestId('profile-gender')).toHaveValue('FEMALE'); }); test('5.2. A. User Profile - Change Password', async ({ page, createUniqueUser }) => { // Seed: Log in as a regular user const user = await createUniqueUser(); await page.goto('/'); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password); await page.getByRole('button', { name: 'Login' }).click(); // Handle potential first-time login try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { // Ignore timeout } await expect(page.getByText('Free Workout')).toBeVisible(); // 2. Navigate to the 'Profile' section. await page.getByRole('button', { name: 'Profile' }).click(); // 3. Enter a new password (min 4 characters) in the 'Change Password' field. const newPassword = 'NewStrongPass!'; await page.getByRole('textbox', { name: 'New Password' }).fill(newPassword); // 4. Click 'OK'. await page.getByRole('button', { name: 'OK' }).click(); await expect(page.getByText('Password changed')).toBeVisible(); // Verify: The user can log in with the new password. // Logout first await page.getByRole('button', { name: 'Logout' }).click(); // Login with new password await page.getByRole('textbox', { name: 'Email' }).fill(user.email); await page.getByRole('textbox', { name: 'Password' }).fill(newPassword); await page.getByRole('button', { name: 'Login' }).click(); await expect(page.getByText('Free Workout')).toBeVisible(); }); test('5.3. A. User Profile - Change Password (Too Short)', async ({ page, createUniqueUser }) => { // Seed const user = await createUniqueUser(); await page.goto('/'); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } await expect(page.getByText('Free Workout')).toBeVisible(); // 2. Navigate to Profile await page.getByRole('button', { name: 'Profile' }).click(); // 3. Enter short password await page.getByRole('textbox', { name: 'New Password' }).fill('123'); // 4. Click OK await page.getByRole('button', { name: 'OK' }).click(); // Expect Error await expect(page.getByText('Password too short')).toBeVisible(); }); test('5.4. A. User Profile - Dedicated Daily Weight Logging', async ({ page, createUniqueUser }) => { // Seed const user = await createUniqueUser(); await page.goto('/'); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } await expect(page.getByText('Free Workout')).toBeVisible(); // 2. Navigate to Profile await page.getByRole('button', { name: 'Profile' }).click(); // 3. Expand Weight Tracker await page.getByRole('button', { name: 'Weight Tracker' }).click(); // 4. Enter weight const weight = '72.3'; await page.getByPlaceholder('Enter weight...').fill(weight); // 5. Click Log await page.getByRole('button', { name: 'Log', exact: true }).click(); // Expect success message await expect(page.getByText('Weight logged successfully')).toBeVisible(); // Expect record in history await expect(page.getByText(`${weight} kg`)).toBeVisible(); // Check if profile weight updated await expect(page.getByRole('spinbutton').first()).toHaveValue(weight); }); test('5.5. A. User Profile - Language Preference Change', async ({ page, createUniqueUser }) => { // 1. Log in as a regular user. const user = await createUniqueUser(); await page.goto('/'); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password); await page.getByRole('button', { name: 'Login' }).click(); // Handle First Time Password Change if it appears try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { // Ignore timeout } await expect(page.getByText('Free Workout')).toBeVisible(); // 2. Navigate to the 'Profile' section. await page.getByRole('button', { name: 'Profile' }).click(); // 3. Select a different language (e.g., 'Русский') from the language dropdown. await page.getByRole('combobox').nth(1).selectOption(['ru']); // Value is 'ru' // 4. Click 'Save Profile'. await page.getByRole('button', { name: /Сохранить профиль|Save Profile/ }).click(); // Expected Results: The UI language immediately switches to the selected language. await expect(page.getByRole('heading', { name: 'Профиль', exact: true })).toBeVisible(); await expect(page.getByText(/Profile saved|Профиль успешно/)).toBeVisible(); // Wait for persistence await expect(page.getByRole('button', { name: 'Сохранить профиль' })).toBeVisible(); // Expected Results: The preference persists across sessions. await page.reload(); // Check if we are still logged in or need to login if (await page.getByLabel('Email').isVisible()) { await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password || 'StrongNewPass123!'); await page.getByRole('button', { name: 'Login' }).click(); } // Verify language is still Russian await page.getByRole('button', { name: /Профиль|Profile/ }).click(); await expect(page.getByRole('heading', { name: 'Профиль', exact: true })).toBeVisible(); }); test('5.6. A. User Profile - Delete Own Account', async ({ page, createUniqueUser }) => { const user = await createUniqueUser(); await page.goto('/'); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } await expect(page.getByText('Free Workout')).toBeVisible(); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: 'Delete' }).click(); await expect(page.getByText('Are you sure?')).toBeVisible(); await page.getByRole('button', { name: 'Delete', exact: true }).last().click(); await expect(page).toHaveURL(/\/login/); await expect(page.getByRole('button', { name: 'Login' })).toBeVisible(); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password || 'StrongNewPass123!'); await page.getByRole('button', { name: 'Login' }).click(); await expect(page.getByText(/Invalid credentials|User not found/i)).toBeVisible(); }); // --- Admin Panel Tests --- test('5.7. B. Admin Panel - View User List', async ({ page, createAdminUser, request }) => { test.setTimeout(120000); // Extend timeout for multiple user creation const adminUser = await createAdminUser(); // Create 25 users to populate the list using Promise.all for parallelism const createdEmails: string[] = []; const creationPromises = []; for (let i = 0; i < 25; i++) { const uniqueId = Math.random().toString(36).substring(7); const email = `list.user.${i}.${uniqueId}@example.com`; const password = 'StrongPassword123!'; createdEmails.push(email); creationPromises.push(request.post('/api/auth/register', { data: { email, password } })); } const responses = await Promise.all(creationPromises); for (const response of responses) { await expect(response).toBeOK(); } await page.goto('/'); await page.getByLabel('Email').fill(adminUser.email); await page.getByLabel('Password').fill(adminUser.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongAdminNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } await expect(page.getByText('Free Workout')).toBeVisible(); await page.getByRole('button', { name: 'Profile' }).click(); // Expand Users List (Admin Area is a header) await page.getByRole('button', { name: /Users List|User List/i }).click(); await expect(page.getByText(/Users List/i)).toBeVisible(); // Verify all created users are visible in the list for (const email of createdEmails) { await expect(page.getByText(email)).toBeVisible(); } }); test('5.8. B. Admin Panel - Create New User', async ({ page, createAdminUser }) => { const adminUser = await createAdminUser(); await page.goto('/'); await page.getByLabel('Email').fill(adminUser.email); await page.getByLabel('Password').fill(adminUser.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongAdminNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } await expect(page.getByText('Free Workout')).toBeVisible(); await page.getByRole('button', { name: 'Profile' }).click(); const uniqueId = Math.random().toString(36).substring(7); const newUserEmail = `new.user.${uniqueId}@example.com`; const newUserPassword = 'NewUserPass123!'; const createUserSection = page.locator('div').filter({ has: page.getByRole('heading', { name: 'Create User' }) }).last(); await createUserSection.getByLabel('Email').fill(newUserEmail); await createUserSection.getByLabel('Password').fill(newUserPassword); await page.getByRole('button', { name: /Create User|Create/i }).click(); await expect(page.getByText(/User created|successfully/i)).toBeVisible(); const userListButton = page.getByRole('button', { name: /Users List/i }); if (await userListButton.getAttribute('aria-expanded') !== 'true') { await userListButton.click(); } const listContainer = page.locator('div.space-y-4.mt-4'); await expect(listContainer).toBeVisible(); await expect(listContainer.getByText(newUserEmail)).toBeVisible(); }); test('5.9. B. Admin Panel - Block/Unblock User', async ({ page, createAdminUser, createUniqueUser }) => { const adminUser = await createAdminUser(); // 1. Login as Admin await page.goto('/'); await page.getByLabel('Email').fill(adminUser.email); await page.getByLabel('Password').fill(adminUser.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongAdminNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } await expect(page.getByText('Free Workout')).toBeVisible(); console.log('Logged in as Admin'); // 2. Create a Regular User (via API) const regularUser = await createUniqueUser(); console.log('Regular user created:', regularUser.email); // 3. Navigate to Admin Panel -> User List await page.getByRole('button', { name: 'Profile' }).filter({ visible: true }).click(); // Ensure list is open and valid const userListButton = page.getByRole('button', { name: /Users List/i }); // Check expanded state and Open if currently closed const isExpanded = await userListButton.getAttribute('aria-expanded'); if (isExpanded !== 'true') { await userListButton.click(); } await expect(userListButton).toHaveAttribute('aria-expanded', 'true'); console.log('User list is open'); // Always Refresh to ensure latest users are fetched await Promise.all([ page.waitForResponse(resp => resp.url().includes('/auth/users')), page.getByTitle('Refresh List').click() ]); // Ensure list remained open or re-open it if (await userListButton.getAttribute('aria-expanded') !== 'true') { console.log('List closed after refresh, re-opening...'); await userListButton.click(); } // Verify user row exists // Fallback to CSS selector if data-testid is missing due to build issues const listContainer = page.locator('div.space-y-4.mt-4'); await expect(listContainer).toBeVisible(); const userRow = listContainer.locator('.bg-surface-container-high').filter({ hasText: regularUser.email }).first(); await expect(userRow).toBeVisible(); // 4. Block the User // Use exact name matching or title since we added aria-label const blockButton = userRow.getByRole('button', { name: 'Block', exact: true }); if (await blockButton.count() === 0) { console.log('Block button NOT found!'); // fallback to find any button to see what is there const buttons = await userRow.getByRole('button').all(); console.log('Buttons found in row:', buttons.length); } await expect(blockButton).toBeVisible(); await blockButton.click(); await expect(userRow.getByText(/Blocked|Block/i)).toBeVisible(); // 5. Verify Blocked User Cannot Login // Logout Admin const logoutButton = page.getByRole('button', { name: /Logout/i }); if (await logoutButton.isVisible()) { await logoutButton.click(); } else { await page.getByText(/Logout/i).click(); } await expect(page.getByRole('button', { name: 'Login' })).toBeVisible(); // Attempt Login as Blocked User await page.getByLabel('Email').fill(regularUser.email); await page.getByLabel('Password').fill(regularUser.password); await page.getByRole('button', { name: 'Login' }).click(); // Assert Error Message await expect(page.getByText(/Account is blocked/i)).toBeVisible(); // 6. Unblock the User // Reload to clear any error states/toasts from previous attempt await page.reload(); // Login as Admin again await page.getByLabel('Email').fill(adminUser.email); // Force the new password since we know step 1 changed it await page.getByLabel('Password').fill('StrongAdminNewPass123!'); await page.getByRole('button', { name: 'Login' }).click(); await expect(page.getByText('Free Workout')).toBeVisible(); console.log('Admin logged back in'); await page.waitForTimeout(1000); await page.getByRole('button', { name: 'Profile' }).filter({ visible: true }).click(); // Open list again await userListButton.click(); await page.getByTitle('Refresh List').click(); // Unblock const userRowAfter = listContainer.locator('.bg-surface-container-high').filter({ hasText: regularUser.email }).first(); await expect(userRowAfter).toBeVisible(); await userRowAfter.getByRole('button', { name: 'Unblock', exact: true }).click(); // Wait for UI to update (block icon/text should disappear or change style) // Ideally we check API response or UI change. Assuming "Blocked" text goes away or button changes. // The original code checked for not.toBeVisible of blocked text, let's stick to that or button state await expect(userRowAfter.getByText(/Blocked/i)).not.toBeVisible(); // 7. Verify Unblocked User Can Login await page.getByRole('button', { name: 'Logout' }).click(); await page.getByLabel('Email').fill(regularUser.email); await page.getByLabel('Password').fill(regularUser.password); await page.getByRole('button', { name: 'Login' }).click(); // Check for Change Password (first login) or direct Dashboard await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongUserNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } await expect(page.getByText('Free Workout')).toBeVisible(); }); test('5.10. B. Admin Panel - Reset User Password', async ({ page, createAdminUser, createUniqueUser }) => { const adminUser = await createAdminUser(); await page.goto('/'); await page.getByLabel('Email').fill(adminUser.email); await page.getByLabel('Password').fill(adminUser.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongAdminNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } const regularUser = await createUniqueUser(); const newPassword = 'NewStrongUserPass!'; await page.getByRole('button', { name: 'Profile' }).click(); // Ensure list is open and valid (Reusing logic from 5.9) const userListButton = page.getByRole('button', { name: /Users List/i }); const isExpanded = await userListButton.getAttribute('aria-expanded'); if (isExpanded !== 'true') { await userListButton.click(); } await expect(userListButton).toHaveAttribute('aria-expanded', 'true'); // Always Refresh to ensure latest users are fetched await Promise.all([ page.waitForResponse(resp => resp.url().includes('/auth/users')), page.getByTitle('Refresh List').click() ]); // Ensure list remained open if (await userListButton.getAttribute('aria-expanded') !== 'true') { await userListButton.click(); } const listContainer = page.locator('div.space-y-4.mt-4'); await expect(listContainer).toBeVisible(); const userRow = listContainer.locator('.bg-surface-container-high').filter({ hasText: regularUser.email }).first(); await expect(userRow).toBeVisible(); await userRow.getByRole('textbox').fill(newPassword); page.on('dialog', async dialog => { console.log(`Dialog message: ${dialog.message()}`); await dialog.accept(); }); await userRow.getByRole('button', { name: /Reset Pass/i }).click(); // Wait to ensure the operation completed (the dialog is the signal, but we might need a small buffer or check effect) // Since dialog is handled immediately by listener, we might race. // Better pattern: wait for the button to be enabled again or some UI feedback. // But since we use window.alert, expecting the dialog content is tricky in Playwright if not careful. // Let's add a small pause to allow backend to process before logout. await page.waitForTimeout(1000); await page.getByRole('button', { name: 'Logout' }).click(); await page.getByLabel('Email').fill(regularUser.email); await page.getByLabel('Password').fill(newPassword); await page.getByRole('button', { name: 'Login' }).click(); // After reset, isFirstLogin is true, so we expect Change Password screen await expect(page.getByRole('heading', { name: /Change Password/i })).toBeVisible({ timeout: 10000 }); await page.getByLabel('New Password').fill('BrandNewUserPass1!'); await page.getByRole('button', { name: /Save|Change/i }).click(); await expect(page.getByText('Free Workout')).toBeVisible(); }); test('5.11. B. Admin Panel - Delete User', async ({ page, createAdminUser, createUniqueUser }) => { const adminUser = await createAdminUser(); await page.goto('/'); await page.getByLabel('Email').fill(adminUser.email); await page.getByLabel('Password').fill(adminUser.password); await page.getByRole('button', { name: 'Login' }).click(); try { await expect(page.getByRole('heading', { name: /Change Password/i }).or(page.getByText('Free Workout'))).toBeVisible({ timeout: 5000 }); if (await page.getByRole('heading', { name: /Change Password/i }).isVisible()) { await page.getByLabel('New Password').fill('StrongAdminNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); } } catch (e) { } const userToDelete = await createUniqueUser(); await page.getByRole('button', { name: 'Profile' }).click(); // Ensure list is open and valid (Reusing logic from 5.9) const userListButton = page.getByRole('button', { name: /Users List/i }); const isExpanded = await userListButton.getAttribute('aria-expanded'); if (isExpanded !== 'true') { await userListButton.click(); } await expect(userListButton).toHaveAttribute('aria-expanded', 'true'); // Always Refresh to ensure latest users are fetched await Promise.all([ page.waitForResponse(resp => resp.url().includes('/auth/users')), page.getByTitle('Refresh List').click() ]); // Ensure list remained open if (await userListButton.getAttribute('aria-expanded') !== 'true') { await userListButton.click(); } const listContainer = page.locator('div.space-y-4.mt-4'); await expect(listContainer).toBeVisible(); const userRow = listContainer.locator('.bg-surface-container-high').filter({ hasText: userToDelete.email }).first(); await expect(userRow).toBeVisible(); page.once('dialog', dialog => dialog.accept()); await userRow.getByRole('button', { name: /Delete/i }).click(); await expect(page.getByText(userToDelete.email)).not.toBeVisible(); }); });