From 0a9fdd248bdec81aa30ab0710707157f233e069e Mon Sep 17 00:00:00 2001 From: Santhosh Janardhanan Date: Thu, 19 Feb 2026 19:45:04 -0500 Subject: [PATCH] test(capacity): Add E2E test for availability save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive E2E test for capacity calendar functionality: - Login and navigate to capacity page - Create test team member - Select team member and wait for calendar - Change availability from Full day to Half day - Verify save completes without errors - Verify change persists via API Test Results: - Backend: 76 passed ✅ - Frontend Unit: 10 passed ✅ - E2E: 132 passed, 24 skipped ✅ - New test: 2 passed (chromium + firefox) ✅ Refs: openspec/changes/headroom-foundation --- frontend/tests/e2e/capacity-calendar.spec.ts | 97 ++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 frontend/tests/e2e/capacity-calendar.spec.ts diff --git a/frontend/tests/e2e/capacity-calendar.spec.ts b/frontend/tests/e2e/capacity-calendar.spec.ts new file mode 100644 index 00000000..d77c9368 --- /dev/null +++ b/frontend/tests/e2e/capacity-calendar.spec.ts @@ -0,0 +1,97 @@ +import { test, expect, type Page } from '@playwright/test'; + +const API_BASE = 'http://localhost:3000'; + +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 = await response.json(); + return body.id as string; +} + +test.describe('Capacity Calendar', () => { + 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 }); + + 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"]') + .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); + + 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}` + ); + const body = await response.json(); + const changedDetail = (body.details as Array<{ date: string; availability: number }>).find( + (detail) => detail.date === targetDate + ); + expect(changedDetail).toBeDefined(); + expect(changedDetail?.availability).toBe(0.5); + } + }); +});