import { test, expect, type Page } from '@playwright/test'; const API_BASE = 'http://localhost:3000'; function unwrapData(payload: unknown): T { let current: unknown = payload; while (current && typeof current === 'object' && 'data' in current) { current = (current as { data: unknown }).data; } return current as T; } 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 createTeamMember(page: Page, token: string) { const response = await page.request.post(`${API_BASE}/api/team-members`, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, data: { name: `Capacity Calendar Tester ${Date.now()}`, role_id: 1, hourly_rate: 150, active: true } }); const body = unwrapData<{ id: string }>(await response.json()); return body.id; } async function createPto(page: Page, token: string, memberId: string, date: string) { await page.request.post(`${API_BASE}/api/ptos`, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, data: { team_member_id: memberId, start_date: date, end_date: date, reason: 'Capacity calendar PTO test' } }); } async function expectNoRawErrorAlerts(page: Page) { await expect(page.locator('.alert.alert-error:has-text(" { let authToken: string; let memberId: string | null = null; test.beforeEach(async ({ page }) => { await login(page); const token = (await page.evaluate(() => localStorage.getItem('headroom_access_token'))) ?? ''; authToken = token; memberId = await createTeamMember(page, authToken); await page.goto('/capacity'); await page.waitForURL('/capacity'); await page.waitForResponse((response) => response.url().includes('/api/team-members') && response.status() === 200); }); test.afterEach(async ({ page }) => { if (memberId) { await page.request.delete(`${API_BASE}/api/team-members/${memberId}`, { headers: { Authorization: `Bearer ${authToken}` } }).catch(() => null); memberId = null; } }); test('should save availability change with success message', async ({ page }) => { await expect(page.locator('select[aria-label="Select team member"]')).toBeVisible({ timeout: 10000 }); await expectNoRawErrorAlerts(page); const teamMemberSelect = page.locator('select[aria-label="Select team member"]'); if (memberId) { await teamMemberSelect.selectOption({ value: memberId }); } await expect(page.locator('[data-testid="capacity-calendar"]')).toBeVisible({ timeout: 10000 }); const availabilitySelects = page .locator('select[aria-label^="Availability for"]') .locator(':scope:not([disabled])') .filter({ has: page.locator('option[value="1"]') }); await expect(availabilitySelects.first()).toBeVisible({ timeout: 5000 }); const firstSelect = availabilitySelects.first(); const ariaLabel = await firstSelect.getAttribute('aria-label'); const targetDate = ariaLabel?.replace('Availability for ', '') ?? ''; expect(targetDate).toBeTruthy(); await firstSelect.selectOption('0.5'); await expect(page.locator('text=Saving availability...')).toBeVisible({ timeout: 5000 }); await expect(page.locator('text=Saving availability...')).not.toBeVisible({ timeout: 10000 }); await expect(page.locator('[data-testid="capacity-calendar"] .alert.alert-error')).toHaveCount(0); await expectNoRawErrorAlerts(page); if (memberId) { const period = (await page.evaluate(() => localStorage.getItem('headroom_selected_period'))) ?? '2026-02'; const response = await page.request.get( `${API_BASE}/api/capacity?month=${period}&team_member_id=${memberId}`, { headers: { Authorization: `Bearer ${authToken}` } } ); const body = unwrapData<{ details: Array<{ date: string; availability: number }> }>( await response.json() ); const changedDetail = body.details.find( (detail) => detail.date === targetDate ); expect(changedDetail).toBeDefined(); expect(changedDetail?.availability).toBe(0.5); } }); test('should preselect availability and force blocked days to zero', async ({ page }) => { await expect(page.locator('select[aria-label="Select team member"]')).toBeVisible({ timeout: 10000 }); const teamMemberSelect = page.locator('select[aria-label="Select team member"]'); if (!memberId) { test.fail(true, 'memberId was not created'); return; } await createPto(page, authToken, memberId, '2026-02-10'); await teamMemberSelect.selectOption({ value: memberId }); await expect(page.locator('[data-testid="capacity-calendar"]')).toBeVisible({ timeout: 10000 }); const weekdaySelect = page.locator('select[aria-label="Availability for 2026-02-02"]'); await expect(weekdaySelect).toBeVisible(); await expect(weekdaySelect).not.toHaveValue(''); const weekendSelect = page.locator('select[aria-label="Availability for 2026-02-01"]'); await expect(weekendSelect).toHaveValue('0'); await expect(weekendSelect).toBeDisabled(); await page.reload(); await page.waitForURL('/capacity'); await teamMemberSelect.selectOption({ value: memberId }); const ptoSelect = page.locator('select[aria-label="Availability for 2026-02-10"]'); await expect(ptoSelect).toHaveValue('0'); await expect(ptoSelect).toBeEnabled(); }); });