import { test, expect } from '@playwright/test'; test.describe('Projects 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'); // Navigate to projects await page.goto('/projects'); }); test('page renders with title and table', async ({ page }) => { await expect(page).toHaveTitle(/Projects/); await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible(); await expect(page.getByText('Manage project lifecycle')).toBeVisible(); await expect(page.getByRole('button', { name: /New Project/i })).toBeVisible(); }); test('search filters projects', async ({ page }) => { // Wait for the table to render (not loading state) await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Get initial row count const initialRows = await page.locator('table tbody tr').count(); expect(initialRows).toBeGreaterThan(0); // Search for specific project await page.fill('input[placeholder="Search projects..."]', 'Website'); 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: 5000 }); // Get initial row count const initialRows = await page.locator('table tbody tr').count(); // Select status filter await page.selectOption('select >> nth=0', 'In Progress'); await page.waitForTimeout(300); // Should show filtered results (fewer or equal rows) const filteredRows = await page.locator('table tbody tr').count(); expect(filteredRows).toBeLessThanOrEqual(initialRows); }); }); test.describe('Project Lifecycle 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 projects await page.goto('/projects'); }); // 3.1.1 E2E test: Create project with unique code test.fixme('create project with unique code', async ({ page }) => { // Click New Project button await page.getByRole('button', { name: /New Project/i }).click(); // Fill in the form await page.fill('input[name="code"]', 'PROJ-TEST-001'); await page.fill('input[name="title"]', 'Test Project E2E'); await page.selectOption('select[name="type_id"]', { index: 1 }); // Submit the form await page.getByRole('button', { name: /Create/i }).click(); // Verify the project was created await expect(page.getByText('PROJ-TEST-001')).toBeVisible(); await expect(page.getByText('Test Project E2E')).toBeVisible(); }); // 3.1.2 E2E test: Reject duplicate project code test.fixme('reject duplicate project code', async ({ page }) => { // Click New Project button await page.getByRole('button', { name: /New Project/i }).click(); // Fill in the form with a code that already exists await page.fill('input[name="code"]', 'PROJ-001'); // Assume this exists from seed await page.fill('input[name="title"]', 'Duplicate Code Project'); await page.selectOption('select[name="type_id"]', { index: 1 }); // Submit the form await page.getByRole('button', { name: /Create/i }).click(); // Verify validation error await expect(page.getByText('Project code must be unique')).toBeVisible(); }); // 3.1.3 E2E test: Valid status transitions test.fixme('valid status transitions', async ({ page }) => { // Wait for table to load await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Click on first project to edit await page.locator('table tbody tr').first().click(); // Wait for modal await expect(page.locator('.modal-box')).toBeVisible(); // Change status to next valid state await page.selectOption('select[name="status_id"]', { label: 'Gathering Estimates' }); // Submit await page.getByRole('button', { name: /Update/i }).click(); // Modal should close await expect(page.locator('.modal-box')).not.toBeVisible({ timeout: 5000 }); }); // 3.1.4 E2E test: Invalid status transitions rejected test.fixme('invalid status transitions rejected', async ({ page }) => { // Wait for table await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Click on a project in Initial status await page.locator('table tbody tr').first().click(); await expect(page.locator('.modal-box')).toBeVisible(); // Try to skip to a status that's not directly reachable await page.selectOption('select[name="status_id"]', { label: 'Done' }); // Submit await page.getByRole('button', { name: /Update/i }).click(); // Should show error await expect(page.locator('.alert-error')).toBeVisible({ timeout: 5000 }); }); // 3.1.5 E2E test: Estimate approved requires approved_estimate > 0 test.fixme('estimate approved requires approved estimate', async ({ page }) => { // Wait for table await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); // Click on project that can transition to Estimate Approved await page.locator('table tbody tr').first().click(); await expect(page.locator('.modal-box')).toBeVisible(); // Try to set status to Estimate Approved without approved estimate await page.selectOption('select[name="status_id"]', { label: 'Estimate Approved' }); await page.getByRole('button', { name: /Update/i }).click(); // Should show validation error await expect(page.getByText('approved estimate')).toBeVisible({ timeout: 5000 }); }); // 3.1.6 E2E test: Workflow progression through all statuses test.fixme('workflow progression through all statuses', async ({ page }) => { // This is a complex test that would progress through the entire workflow // For now, just verify the status dropdown has expected options await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); await page.locator('table tbody tr').first().click(); await expect(page.locator('.modal-box')).toBeVisible(); // Check that status dropdown has key statuses const statusSelect = page.locator('select[name="status_id"]'); await expect(statusSelect).toBeVisible(); // Just verify modal can be closed await page.getByRole('button', { name: /Cancel/i }).click(); await expect(page.locator('.modal-box')).not.toBeVisible(); }); // 3.1.7 E2E test: Estimate rework path test.fixme('estimate rework path', async ({ page }) => { // This tests the rework workflow path await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible(); expect(true).toBe(true); }); // 3.1.8 E2E test: Project on hold preserves allocations test.fixme('project on hold preserves allocations', async ({ page }) => { // This tests that putting a project on hold doesn't delete allocations await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible(); expect(true).toBe(true); }); // 3.1.9 E2E test: Cancelled project prevents new allocations test.fixme('cancelled project prevents new allocations', async ({ page }) => { // This tests that cancelled projects can't have new allocations await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible(); expect(true).toBe(true); }); // 3.1.10 E2E test: Set approved estimate test.fixme('set approved estimate', async ({ page }) => { await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); await page.locator('table tbody tr').first().click(); await expect(page.locator('.modal-box')).toBeVisible(); // Set approved estimate await page.fill('input[name="approved_estimate"]', '120'); // Submit await page.getByRole('button', { name: /Update/i }).click(); await expect(page.locator('.modal-box')).not.toBeVisible({ timeout: 5000 }); }); // 3.1.11 E2E test: Update forecasted effort test.fixme('update forecasted effort', async ({ page }) => { await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 }); await page.locator('table tbody tr').first().click(); await expect(page.locator('.modal-box')).toBeVisible(); // Just verify the form can be closed await page.getByRole('button', { name: /Cancel/i }).click(); await expect(page.locator('.modal-box')).not.toBeVisible(); }); // 3.1.12 E2E test: Validate forecasted effort equals approved estimate test.fixme('validate forecasted effort equals approved estimate', async ({ page }) => { await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible(); expect(true).toBe(true); }); });