import { test, expect, type Page } from '@playwright/test'; async function login(page: Page) { await page.goto('/login'); await page.fill('input[type="email"]', 'superuser@headroom.test'); await page.fill('input[type="password"]', 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard'); } async function getAccessToken(page: Page): Promise { return (await page.evaluate(() => localStorage.getItem('headroom_access_token'))) as string; } async function setPeriod(page: Page, period = '2026-02') { await page.evaluate((value) => { localStorage.setItem('headroom_selected_period', value); }, period); } async function goToCapacity(page: Page) { await page.goto('/capacity'); await expect(page).toHaveURL(/\/capacity/); // Click on Calendar tab to ensure it's active await page.getByRole('button', { name: 'Calendar' }).click(); // Wait for team member selector to be visible await expect(page.locator('select[aria-label="Select team member"]')).toBeVisible({ timeout: 10000 }); // Wait a moment for data to load await page.waitForTimeout(500); } // Backend API base URL (direct access since Vite proxy doesn't work in test env) const API_BASE = 'http://localhost:3000'; async function createTeamMember(page: Page, token: string, overrides: Record = {}) { const payload = { name: `Capacity Tester ${Date.now()}`, role_id: 1, hourly_rate: 150, active: true, ...overrides }; const response = await page.request.post(`${API_BASE}/api/team-members`, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, data: payload }); return response.json(); } async function createHoliday(page: Page, token: string, payload: { date: string; name: string; description?: string }) { return page.request.post(`${API_BASE}/api/holidays`, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, data: payload }); } async function createPTO(page: Page, token: string, payload: { team_member_id: string; start_date: string; end_date: string; reason?: string }) { return page.request.post(`${API_BASE}/api/ptos`, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, data: payload }); } test.describe('Capacity Planning - Phase 1 Tests (RED)', () => { let authToken: string; let mainMemberId: string; let createdMembers: string[] = []; let createdHolidayIds: string[] = []; test.beforeEach(async ({ page }) => { createdMembers = []; createdHolidayIds = []; await login(page); authToken = await getAccessToken(page); await setPeriod(page, '2026-02'); const member = await createTeamMember(page, authToken); mainMemberId = member.id; createdMembers.push(mainMemberId); await goToCapacity(page); await page.selectOption('select[aria-label="Select team member"]', mainMemberId); // Wait for calendar to be visible after selecting team member await expect(page.getByTestId('capacity-calendar')).toBeVisible({ timeout: 10000 }); }); test.afterEach(async ({ page }) => { for (const holidayId of createdHolidayIds.splice(0)) { await page.request.delete(`${API_BASE}/api/holidays/${holidayId}`, { headers: { Authorization: `Bearer ${authToken}` } }).catch(() => null); } for (const memberId of createdMembers.splice(0)) { await page.request.delete(`${API_BASE}/api/team-members/${memberId}`, { headers: { Authorization: `Bearer ${authToken}` } }).catch(() => null); } }); test.fixme('4.1.1 Calculate capacity for full month', async ({ page }) => { const card = page.getByTestId('team-capacity-card'); await expect(card).toContainText('20.0d'); await expect(card).toContainText('160 hrs'); }); test.fixme('4.1.2 Calculate capacity with half-day availability', async ({ page }) => { const targetCell = page.locator('[data-date="2026-02-03"]'); await targetCell.locator('select').selectOption('0.5'); await expect(targetCell).toContainText('Half day'); }); test.fixme('4.1.3 Calculate capacity with PTO', async ({ page }) => { await createPTO(page, authToken, { team_member_id: mainMemberId, start_date: '2026-02-10', end_date: '2026-02-12', reason: 'Testing PTO' }); await setPeriod(page, '2026-02'); await goToCapacity(page); await page.selectOption('select[aria-label="Select team member"]', mainMemberId); await expect(page.locator('[data-date="2026-02-10"]')).toContainText('PTO'); }); test.fixme('4.1.4 Calculate capacity with holidays', async ({ page }) => { const holiday = await createHoliday(page, authToken, { date: '2026-02-17', name: 'President Day', description: 'Company holiday' }); const holidayId = await holiday.json().then((body) => body.id); createdHolidayIds.push(holidayId); await setPeriod(page, '2026-02'); await goToCapacity(page); await page.selectOption('select[aria-label="Select team member"]', mainMemberId); await expect(page.locator('[data-date="2026-02-17"]')).toContainText('Holiday'); await expect(page.getByText('President Day')).toBeVisible(); }); test.fixme('4.1.5 Calculate capacity with mixed availability', async ({ page }) => { const firstCell = page.locator('[data-date="2026-02-04"]'); const secondCell = page.locator('[data-date="2026-02-05"]'); await firstCell.locator('select').selectOption('0.5'); await secondCell.locator('select').selectOption('0'); await expect(firstCell).toContainText('Half day'); await expect(secondCell).toContainText('Off'); }); test.fixme('4.1.6 Calculate team capacity sum', async ({ page }) => { const extra = await createTeamMember(page, authToken, { name: 'Capacity Pal', hourly_rate: 160 }); createdMembers.push(extra.id); await setPeriod(page, '2026-02'); await goToCapacity(page); await expect(page.getByText('2 members')).toBeVisible(); const totalCard = page.getByTestId('team-capacity-card'); await expect(totalCard).toContainText('40.0d'); await expect(totalCard).toContainText('320 hrs'); }); test.fixme('4.1.7 Exclude inactive from team capacity', async ({ page }) => { const inactive = await createTeamMember(page, authToken, { name: 'Inactive Pal', active: false }); createdMembers.push(inactive.id); await setPeriod(page, '2026-02'); await goToCapacity(page); await expect(page.getByText('1 members')).toBeVisible(); await expect(page.getByTestId('team-capacity-card')).toContainText('20.0d'); }); test.fixme('4.1.8 Calculate possible revenue', async ({ page }) => { const revenueResponse = await page.request.get(`${API_BASE}/api/capacity/revenue?month=2026-02`, { headers: { Authorization: `Bearer ${authToken}` } }); const { possible_revenue } = await revenueResponse.json(); const formatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(possible_revenue); await expect(page.getByTestId('possible-revenue-card')).toContainText(formatted); }); test.fixme('4.1.9 View capacity calendar', async ({ page }) => { await expect(page.getByText('Sun')).toBeVisible(); await expect(page.getByText('Mon')).toBeVisible(); }); test.fixme('4.1.10 Edit availability in calendar', async ({ page }) => { const cell = page.locator('[data-date="2026-02-07"]'); await cell.locator('select').selectOption('0'); await expect(cell).toContainText('Off'); await cell.locator('select').selectOption('1'); await expect(cell).toContainText('Full day'); }); });