import { test, expect } from './fixtures'; import { randomUUID } from 'crypto'; test.describe('II. Workout Management', () => { test('2.1 A. Workout Plans - Create New Plan', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); // Seed exercise const seedResp = await request.post('/api/exercises', { data: { name: 'Test Sq', type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); expect(seedResp.ok()).toBeTruthy(); await page.reload(); await page.getByRole('button', { name: 'Plans' }).first().click(); await page.getByRole('button', { name: 'Create Plan' }).click(); // Wait for potential animation/loading await expect(page.getByText('Plan Editor')).toBeVisible({ timeout: 10000 }); await page.getByLabel(/Name/i).fill('My New Strength Plan'); await page.getByPlaceholder(/Describe preparation/i).fill('Focus on compound lifts'); await page.getByRole('button', { name: 'Add Exercise' }).click(); await expect(page.getByText('Select Exercise')).toBeVisible(); await page.getByText('Test Sq').click(); await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('My New Strength Plan')).toBeVisible(); await expect(page.getByText('Focus on compound lifts')).toBeVisible(); }); test('2.2 A. Workout Plans - Edit Existing Plan', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const seedResp = await request.post('/api/plans', { data: { id: randomUUID(), name: 'Original Plan', description: 'Original Description', steps: [] }, headers: { 'Authorization': `Bearer ${user.token}` } }); expect(seedResp.ok()).toBeTruthy(); await page.reload(); await page.getByRole('button', { name: 'Plans' }).first().click(); await expect(page.getByText('Original Plan')).toBeVisible(); const card = page.locator('div') .filter({ hasText: 'Original Plan' }) .filter({ has: page.getByRole('button', { name: 'Edit Plan' }) }) .last(); await card.getByRole('button', { name: 'Edit Plan' }).click(); await page.getByLabel(/Name/i).fill('Updated Plan Name'); await page.getByRole('button', { name: 'Save' }).click(); await expect(page.getByText('Updated Plan Name')).toBeVisible(); await expect(page.getByText('Original Plan')).not.toBeVisible(); }); test('2.3 A. Workout Plans - Delete Plan', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const resp = await request.post('/api/plans', { data: { id: randomUUID(), name: 'Plan To Delete', description: 'Delete me', steps: [] }, headers: { 'Authorization': `Bearer ${user.token}` } }); expect(resp.ok()).toBeTruthy(); await page.reload(); await page.getByRole('button', { name: 'Plans' }).first().click(); page.on('dialog', dialog => dialog.accept()); const card = page.locator('div') .filter({ hasText: 'Plan To Delete' }) .filter({ has: page.getByRole('button', { name: 'Delete Plan' }) }) .last(); await card.getByRole('button', { name: 'Delete Plan' }).click(); await expect(page.getByText('Plan To Delete')).not.toBeVisible(); }); test('2.4 A. Workout Plans - Reorder Exercises', async ({ page, createUniqueUser, request }) => { page.on('console', msg => console.log('PAGE LOG:', msg.text())); const user = await loginAndSetup(page, createUniqueUser); // Need exercises const ex1Id = randomUUID(); const ex2Id = randomUUID(); await request.post('/api/exercises', { data: { id: ex1Id, name: 'Ex One', type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await request.post('/api/exercises', { data: { id: ex2Id, name: 'Ex Two', type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); const planId = randomUUID(); await request.post('/api/plans', { data: { id: planId, name: 'Reorder Plan', description: 'Testing reorder', steps: [ { exerciseId: ex1Id, isWeighted: false }, { exerciseId: ex2Id, isWeighted: false } ] }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.reload(); await page.getByRole('button', { name: 'Plans' }).first().click(); // Use the new aria-label selector const card = page.locator('div') .filter({ hasText: 'Reorder Plan' }) .filter({ has: page.getByRole('button', { name: 'Edit Plan' }) }) .last(); await card.getByRole('button', { name: 'Edit Plan' }).click(); const card1 = page.locator('[draggable="true"]').filter({ hasText: 'Ex One' }); const card2 = page.locator('[draggable="true"]').filter({ hasText: 'Ex Two' }); // Initial state check await expect(page.locator('[draggable="true"]').first()).toContainText('Ex One'); // Drag using handles with explicit wait const sourceHandle = card1.locator('.lucide-grip-vertical'); const targetHandle = card2.locator('.lucide-grip-vertical'); await expect(sourceHandle).toBeVisible(); await expect(targetHandle).toBeVisible(); console.log('Starting Drag...'); await sourceHandle.dragTo(targetHandle); console.log('Drag complete'); // Wait for reorder to settle await page.waitForTimeout(1000); // Verify Swap immediately await expect(page.locator('[draggable="true"]').first()).toContainText('Ex Two'); await page.getByRole('button', { name: 'Save' }).click(); // Reload and verify persistence await page.reload(); await page.getByRole('button', { name: 'Plans' }).first().click(); const cardRevisit = page.locator('div') .filter({ hasText: 'Reorder Plan' }) .filter({ has: page.getByRole('button', { name: 'Edit Plan' }) }) .last(); await cardRevisit.getByRole('button', { name: 'Edit Plan' }).click(); await expect(page.locator('[draggable="true"]').first()).toContainText('Ex Two'); await expect(page.locator('[draggable="true"]').last()).toContainText('Ex One'); }); test('2.5 A. Workout Plans - Start Session from Plan', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const resp = await request.post('/api/plans', { data: { id: randomUUID(), name: 'Startable Plan', description: 'Ready to go', steps: [] }, headers: { 'Authorization': `Bearer ${user.token}` } }); console.log(await resp.json()); expect(resp.ok()).toBeTruthy(); await page.reload(); await page.getByRole('button', { name: 'Plans' }).first().click(); const card = page.locator('div') .filter({ hasText: 'Startable Plan' }) .filter({ has: page.getByRole('button', { name: 'Start' }) }) .last(); await card.getByRole('button', { name: 'Start' }).click(); await expect(page.getByText('Startable Plan', { exact: false })).toBeVisible(); await expect(page.getByRole('button', { name: 'Finish' })).toBeVisible(); }); // --- Exercise Tests --- test('2.6 B. Exercise Library - Create Custom Exercise (Strength)', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Profile' }).click(); await page.locator('button:has-text("Manage Exercises")').click(); // Use force click as button might be obstructed or animating await page.getByRole('button', { name: /New Exercise/i }).click({ force: true }); await expect(page.locator('div[role="dialog"]')).toBeVisible(); await page.locator('div[role="dialog"]').getByLabel('Name').fill('Custom Bicep Curl'); await expect(page.locator('div[role="dialog"]').getByText('Free Weights & Machines', { exact: false })).toBeVisible(); await page.locator('div[role="dialog"]').getByRole('button', { name: 'Create' }).click(); await page.waitForTimeout(1000); // Reload and filter await page.reload(); await page.getByRole('button', { name: 'Profile' }).click(); await page.locator('button:has-text("Manage Exercises")').click(); await page.getByLabel(/Filter by name/i).fill('Custom Bicep Curl'); await expect(page.getByText('Custom Bicep Curl')).toBeVisible(); }); test('2.7 B. Exercise Library - Create Custom Exercise (Bodyweight)', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: /Manage Exercises/i }).click(); await page.getByRole('button', { name: /New Exercise/i }).click({ force: true }); await expect(page.locator('div[role="dialog"]')).toBeVisible(); await page.locator('div[role="dialog"]').getByLabel('Name').fill('Adv Pushup'); // Scope to dialog and use force click for type selection await page.locator('div[role="dialog"]').getByRole('button', { name: /Bodyweight/i }).click({ force: true }); await expect(page.getByLabel('Body Weight')).toBeVisible(); await page.getByLabel('Body Weight').fill('50'); await page.locator('div[role="dialog"]').getByRole('button', { name: 'Create' }).click(); await page.waitForTimeout(1000); // Reload and filter await page.reload(); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: /Manage Exercises/i }).click(); await page.getByLabel(/Filter by name/i).fill('Adv Pushup'); await expect(page.getByText('Adv Pushup')).toBeVisible(); await expect(page.getByText('Bodyweight', { exact: false }).first()).toBeVisible(); }); test('2.8 B. Exercise Library - Edit Exercise Name', async ({ page, createUniqueUser }) => { // Updated to use UI creation for robustness await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Profile' }).click(); await page.locator('button:has-text("Manage Exercises")').click(); // Use force click as button might be obstructed or animating await page.getByRole('button', { name: /New Exercise/i }).click({ force: true }); await expect(page.locator('div[role="dialog"]')).toBeVisible(); await page.locator('div[role="dialog"]').getByLabel('Name').fill('Typo Name'); await expect(page.locator('div[role="dialog"]').getByText('Free Weights & Machines', { exact: false })).toBeVisible(); await page.locator('div[role="dialog"]').getByRole('button', { name: 'Create' }).click(); await page.waitForTimeout(1000); // Reload and filter await page.reload(); await page.getByRole('button', { name: 'Profile' }).click(); await page.locator('button:has-text("Manage Exercises")').click(); await page.getByLabel(/Filter by name/i).fill('Typo Name'); await expect(page.getByText('Typo Name')).toBeVisible(); // Filter specifically for the container that has both text and button const row = page.locator('div') .filter({ hasText: 'Typo Name' }) .filter({ has: page.getByLabel('Edit Exercise') }) .last(); await expect(row).toBeVisible(); await row.getByLabel('Edit Exercise').click(); await page.locator('div[role="dialog"] input').first().fill('Fixed Name'); await page.locator('div[role="dialog"]').getByRole('button', { name: 'Save', exact: true }).click(); // Clear filter to see the renamed exercise await page.getByLabel(/Filter by name/i).fill(''); await expect(page.getByText('Fixed Name')).toBeVisible(); await expect(page.getByText('Typo Name')).not.toBeVisible(); }); test('2.9 B. Exercise Library - Archive/Unarchive', async ({ page, createUniqueUser }) => { // Updated to use UI creation for robustness await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Profile' }).click(); await page.locator('button:has-text("Manage Exercises")').click(); // Use force click as button might be obstructed or animating await page.getByRole('button', { name: /New Exercise/i }).click({ force: true }); await expect(page.locator('div[role="dialog"]')).toBeVisible(); await page.locator('div[role="dialog"]').getByLabel('Name').fill('Archive Me'); await expect(page.locator('div[role="dialog"]').getByText('Free Weights & Machines', { exact: false })).toBeVisible(); await page.locator('div[role="dialog"]').getByRole('button', { name: 'Create' }).click(); await page.waitForTimeout(1000); // Reload and filter await page.reload(); await page.getByRole('button', { name: 'Profile' }).click(); await page.locator('button:has-text("Manage Exercises")').click(); await page.getByLabel(/Filter by name/i).fill('Archive Me'); await expect(page.getByText('Archive Me')).toBeVisible(); const row = page.locator('div.flex.justify-between').filter({ hasText: 'Archive Me' }).last(); // Archive button (box-archive or similar) await row.locator('[aria-label="Archive Exercise"]').click(); // It should disappear or fade. "Show Archived" is false by default. await expect(page.getByText('Archive Me')).not.toBeVisible(); // Toggle Show Archived // Label might not be linked, so we filter by text and find the adjacent checkbox await page.locator('div').filter({ hasText: /Show Archived/i }).last().locator('input[type="checkbox"]').check(); await expect(page.getByText('Archive Me')).toBeVisible(); // Unarchive const archivedRow = page.locator('div') .filter({ hasText: 'Archive Me' }) .filter({ has: page.getByLabel('Unarchive Exercise') }) .last(); await archivedRow.getByLabel('Unarchive Exercise').click(); // Verify it persists after unchecking "Show Archived" await page.locator('div').filter({ hasText: /Show Archived/i }).last().locator('input[type="checkbox"]').uncheck(); await expect(page.getByText('Archive Me')).toBeVisible(); }); test('2.10 B. Exercise Library - Filter by Name', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); await request.post('/api/exercises', { data: { name: 'FindThisOne', type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await request.post('/api/exercises', { data: { name: 'IgnoreThatOne', type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.reload(); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: /Manage Exercises/i }).click(); await page.getByLabel(/Filter by name/i).fill('FindThis'); await expect(page.getByText('FindThisOne')).toBeVisible(); await expect(page.getByText('IgnoreThatOne')).not.toBeVisible(); }); test('2.11 B. Exercise Library - Capitalization (Mobile)', async ({ page, createUniqueUser }) => { // Simulate Mobile Viewport await page.setViewportSize({ width: 390, height: 844 }); // iPhone 12 Pro await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Profile' }).click(); await page.locator('button:has-text("Manage Exercises")').click(); // Use force as FAB might be different on mobile, but text is same await page.getByRole('button', { name: /New Exercise/i }).click({ force: true }); await expect(page.locator('div[role="dialog"]')).toBeVisible(); // Verify autocapitalize attribute is set to 'words' or 'sentences' // In ExerciseModal.tsx it is set to 'words' const nameInput = page.locator('div[role="dialog"]').getByLabel('Name'); await expect(nameInput).toHaveAttribute('autocapitalize', 'words'); }); test('2.12 B. Exercise Library - Unilateral', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: /Manage Exercises/i }).click(); await page.getByRole('button', { name: /New Exercise/i }).click({ force: true }); await expect(page.locator('div[role="dialog"]')).toBeVisible(); await page.locator('div[role="dialog"]').getByLabel('Name').fill('Single Leg Squat'); await page.getByLabel(/Unilateral exercise/).check(); await page.locator('div[role="dialog"]').getByRole('button', { name: 'Create' }).click(); await page.waitForTimeout(1000); // Reload and filter await page.reload(); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: /Manage Exercises/i }).click(); await page.getByLabel(/Filter by name/i).fill('Single Leg Squat'); await expect(page.getByText('Single Leg Squat')).toBeVisible(); await expect(page.getByText('Unilateral', { exact: false }).first()).toBeVisible(); }); test('2.13 B. Exercise Library - Special Types', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: /Manage Exercises/i }).click(); await page.getByRole('button', { name: /New Exercise/i }).click({ force: true }); await expect(page.locator('div[role="dialog"]')).toBeVisible(); await page.locator('div[role="dialog"]').getByLabel('Name').fill('Plank Test'); await page.locator('div[role="dialog"]').getByRole('button', { name: /Static/i }).click({ force: true }); await page.locator('div[role="dialog"]').getByRole('button', { name: 'Create' }).click(); await page.waitForTimeout(1000); // Reload and filter await page.reload(); await page.getByRole('button', { name: 'Profile' }).click(); await page.getByRole('button', { name: /Manage Exercises/i }).click(); await page.getByLabel(/Filter by name/i).fill('Plank Test'); await expect(page.getByText('Plank Test')).toBeVisible(); await expect(page.getByText('Static', { exact: false }).first()).toBeVisible(); }); }); async function loginAndSetup(page: any, createUniqueUser: any) { 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 { const heading = page.getByRole('heading', { name: /Change Password/i }); const dashboard = page.getByText('Free Workout'); await expect(heading.or(dashboard)).toBeVisible({ timeout: 5000 }); if (await heading.isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); await expect(dashboard).toBeVisible(); } } catch (e) { // Login might already be done or dashboard loaded fast } return user; }