import { test, expect } from './fixtures'; import { randomUUID } from 'crypto'; // Helper for setup 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 initAcc = page.getByRole('heading', { name: /Setup Your Account/i }); const dashboard = page.getByText('Free Workout'); await expect(heading.or(initAcc).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(initAcc.or(dashboard)).toBeVisible(); } if (await initAcc.isVisible()) { await page.getByRole('button', { name: /Get Started/i }).click(); await expect(dashboard).toBeVisible(); } } catch (e) { // Login might already be done } return user; } test.describe('III. Workout Tracking', () => { test('3.1 B. Idle State - Start Free Workout', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await expect(page.getByText('Start Empty Workout').or(page.getByText('Free Workout'))).toBeVisible(); await page.locator('div').filter({ hasText: 'Bodyweight (kg)' }).locator('input[type="number"]').fill('75.5'); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await expect(page.getByRole('button', { name: 'Finish' })).toBeVisible(); await expect(page.getByText('Select Exercise')).toBeVisible(); await expect(page.getByText('00:00')).toBeVisible(); await expect(page.getByText('75.5')).toBeVisible(); }); test('3.2 B. Idle State - Start Quick Log', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Quick Log' }).click(); await expect(page.getByText('Quick Log').first()).toBeVisible(); await expect(page.getByText('Select Exercise')).toBeVisible(); }); test('3.3 B. Idle State - Body Weight Defaults from Profile', async ({ page, createUniqueUser, request }) => { const user = await createUniqueUser(); const updateResp = await request.patch('/api/auth/profile', { data: { weight: 75.5 }, headers: { 'Authorization': `Bearer ${user.token}` } }); expect(updateResp.ok()).toBeTruthy(); await page.goto('/'); await page.getByLabel('Email').fill(user.email); await page.getByLabel('Password').fill(user.password); await page.getByRole('button', { name: 'Login' }).click(); const heading = page.getByRole('heading', { name: /Change Password/i }); const initAcc = page.getByRole('heading', { name: /Setup Your Account/i }); const dashboard = page.getByText('Start Empty Workout').or(page.getByText('Free Workout')); await expect(heading.or(initAcc).or(dashboard)).toBeVisible({ timeout: 10000 }); if (await heading.isVisible()) { await page.getByLabel('New Password').fill('StrongNewPass123!'); await page.getByRole('button', { name: /Save|Change/i }).click(); await expect(initAcc.or(dashboard)).toBeVisible(); } if (await initAcc.isVisible()) { await page.getByRole('button', { name: /Get Started/i }).click(); await expect(dashboard).toBeVisible(); } await expect(page.getByText('Start Empty Workout').or(page.getByText('Free Workout'))).toBeVisible(); const weightInput = page.locator('div').filter({ hasText: 'Bodyweight (kg)' }).locator('input[type="number"]'); await expect(weightInput).toBeVisible(); await expect(weightInput).toHaveValue('75.5'); }); test('3.4 C. Active Session - Log Strength Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Bench Press ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByText('Select Exercise').click(); await page.getByText(exName).click(); await page.getByLabel('Weight (kg)').first().fill('80'); await page.getByLabel('Reps').first().fill('5'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('80 kg x 5 reps')).toBeVisible(); }); test('3.5 C. Active Session - Log Bodyweight Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Pull-up ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'BODYWEIGHT' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByText('Select Exercise').click(); await page.getByText(exName).click(); await page.getByLabel(/Add.? Weight/i).first().fill('10'); await page.getByLabel('Reps').first().fill('8'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('+10 kg x 8 reps')).toBeVisible(); await page.getByLabel(/Add.? Weight/i).first().fill('-30'); await page.getByLabel('Reps').first().fill('12'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('-30 kg x 12 reps')).toBeVisible(); }); test('3.6 C. Active Session - Log Cardio Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Running ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'CARDIO' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByText(exName).click(); await page.getByLabel('Time').fill('300'); await page.getByLabel('Distance (m)').fill('1000'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('300s')).toBeVisible(); await expect(page.getByText('1000m')).toBeVisible(); }); test('3.7 C. Active Session - Edit Logged Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Edit Test ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByText(exName).click(); await page.getByLabel('Weight (kg)').first().fill('100'); await page.getByLabel('Reps').first().fill('10'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('100 kg x 10 reps')).toBeVisible(); // Use filter to find the container, then find 'Edit' button inside it const row = page.locator('div.shadow-elevation-1').filter({ hasText: '100 kg x 10 reps' }).first(); // The Edit button might be an icon button or text. Assuming it's the one with 'Edit' text or accessible name await row.getByRole('button', { name: /Edit/i }).click(); // Wait for edit inputs to appear // The modal should be visible const editModal = page.locator('div[role="dialog"]'); await expect(editModal.getByRole('button', { name: 'Save', exact: true })).toBeVisible(); // EditSetModal doesn't use htmlFor, so we find the container with the label await editModal.locator('div.bg-surface-container-high').filter({ hasText: 'Weight (kg)' }).locator('input').fill('105'); await editModal.locator('div.bg-surface-container-high').filter({ hasText: 'Reps' }).locator('input').fill('11'); await editModal.getByRole('button', { name: 'Save', exact: true }).click(); await expect(page.getByText('105 kg x 11 reps')).toBeVisible(); await expect(page.getByText('100 kg x 10 reps')).not.toBeVisible(); }); test('3.8 C. Active Session - Delete Logged Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Delete Test ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByText(exName).click(); await page.getByLabel('Weight (kg)').first().fill('100'); await page.getByLabel('Reps').first().fill('10'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('100 kg x 10 reps')).toBeVisible(); const row = page.locator('div.shadow-elevation-1').filter({ hasText: '100 kg x 10 reps' }).first(); page.on('dialog', dialog => dialog.accept()); await row.getByRole('button', { name: /Delete|Remove/i }).click(); await expect(page.getByText('100 kg x 10 reps')).not.toBeVisible(); }); test('3.9 C. Active Session - Finish Session', async ({ page, createUniqueUser }) => { const user = await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('button', { name: 'Finish' }).click(); await page.getByRole('button', { name: 'Confirm' }).click(); await expect(page.getByText(/Free Workout|Start Empty/i)).toBeVisible(); await page.getByRole('button', { name: 'History' }).click(); await expect(page.getByText('No plan').first()).toBeVisible(); await expect(page.getByText('Sets: 0').first()).toBeVisible(); }); test('3.10 C. Active Session - Quit Session Without Saving', async ({ page, createUniqueUser }) => { const user = await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('button', { name: 'Options' }).click(); await page.getByText(/Quit/i).click(); await page.getByRole('button', { name: 'Confirm' }).click(); await expect(page.getByText(/Free Workout|Start Empty/i)).toBeVisible(); }); test('3.11 C. Active Session - Plan Progression and Jump to Step', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const ex1Id = randomUUID(); const ex2Id = randomUUID(); const ex3Id = 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}` } }); await request.post('/api/exercises', { data: { id: ex3Id, name: 'Ex Three', type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); const planId = randomUUID(); await request.post('/api/plans', { data: { id: planId, name: 'Progression Plan', steps: [ { exerciseId: ex1Id }, { exerciseId: ex2Id }, { exerciseId: ex3Id } ] }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: 'Plans' }).click(); const card = page.locator('div').filter({ hasText: 'Progression Plan' }).last(); // Assuming there isn't a direct start button on the card list without expansion, // but often there is. If not, click card then start. // Assuming direct start or via expand. // Let's try to find Start button in the card if (await card.getByRole('button', { name: 'Start' }).isVisible()) { await card.getByRole('button', { name: 'Start' }).click(); } else { // Click card to expand details then start? // Or check if we are in Plans view. } // Fallback: await page.locator('div').filter({ hasText: 'Progression Plan' }).getByRole('button', { name: 'Start' }).click(); // Prepare modal const modal = page.locator('.fixed.inset-0.z-50'); if (await modal.isVisible()) { await modal.getByRole('button', { name: 'Start' }).click(); } await expect(page.getByText('Ex One')).toBeVisible(); await page.getByLabel('Weight (kg)').first().fill('50'); await page.getByLabel('Reps').first().fill('10'); await page.getByRole('button', { name: /Log Set/i }).click(); await page.getByText(/Step \d+ of \d+/i).click(); await page.getByRole('button', { name: /Ex Three/i }).click(); await expect(page.getByText('Ex Three')).toBeVisible(); }); test('3.12 D. Sporadic Logging - Log Strength Sporadic Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Quick Ex ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Quick Log/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByText(exName).click(); await page.getByLabel(/Weight/i).first().fill('60'); await page.getByLabel(/Reps/i).first().fill('8'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('60 kg x 8 reps')).toBeVisible(); }); test('3.13 D. Sporadic Logging - Exercise Search and Clear', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const benchPressName = 'Bench Press ' + randomUUID().slice(0, 4); const benchDipName = 'Bench Dip ' + randomUUID().slice(0, 4); const squatName = 'Squat ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: benchPressName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await request.post('/api/exercises', { data: { name: benchDipName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await request.post('/api/exercises', { data: { name: squatName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Quick Log/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).fill(benchPressName.substring(0, 4)); // "Benc" await expect(page.getByText(benchPressName)).toBeVisible(); await expect(page.getByText(benchDipName)).toBeVisible(); await expect(page.getByText(squatName)).not.toBeVisible(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).fill(''); await expect(page.getByText(squatName)).toBeVisible(); }); test('3.14 C. Active Session - Log Unilateral Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Uni Row ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH', isUnilateral: true }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).fill(exName); await page.getByText(exName).click(); await expect(page.getByRole('button', { name: 'L', exact: true })).toBeVisible(); // Helper to log a set const logSet = async (side: 'L' | 'R' | 'A') => { const logger = page.locator('div').filter({ has: page.getByRole('button', { name: /Log Set|Saved/i }) }).last(); await logger.getByRole('button', { name: side, exact: true }).click(); const weightInput = logger.getByLabel('Weight (kg)'); await weightInput.click(); await weightInput.fill('20'); await logger.getByLabel('Reps').fill('10'); await logger.getByRole('button', { name: /Log Set|Saved/i }).click(); }; await logSet('L'); await expect(page.getByText('Left', { exact: true })).toBeVisible(); await logSet('R'); await expect(page.getByText('Right', { exact: true })).toBeVisible(); const rightSetRow = page.locator('.bg-surface-container.rounded-xl.shadow-elevation-1').first(); await rightSetRow.getByRole('button', { name: 'Edit' }).click(); const editModal = page.locator('div[role="dialog"]'); const saveButton = editModal.getByRole('button', { name: /Save/i }); const aButton = editModal.getByRole('button', { name: 'A', exact: true }); await aButton.click(); await saveButton.click(); await expect(page.getByText(/Alternately/i)).toBeVisible(); }); test('3.15 C. Active Session - Log Special Type Set', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const plankName = 'Plank ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: plankName, type: 'STATIC' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByText(plankName).click(); await page.getByLabel('Time (sec)').fill('60'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('60s')).toBeVisible(); }); test('3.16 C. Active Session - Log Set with Default Reps', async ({ page, createUniqueUser, request }) => { const user = await loginAndSetup(page, createUniqueUser); const exName = 'Default Reps ' + randomUUID().slice(0, 4); await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('textbox', { name: /Select Exercise/i }).click(); await page.getByText(exName).click(); await page.getByLabel('Weight (kg)').first().fill('50'); await page.getByRole('button', { name: /Log Set/i }).click(); await expect(page.getByText('50 kg x 1 reps')).toBeVisible(); }); test('3.17 B. Idle State - Days Off Training Logic', async ({ page, createUniqueUser }) => { const user = await loginAndSetup(page, createUniqueUser); await expect(page.getByText('Do your very first workout today.')).toBeVisible(); await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); await page.getByRole('button', { name: 'Finish' }).click(); await page.getByRole('button', { name: 'Confirm' }).click(); await expect(page.getByText('Last workout: Today')).toBeVisible(); }); test.describe('Rest Timer', () => { // Merged from rest-timer.spec.ts test('3.16 C. Rest Timer - Manual Edit & Validation', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Free Workout' }).click(); const fab = page.locator('.fixed.bottom-24.right-6'); await fab.click(); const editBtn = fab.locator('button[aria-label="Edit"]'); await editBtn.click(); const timerInput = page.getByRole('textbox').nth(1); await timerInput.fill('90'); await expect(timerInput).toHaveValue('90'); await timerInput.fill('10:99'); await expect(timerInput).toHaveValue('10:59'); const saveBtn = fab.locator('button[aria-label="Save"]'); await saveBtn.click(); }); test('3.17 C. Rest Timer - Context & Persistence', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Free Workout' }).click(); const fab = page.locator('.fixed.bottom-24.right-6'); await fab.click(); const editBtn = fab.locator('button[aria-label="Edit"]'); await editBtn.click(); await page.getByRole('textbox').nth(1).fill('45'); await fab.locator('button[aria-label="Save"]').click(); await page.getByRole('button', { name: 'Finish' }).click(); await page.getByRole('button', { name: 'Confirm' }).click(); await page.getByRole('button', { name: 'Quick Log' }).click(); const quickFab = page.locator('.fixed.bottom-24.right-6'); await quickFab.click(); await expect(page.locator('div').filter({ hasText: /0:45/ }).first()).toBeVisible(); }); test('3.18 C. Rest Timer - Plan Integration', async ({ page, createUniqueUser }) => { await loginAndSetup(page, createUniqueUser); await page.getByRole('button', { name: 'Plans' }).click(); await page.getByRole('button', { name: 'Create Plan' }).click(); await expect(page.getByRole('button', { name: 'Manually' })).toBeVisible(); await page.getByRole('button', { name: 'Manually' }).click(); await page.getByRole('textbox', { name: 'Name' }).fill('Timer Test Plan'); await page.getByRole('button', { name: 'Add Exercise' }).click(); await page.getByRole('button', { name: 'New Exercise' }).click(); // Scope to the top-most dialog for the new exercise form const newExerciseModal = page.locator('div[role="dialog"]').last(); await newExerciseModal.getByLabel('Name').fill('Bench Press Test'); await newExerciseModal.getByRole('button', { name: 'Free Weights & Machines' }).click(); await newExerciseModal.getByRole('button', { name: 'Create' }).click(); // Rest input is on the plan step card now await expect(page.getByTestId('plan-exercise-item').filter({ hasText: 'Bench Press Test' })).toBeVisible(); await page.locator('input[placeholder="Rest (s)"]').last().fill('30'); await page.getByRole('button', { name: 'Add Exercise' }).click(); await page.getByRole('button', { name: 'New Exercise' }).click(); await newExerciseModal.getByLabel('Name').fill('Squat Test'); await newExerciseModal.getByRole('button', { name: 'Free Weights & Machines' }).click(); await newExerciseModal.getByRole('button', { name: 'Create' }).click(); await expect(page.getByTestId('plan-exercise-item').filter({ hasText: 'Squat Test' })).toBeVisible(); await page.locator('input[placeholder="Rest (s)"]').last().fill('60'); await page.getByRole('button', { name: 'Save' }).click(); await page.getByRole('button', { name: 'Start' }).click(); const modal = page.locator('.fixed.inset-0.z-50'); if (await modal.isVisible()) { await modal.getByRole('button', { name: 'Start' }).click(); } const fab = page.locator('.fixed.bottom-24.right-6'); await fab.click(); await expect(page.locator('div').filter({ hasText: /0:30/ }).first()).toBeVisible(); await fab.locator('button[aria-label="Start"]').click(); await page.getByRole('button', { name: 'Log Set' }).click(); await expect(page.locator('div').filter({ hasText: /0:2[0-9]/ }).first()).toBeVisible(); const resetBtn = fab.locator('button[aria-label="Reset"]'); await resetBtn.click(); await expect(page.locator('div').filter({ hasText: /1:00/ }).first()).toBeVisible(); }); }); });