import { test, expect } from '@playwright/test'; // Helper to seed team members via API async function seedTeamMembers(page: import('@playwright/test').Page) { // Get auth token from localStorage const token = await page.evaluate(() => localStorage.getItem('headroom_access_token')); // First, ensure roles exist by fetching them const rolesResponse = await page.request.get('/api/roles', { headers: { 'Authorization': `Bearer ${token}` } }); // If roles endpoint doesn't exist, use hardcoded IDs based on seeder order // 1: Frontend Dev, 2: Backend Dev, 3: QA, 4: DevOps, 5: UX, 6: PM, 7: Architect const members = [ { name: 'Alice Johnson', role_id: 1, hourly_rate: 85, active: true }, { name: 'Bob Smith', role_id: 2, hourly_rate: 90, active: true }, { name: 'Carol Williams', role_id: 5, hourly_rate: 75, active: false } ]; // Create test team members via API (one at a time) for (const member of members) { const response = await page.request.post('/api/team-members', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, data: member }); // Don't fail on duplicate - just continue if (!response.ok() && response.status() !== 422) { console.log(`Failed to create member ${member.name}: ${response.status()}`); } } } test.describe('Team Members Page', () => { test.beforeEach(async ({ page }) => { // Login first 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'); // Seed test data via API await seedTeamMembers(page); // Navigate to team members await page.goto('/team-members'); }); test('page renders with title and table', async ({ page }) => { await expect(page).toHaveTitle(/Team Members/); await expect(page.locator('h1', { hasText: 'Team Members' })).toBeVisible(); await expect(page.getByText('Manage your team roster')).toBeVisible(); await expect(page.getByRole('button', { name: /Add Member/i })).toBeVisible(); }); test('search filters team members', async ({ page }) => { // Wait for the table to render (not loading state) await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); // Get initial row count const initialRows = await page.locator('table tbody tr').count(); expect(initialRows).toBeGreaterThan(0); // Search for specific member await page.fill('input[placeholder="Search team members..."]', 'Alice'); await page.waitForTimeout(300); // Should show fewer or equal rows after filtering const filteredRows = await page.locator('table tbody tr').count(); expect(filteredRows).toBeLessThanOrEqual(initialRows); }); test('status filter works', async ({ page }) => { // Wait for the table to render (not loading state) await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 10000 }); // Get initial row count (all members) const initialRows = await page.locator('table tbody tr').count(); expect(initialRows).toBeGreaterThan(0); // Select active filter using more specific selector await page.locator('.filter-bar select, select').first().selectOption('active'); await page.waitForTimeout(500); // Should show filtered results const filteredRows = await page.locator('table tbody tr').count(); // Just verify filtering happened - count should be valid expect(filteredRows).toBeGreaterThanOrEqual(0); }); }); test.describe('Team Member Management - Phase 1 Tests (RED)', () => { test.beforeEach(async ({ page }) => { // Login first 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'); // Navigate to team members await page.goto('/team-members'); }); // 2.1.1 E2E test: Create team member with valid data test.fixme('create team member with valid data', async ({ page }) => { // Click Add Member button await page.getByRole('button', { name: /Add Member/i }).click(); // Fill in the form await page.fill('input[name="name"]', 'John Doe'); await page.selectOption('select[name="role_id"]', { label: 'Backend Developer' }); await page.fill('input[name="hourly_rate"]', '150'); // Submit the form await page.getByRole('button', { name: /Create|Save/i }).click(); // Verify the team member was created await expect(page.getByText('John Doe')).toBeVisible(); await expect(page.getByText('$150.00')).toBeVisible(); await expect(page.getByText('Backend Developer')).toBeVisible(); }); // 2.1.2 E2E test: Reject team member with invalid hourly rate test.fixme('reject team member with invalid hourly rate', async ({ page }) => { // Click Add Member button await page.getByRole('button', { name: /Add Member/i }).click(); // Fill in the form with invalid hourly rate await page.fill('input[name="name"]', 'Jane Smith'); await page.selectOption('select[name="role_id"]', { label: 'Frontend Developer' }); await page.fill('input[name="hourly_rate"]', '0'); // Submit the form await page.getByRole('button', { name: /Create|Save/i }).click(); // Verify validation error await expect(page.getByText('Hourly rate must be greater than 0')).toBeVisible(); // Try with negative value await page.fill('input[name="hourly_rate"]', '-50'); await page.getByRole('button', { name: /Create|Save/i }).click(); // Verify validation error await expect(page.getByText('Hourly rate must be greater than 0')).toBeVisible(); }); // 2.1.3 E2E test: Reject team member with missing required fields test.fixme('reject team member with missing required fields', async ({ page }) => { // Click Add Member button await page.getByRole('button', { name: /Add Member/i }).click(); // Submit the form without filling required fields await page.getByRole('button', { name: /Create|Save/i }).click(); // Verify validation errors for required fields await expect(page.getByText('Name is required')).toBeVisible(); await expect(page.getByText('Role is required')).toBeVisible(); await expect(page.getByText('Hourly rate is required')).toBeVisible(); }); // 2.1.4 E2E test: View all team members list test.fixme('view all team members list', async ({ page }) => { // Wait for the table to load await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Verify the list shows all team members including inactive ones const rows = await page.locator('table tbody tr').count(); expect(rows).toBeGreaterThan(0); // Verify columns are displayed await expect(page.getByText('Name')).toBeVisible(); await expect(page.getByText('Role')).toBeVisible(); await expect(page.getByText('Hourly Rate')).toBeVisible(); await expect(page.getByText('Status')).toBeVisible(); }); // 2.1.5 E2E test: Filter active team members only test.fixme('filter active team members only', async ({ page }) => { // Wait for the table to load await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Get total count const totalRows = await page.locator('table tbody tr').count(); // Apply active filter await page.selectOption('select[name="status_filter"]', 'active'); await page.waitForTimeout(300); // Verify only active members are shown const activeRows = await page.locator('table tbody tr').count(); expect(activeRows).toBeLessThanOrEqual(totalRows); // Verify no inactive badges are visible const inactiveBadges = await page.locator('.badge:has-text("Inactive")').count(); expect(inactiveBadges).toBe(0); }); // 2.1.6 E2E test: Update team member details test.fixme('update team member details', async ({ page }) => { // Wait for the table to load await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Click edit on the first team member await page.locator('table tbody tr').first().getByRole('button', { name: /Edit/i }).click(); // Update the hourly rate await page.fill('input[name="hourly_rate"]', '175'); // Submit the form await page.getByRole('button', { name: /Update|Save/i }).click(); // Verify the update was saved await expect(page.getByText('$175.00')).toBeVisible(); }); // 2.1.7 E2E test: Deactivate team member preserves data test.fixme('deactivate team member preserves data', async ({ page }) => { // Wait for the table to load await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Get the first team member's name const firstMemberName = await page.locator('table tbody tr').first().locator('td').first().textContent(); // Click edit on the first team member await page.locator('table tbody tr').first().getByRole('button', { name: /Edit/i }).click(); // Uncheck the active checkbox await page.uncheck('input[name="active"]'); // Submit the form await page.getByRole('button', { name: /Update|Save/i }).click(); // Verify the member is marked as inactive await expect(page.getByText('Inactive')).toBeVisible(); // Verify the member's data is still in the list await expect(page.getByText(firstMemberName || '')).toBeVisible(); }); // 2.1.8 E2E test: Cannot delete team member with allocations test.fixme('cannot delete team member with allocations', async ({ page }) => { // Wait for the table to load await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Try to delete a team member that has allocations // Note: This assumes at least one team member has allocations await page.locator('table tbody tr').first().getByRole('button', { name: /Delete/i }).click(); // Confirm deletion await page.getByRole('button', { name: /Confirm|Yes/i }).click(); // Verify error message is shown await expect(page.getByText('Cannot delete team member with active allocations')).toBeVisible(); await expect(page.getByText('deactivating the team member instead')).toBeVisible(); }); });