From 9e9d4245bb7b2138152f44d873c06a200e58f5a7 Mon Sep 17 00:00:00 2001 From: kyle Date: Sat, 31 Jan 2026 17:50:24 +0100 Subject: [PATCH] chore: Move development files to gitignore (keep locally) Removed from repository but kept locally for development: - .github/workflows/ - GitHub Actions (Gitea uses .gitea/workflows/) - frontend/e2e/ - Playwright E2E tests (development only) Added to .gitignore: - .github/ - GitHub-specific CI/CD (not used on Gitea) - frontend/e2e/ - E2E tests kept locally for testing - **/playwright-report/ - Test result reports - **/test-results/ - Test artifacts These files remain on local machine for development/testing but are excluded from repository to reduce clutter. Note: Gitea uses .gitea/workflows/ for CI, not .github/workflows/ Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/ci.yml | 239 ---------------- .github/workflows/dependency-update.yml | 69 ----- .github/workflows/e2e-tests.yml | 119 -------- .gitignore | 8 + frontend/e2e/accessibility.spec.ts | 32 --- frontend/e2e/admin-test-data.spec.ts | 288 ------------------- frontend/e2e/api.spec.ts | 81 ------ frontend/e2e/auth-flow.spec.ts | 238 ---------------- frontend/e2e/auth.setup.ts | 47 ---- frontend/e2e/driver-selector.spec.ts | 152 ---------- frontend/e2e/event-management.spec.ts | 334 ---------------------- frontend/e2e/filter-modal.spec.ts | 239 ---------------- frontend/e2e/ipad-ui.spec.ts | 314 --------------------- frontend/e2e/last-name-sorting.spec.ts | 198 ------------- frontend/e2e/multi-vip-events.spec.ts | 288 ------------------- frontend/e2e/navigation.spec.ts | 81 ------ frontend/e2e/ui-enhancements.spec.ts | 333 ---------------------- frontend/e2e/user-workflow.spec.ts | 358 ------------------------ frontend/e2e/vip-filter-check.spec.ts | 45 --- 19 files changed, 8 insertions(+), 3455 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/dependency-update.yml delete mode 100644 .github/workflows/e2e-tests.yml delete mode 100644 frontend/e2e/accessibility.spec.ts delete mode 100644 frontend/e2e/admin-test-data.spec.ts delete mode 100644 frontend/e2e/api.spec.ts delete mode 100644 frontend/e2e/auth-flow.spec.ts delete mode 100644 frontend/e2e/auth.setup.ts delete mode 100644 frontend/e2e/driver-selector.spec.ts delete mode 100644 frontend/e2e/event-management.spec.ts delete mode 100644 frontend/e2e/filter-modal.spec.ts delete mode 100644 frontend/e2e/ipad-ui.spec.ts delete mode 100644 frontend/e2e/last-name-sorting.spec.ts delete mode 100644 frontend/e2e/multi-vip-events.spec.ts delete mode 100644 frontend/e2e/navigation.spec.ts delete mode 100644 frontend/e2e/ui-enhancements.spec.ts delete mode 100644 frontend/e2e/user-workflow.spec.ts delete mode 100644 frontend/e2e/vip-filter-check.spec.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 99d5ab5..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,239 +0,0 @@ -name: CI/CD Pipeline - -on: - push: - branches: [ main, develop ] - pull_request: - branches: [ main, develop ] - -env: - REGISTRY: docker.io - IMAGE_NAME: t72chevy/vip-coordinator - -jobs: - # Backend tests - backend-tests: - name: Backend Tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: test_user - POSTGRES_PASSWORD: test_password - POSTGRES_DB: vip_coordinator_test - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - redis: - image: redis:7 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6379:6379 - - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: backend/package-lock.json - - - name: Install dependencies - working-directory: ./backend - run: npm ci - - - name: Run linter - working-directory: ./backend - run: npm run lint || true - - - name: Run type check - working-directory: ./backend - run: npx tsc --noEmit - - - name: Run tests - working-directory: ./backend - env: - DATABASE_URL: postgresql://test_user:test_password@localhost:5432/vip_coordinator_test - REDIS_URL: redis://localhost:6379 - GOOGLE_CLIENT_ID: test_client_id - GOOGLE_CLIENT_SECRET: test_client_secret - GOOGLE_REDIRECT_URI: http://localhost:3000/auth/google/callback - FRONTEND_URL: http://localhost:5173 - JWT_SECRET: test_jwt_secret_minimum_32_characters_long - NODE_ENV: test - run: npm test - - - name: Upload coverage - uses: actions/upload-artifact@v4 - if: always() - with: - name: backend-coverage - path: backend/coverage/ - - # Frontend tests - frontend-tests: - name: Frontend Tests - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - cache-dependency-path: frontend/package-lock.json - - - name: Install dependencies - working-directory: ./frontend - run: npm ci - - - name: Run linter - working-directory: ./frontend - run: npm run lint - - - name: Run type check - working-directory: ./frontend - run: npx tsc --noEmit - - - name: Run tests - working-directory: ./frontend - run: npm test -- --run - - - name: Build frontend - working-directory: ./frontend - run: npm run build - - - name: Upload coverage - uses: actions/upload-artifact@v4 - if: always() - with: - name: frontend-coverage - path: frontend/coverage/ - - # Build Docker images - build-images: - name: Build Docker Images - runs-on: ubuntu-latest - needs: [backend-tests, frontend-tests] - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') - - steps: - - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=sha,prefix={{branch}}- - type=raw,value=latest,enable={{is_default_branch}} - - - name: Build and push Backend - uses: docker/build-push-action@v5 - with: - context: ./backend - push: true - tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:backend-${{ github.sha }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:backend-latest - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Build and push Frontend - uses: docker/build-push-action@v5 - with: - context: ./frontend - push: true - tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:frontend-${{ github.sha }} - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:frontend-latest - cache-from: type=gha - cache-to: type=gha,mode=max - - # Security scan - security-scan: - name: Security Scan - runs-on: ubuntu-latest - needs: [backend-tests, frontend-tests] - - steps: - - uses: actions/checkout@v4 - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@master - with: - scan-type: 'fs' - scan-ref: '.' - format: 'sarif' - output: 'trivy-results.sarif' - - - name: Upload Trivy scan results - uses: github/codeql-action/upload-sarif@v2 - if: always() - with: - sarif_file: 'trivy-results.sarif' - - # Deploy to staging (example) - deploy-staging: - name: Deploy to Staging - runs-on: ubuntu-latest - needs: [build-images] - if: github.ref == 'refs/heads/develop' - environment: - name: staging - url: https://staging.bsa.madeamess.online - - steps: - - uses: actions/checkout@v4 - - - name: Deploy to staging - run: | - echo "Deploying to staging environment..." - # Add your deployment script here - # Example: ssh to server and docker-compose pull && up - - # Deploy to production - deploy-production: - name: Deploy to Production - runs-on: ubuntu-latest - needs: [build-images, security-scan] - if: github.ref == 'refs/heads/main' - environment: - name: production - url: https://bsa.madeamess.online - - steps: - - uses: actions/checkout@v4 - - - name: Deploy to production - run: | - echo "Deploying to production environment..." - # Add your deployment script here - # Example: ssh to server and docker-compose pull && up \ No newline at end of file diff --git a/.github/workflows/dependency-update.yml b/.github/workflows/dependency-update.yml deleted file mode 100644 index b53273d..0000000 --- a/.github/workflows/dependency-update.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Dependency Updates - -on: - schedule: - # Run weekly on Mondays at 3 AM UTC - - cron: '0 3 * * 1' - workflow_dispatch: - -jobs: - update-dependencies: - name: Update Dependencies - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Update Backend Dependencies - working-directory: ./backend - run: | - npm update - npm audit fix || true - - - name: Update Frontend Dependencies - working-directory: ./frontend - run: | - npm update - npm audit fix || true - - - name: Check for changes - id: check_changes - run: | - if [[ -n $(git status -s) ]]; then - echo "changes=true" >> $GITHUB_OUTPUT - else - echo "changes=false" >> $GITHUB_OUTPUT - fi - - - name: Create Pull Request - if: steps.check_changes.outputs.changes == 'true' - uses: peter-evans/create-pull-request@v5 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'chore: update dependencies' - title: 'Automated Dependency Updates' - body: | - ## Automated Dependency Updates - - This PR contains automated dependency updates for both frontend and backend packages. - - ### What's included: - - Updated npm dependencies to latest compatible versions - - Applied security fixes from `npm audit` - - ### Checklist: - - [ ] Review dependency changes - - [ ] Run tests locally - - [ ] Check for breaking changes in updated packages - - [ ] Update any affected code if needed - - *This PR was automatically generated by the dependency update workflow.* - branch: deps/automated-update-${{ github.run_number }} - delete-branch: true \ No newline at end of file diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml deleted file mode 100644 index 9c63bdc..0000000 --- a/.github/workflows/e2e-tests.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: E2E Tests - -on: - schedule: - # Run E2E tests daily at 2 AM UTC - - cron: '0 2 * * *' - workflow_dispatch: - inputs: - environment: - description: 'Environment to test' - required: true - default: 'staging' - type: choice - options: - - staging - - production - -jobs: - e2e-tests: - name: E2E Tests - ${{ github.event.inputs.environment || 'staging' }} - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install Playwright - run: | - npm init -y - npm install -D @playwright/test - npx playwright install --with-deps - - - name: Create E2E test structure - run: | - mkdir -p e2e/tests - cat > e2e/playwright.config.ts << 'EOF' - import { defineConfig, devices } from '@playwright/test'; - - export default defineConfig({ - testDir: './tests', - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - reporter: 'html', - use: { - baseURL: process.env.BASE_URL || 'https://staging.bsa.madeamess.online', - trace: 'on-first-retry', - screenshot: 'only-on-failure', - }, - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, - ], - }); - EOF - - - name: Create sample E2E test - run: | - cat > e2e/tests/auth.spec.ts << 'EOF' - import { test, expect } from '@playwright/test'; - - test.describe('Authentication Flow', () => { - test('should display login page', async ({ page }) => { - await page.goto('/'); - await expect(page).toHaveTitle(/VIP Coordinator/); - await expect(page.locator('text=Sign in with Google')).toBeVisible(); - }); - - test('should redirect to dashboard after login', async ({ page }) => { - // This would require mocking Google OAuth or using test credentials - // For now, just check that the login button exists - await page.goto('/'); - const loginButton = page.locator('button:has-text("Sign in with Google")'); - await expect(loginButton).toBeVisible(); - }); - }); - EOF - - - name: Run E2E tests - env: - BASE_URL: ${{ github.event.inputs.environment == 'production' && 'https://bsa.madeamess.online' || 'https://staging.bsa.madeamess.online' }} - run: | - cd e2e - npx playwright test - - - name: Upload test results - uses: actions/upload-artifact@v4 - if: always() - with: - name: playwright-report - path: e2e/playwright-report/ - retention-days: 30 - - notify-results: - name: Notify Results - runs-on: ubuntu-latest - needs: [e2e-tests] - if: always() - - steps: - - name: Send notification - run: | - echo "E2E tests completed with status: ${{ needs.e2e-tests.result }}" - # Add notification logic here (Slack, email, etc.) \ No newline at end of file diff --git a/.gitignore b/.gitignore index ae3af3d..811370a 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,14 @@ jspm_packages/ # AI context files CLAUDE.md +# CI/CD (GitHub-specific, not needed for Gitea) +.github/ + +# E2E tests (keep locally for development, don't commit) +frontend/e2e/ +**/playwright-report/ +**/test-results/ + # OS generated files .DS_Store .DS_Store? diff --git a/frontend/e2e/accessibility.spec.ts b/frontend/e2e/accessibility.spec.ts deleted file mode 100644 index 644e79b..0000000 --- a/frontend/e2e/accessibility.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { test, expect } from '@playwright/test'; -import AxeBuilder from '@axe-core/playwright'; - -/** - * Accessibility Tests - * - * Uses axe-core to check for accessibility issues - */ - -test.describe('Accessibility', () => { - test('login page should not have accessibility violations', async ({ page }) => { - await page.goto('/login'); - - const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); - - console.log('\n=== Accessibility Scan Results ==='); - console.log(`Violations found: ${accessibilityScanResults.violations.length}`); - - if (accessibilityScanResults.violations.length > 0) { - console.log('\nViolations:'); - accessibilityScanResults.violations.forEach((violation, i) => { - console.log(`\n${i + 1}. ${violation.id}: ${violation.description}`); - console.log(` Impact: ${violation.impact}`); - console.log(` Help: ${violation.helpUrl}`); - console.log(` Elements affected: ${violation.nodes.length}`); - }); - } - console.log('=================================\n'); - - expect(accessibilityScanResults.violations).toEqual([]); - }); -}); diff --git a/frontend/e2e/admin-test-data.spec.ts b/frontend/e2e/admin-test-data.spec.ts deleted file mode 100644 index 3cd5d37..0000000 --- a/frontend/e2e/admin-test-data.spec.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Admin Test Data Management', () => { - test.beforeEach(async ({ page }) => { - test.setTimeout(600000); // 10 minutes - test data creation can take a while with rate limiting - - // Login - await page.goto('http://localhost:5173/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - await page.waitForTimeout(2000); - - await page.locator('input[name="username"]').fill('test@test.com'); - await page.locator('input[name="password"]').fill('P@ssw0rd!'); - await page.locator('button[type="submit"][data-action-button-primary="true"]').click(); - - await page.waitForURL('**/dashboard', { timeout: 30000 }); - console.log('āœ… Logged in successfully'); - }); - - test('should load all test data via Admin menu and verify in Events view', async ({ page }) => { - console.log('\nšŸ”§ Testing Admin Test Data Management'); - - // Navigate to Admin Tools - console.log('šŸ“‹ Navigating to Admin Tools page'); - await page.goto('http://localhost:5173/admin-tools', { waitUntil: 'networkidle' }); - - // Force reload to ensure latest JavaScript is loaded - await page.reload({ waitUntil: 'networkidle' }); - - await page.screenshot({ path: 'test-results/admin-01-tools-page.png', fullPage: true }); - - // Verify Admin Tools page loaded - const pageTitle = page.locator('h1:has-text("Administrator Tools")'); - await expect(pageTitle).toBeVisible(); - console.log('āœ… Admin Tools page loaded'); - - // Step 1: Add Test VIPs - console.log('\nšŸ‘„ Adding Test VIPs...'); - const addVipsButton = page.locator('button:has-text("Add Test VIPs")'); - await addVipsButton.scrollIntoViewIfNeeded(); - await expect(addVipsButton).toBeVisible(); - - // Setup dialog handler for confirmation - page.on('dialog', async dialog => { - console.log(`šŸ“¢ Confirmation: ${dialog.message()}`); - await dialog.accept(); - }); - - await addVipsButton.click(); - - // Wait for success toast - await page.waitForSelector('text=/Added \\d+ test VIPs/', { timeout: 60000 }); - console.log('āœ… Test VIPs added successfully'); - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/admin-02-vips-added.png', fullPage: true }); - - // Step 2: Add Test Drivers - console.log('\nšŸš— Adding Test Drivers...'); - const addDriversButton = page.locator('button:has-text("Add Test Drivers")'); - await expect(addDriversButton).toBeVisible(); - await addDriversButton.click(); - - await page.waitForSelector('text=/Added .* test [Dd]rivers?/', { timeout: 30000 }); - console.log('āœ… Test Drivers added successfully'); - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/admin-03-drivers-added.png', fullPage: true }); - - // Step 3: Add Test Vehicles - console.log('\n🚐 Adding Test Vehicles...'); - const addVehiclesButton = page.locator('button:has-text("Add Test Vehicles")'); - await expect(addVehiclesButton).toBeVisible(); - await addVehiclesButton.click(); - - await page.waitForSelector('text=/Added .* test [Vv]ehicles?/', { timeout: 30000 }); - console.log('āœ… Test Vehicles added successfully'); - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/admin-04-vehicles-added.png', fullPage: true }); - - // Step 4: Add Test Schedule - console.log('\nšŸ“… Adding Test Schedule Events...'); - const addScheduleButton = page.locator('button:has-text("Add Test Schedule")'); - await expect(addScheduleButton).toBeVisible(); - await addScheduleButton.click(); - - // Wait for schedule creation to complete by polling the stats - // This can take a while as it creates many events - console.log('ā³ Waiting for schedule events to be created...'); - - let scheduleEventCount = 0; - for (let i = 0; i < 60; i++) { // Poll for up to 2 minutes - await page.waitForTimeout(2000); - - // Click refresh button to update stats - const refreshButton = page.locator('button:has-text("Refresh")'); - if (await refreshButton.isVisible({ timeout: 1000 })) { - await refreshButton.click(); - await page.waitForTimeout(500); - } - - // Check event count - const statsSection = page.locator('text=Database Statistics').locator('..'); - const eventsStat = statsSection.locator('text=/Events/').locator('..'); - const eventCountText = await eventsStat.locator('.text-3xl, .text-2xl, .text-xl').first().textContent(); - scheduleEventCount = parseInt(eventCountText || '0'); - - if (scheduleEventCount > 0) { - console.log(`āœ… Schedule created: ${scheduleEventCount} events found`); - break; - } - - if (i % 5 === 0) { - console.log(`ā³ Still waiting... (${i * 2}s elapsed)`); - } - } - - if (scheduleEventCount === 0) { - throw new Error('Schedule creation timed out - no events created after 2 minutes'); - } - - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/admin-05-schedule-added.png', fullPage: true }); - - // Step 5: Verify database statistics updated - console.log('\nšŸ“Š Verifying database statistics...'); - - // Refresh the page to get updated stats - await page.reload(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/admin-06-stats-updated.png', fullPage: true }); - - // Try to verify stats if visible - try { - const statsSection = page.locator('text=Database Statistics').locator('..'); - if (await statsSection.isVisible({ timeout: 3000 })) { - console.log('āœ… Database statistics section found'); - - // Look for any number indicators that show counts - const numbers = await statsSection.locator('.text-3xl, .text-2xl, .text-xl').allTextContents(); - console.log(`šŸ“Š Stats visible: ${numbers.join(', ')}`); - } else { - console.log('ā„¹ļø Stats section not found - will verify via Events page'); - } - } catch (e) { - console.log('ā„¹ļø Could not verify stats - will check Events page instead'); - } - - // Step 6: Navigate to Events view - console.log('\nšŸ“… Navigating to Events view...'); - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/admin-07-events-page.png', fullPage: true }); - - // Verify Events page loaded - const eventsPageTitle = page.locator('h1, h2').filter({ hasText: /Events|Schedule/ }); - await expect(eventsPageTitle).toBeVisible(); - console.log('āœ… Events page loaded'); - - // Step 7: Verify events are displayed - console.log('\nšŸ” Verifying events are displayed...'); - - // Wait for table to load - await page.waitForSelector('table, .event-list, .event-card', { timeout: 10000 }); - - // Count event rows (could be in a table or as cards) - let eventCount = 0; - - // Try to find table rows first - const tableRows = await page.locator('tbody tr').count(); - if (tableRows > 0) { - eventCount = tableRows; - console.log(`šŸ“Š Found ${eventCount} events in table`); - } else { - // Try to find event cards - const eventCards = await page.locator('.event-card, [class*="event"]').count(); - eventCount = eventCards; - console.log(`šŸ“Š Found ${eventCount} event cards`); - } - - // Verify we have events - expect(eventCount).toBeGreaterThan(0); - console.log(`āœ… Events displayed: ${eventCount} events found`); - - // Step 8: Verify event details show VIPs, drivers, vehicles - console.log('\nšŸ” Verifying event details...'); - - if (tableRows > 0) { - // Check first event row has data - const firstRow = page.locator('tbody tr').first(); - await expect(firstRow).toBeVisible(); - - // Try to find VIP names - const vipCell = firstRow.locator('td').nth(1); // Usually second column - const vipCellText = await vipCell.textContent(); - console.log(`šŸ“‹ Sample VIP data: ${vipCellText?.substring(0, 50)}...`); - - // Try to find vehicle info - const vehicleCell = firstRow.locator('td').nth(2); // Usually third column - const vehicleCellText = await vehicleCell.textContent(); - console.log(`šŸš— Sample Vehicle data: ${vehicleCellText?.substring(0, 50)}...`); - - // Try to find driver info - const driverCell = firstRow.locator('td').nth(3); // Usually fourth column - const driverCellText = await driverCell.textContent(); - console.log(`šŸ‘¤ Sample Driver data: ${driverCellText?.substring(0, 50)}...`); - - // Verify cells have content (not empty) - expect(vipCellText).not.toBe(''); - expect(vehicleCellText).not.toBe(''); - expect(driverCellText).not.toBe(''); - - console.log('āœ… Event details contain VIP, Vehicle, and Driver data'); - } - - await page.screenshot({ path: 'test-results/admin-08-events-verified.png', fullPage: true }); - - // Step 9: Verify multi-VIP events display correctly - console.log('\nšŸ‘„ Checking for multi-VIP events...'); - - // Look for comma-separated VIP names (indicates multi-VIP event) - const multiVipRows = await page.locator('tbody tr').filter({ hasText: /,/ }).count(); - console.log(`šŸ“Š Found ${multiVipRows} rows with comma-separated data (likely multi-VIP events)`); - - if (multiVipRows > 0) { - const firstMultiVipRow = page.locator('tbody tr').filter({ hasText: /,/ }).first(); - const vipCell = firstMultiVipRow.locator('td').nth(1); - const vipNames = await vipCell.textContent(); - console.log(`šŸ‘„ Sample multi-VIP event: ${vipNames?.substring(0, 100)}...`); - console.log('āœ… Multi-VIP events displaying correctly'); - } else { - console.log('ā„¹ļø No obvious multi-VIP events detected (may be using badges/pills instead of commas)'); - } - - console.log('\nšŸŽ‰ Test Data Load and Verification Complete!'); - console.log(`āœ… ${eventCount} events verified in Events view`); - }); - - test('should show error handling for database operations', async ({ page }) => { - console.log('\nāš ļø Testing error handling (optional validation)'); - - await page.goto('http://localhost:5173/admin-tools'); - await page.waitForLoadState('networkidle'); - - // Try clicking Clear All Data button if it exists - const clearButton = page.locator('button:has-text("Clear All Data"), button:has-text("Delete All")'); - - if (await clearButton.isVisible({ timeout: 2000 })) { - console.log('šŸ—‘ļø Found Clear All Data button'); - - // Setup dialog handler for confirmation - page.on('dialog', async dialog => { - console.log(`šŸ“¢ Confirmation dialog: ${dialog.message()}`); - await dialog.accept(); - }); - - await page.screenshot({ path: 'test-results/admin-09-before-clear.png', fullPage: true }); - - await clearButton.click(); - await page.waitForTimeout(3000); - - await page.screenshot({ path: 'test-results/admin-10-after-clear.png', fullPage: true }); - - console.log('āœ… Clear operation completed'); - - // Verify data is cleared by checking Events page - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - - const eventRows = await page.locator('tbody tr').count(); - console.log(`šŸ“Š Event count after clear: ${eventRows}`); - - if (eventRows === 0) { - console.log('āœ… All events cleared successfully'); - } else { - console.log(`ā„¹ļø ${eventRows} events still present (may be seed data)`); - } - } else { - console.log('ā„¹ļø No Clear All Data button found - skipping clear test'); - } - }); -}); diff --git a/frontend/e2e/api.spec.ts b/frontend/e2e/api.spec.ts deleted file mode 100644 index 9aeba5e..0000000 --- a/frontend/e2e/api.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * API Integration Tests - * - * Tests API calls and network requests - */ - -test.describe('API Integration', () => { - test('should handle API errors gracefully', async ({ page }) => { - // Capture console logs - const logs: string[] = []; - page.on('console', (msg) => { - logs.push(`[${msg.type()}] ${msg.text()}`); - console.log(`[BROWSER ${msg.type()}]:`, msg.text()); - }); - - // Capture network errors - const networkErrors: any[] = []; - page.on('response', (response) => { - if (response.status() >= 400) { - networkErrors.push({ - url: response.url(), - status: response.status(), - statusText: response.statusText(), - }); - console.log(`[NETWORK ERROR] ${response.status()} ${response.url()}`); - } - }); - - await page.goto('/login'); - await page.waitForLoadState('networkidle'); - - // Log results - console.log(`\n=== Test Results ===`); - console.log(`Total console logs: ${logs.length}`); - console.log(`Network errors: ${networkErrors.length}`); - if (networkErrors.length > 0) { - console.log('\nNetwork Errors:'); - networkErrors.forEach((err) => { - console.log(` - ${err.status} ${err.url}`); - }); - } - console.log(`===================\n`); - }); - - test('should log all network requests', async ({ page }) => { - const requests: any[] = []; - - page.on('request', (request) => { - requests.push({ - method: request.method(), - url: request.url(), - headers: request.headers(), - }); - console.log(`[→ REQUEST] ${request.method()} ${request.url()}`); - }); - - page.on('response', async (response) => { - const request = response.request(); - console.log(`[← RESPONSE] ${response.status()} ${request.method()} ${request.url()}`); - - // Log response body for API calls (not assets) - if (request.url().includes('/api/')) { - try { - const body = await response.text(); - console.log(`[RESPONSE BODY] ${body.substring(0, 200)}${body.length > 200 ? '...' : ''}`); - } catch (e) { - // Can't read body for some responses - } - } - }); - - await page.goto('/login'); - await page.waitForLoadState('networkidle'); - - console.log(`\n=== Network Summary ===`); - console.log(`Total requests: ${requests.length}`); - console.log(`===================\n`); - }); -}); diff --git a/frontend/e2e/auth-flow.spec.ts b/frontend/e2e/auth-flow.spec.ts deleted file mode 100644 index b865dca..0000000 --- a/frontend/e2e/auth-flow.spec.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Authentication Flow', () => { - let consoleMessages: string[] = []; - let consoleErrors: string[] = []; - let failedRequests: string[] = []; - - test.beforeEach(async ({ page }) => { - test.setTimeout(120000); - - // Capture all console messages - page.on('console', msg => { - const text = `[${msg.type()}] ${msg.text()}`; - consoleMessages.push(text); - - if (msg.type() === 'error' || msg.type() === 'warning') { - consoleErrors.push(text); - } - }); - - // Capture page errors - page.on('pageerror', error => { - const text = `[PAGE ERROR] ${error.message}\n${error.stack}`; - consoleErrors.push(text); - }); - - // Capture failed network requests - page.on('response', response => { - if (response.status() >= 400) { - const text = `[${response.status()}] ${response.request().method()} ${response.url()}`; - failedRequests.push(text); - consoleErrors.push(text); - } - }); - }); - - test.afterEach(async () => { - // Print all console output for debugging - if (consoleMessages.length > 0) { - console.log('\n=== CONSOLE OUTPUT ==='); - consoleMessages.forEach(msg => console.log(msg)); - } - - if (failedRequests.length > 0) { - console.log('\n=== FAILED HTTP REQUESTS ==='); - failedRequests.forEach(req => console.log(req)); - } - - if (consoleErrors.length > 0) { - console.log('\n=== CONSOLE ERRORS/WARNINGS ==='); - consoleErrors.forEach(err => console.log(err)); - } - - // Reset for next test - consoleMessages = []; - consoleErrors = []; - failedRequests = []; - }); - - test('should navigate from root URL to login and authenticate', async ({ page }) => { - console.log('\nšŸ” Starting from root URL: http://localhost:5173/'); - - // Go to root URL (not /login) - await page.goto('http://localhost:5173/', { waitUntil: 'networkidle' }); - - // Take screenshot of initial page - await page.screenshot({ path: 'test-results/auth-flow-01-initial.png', fullPage: true }); - - // Wait a moment to see what happens - await page.waitForTimeout(2000); - - console.log('šŸ“ø Current URL:', page.url()); - console.log('šŸ“„ Page title:', await page.title()); - - // Check if we're redirected to login - const currentUrl = page.url(); - if (currentUrl.includes('/login')) { - console.log('āœ… Automatically redirected to /login'); - } else if (currentUrl.includes('auth0')) { - console.log('āœ… Redirected to Auth0 login'); - } else { - console.log('āŒ NOT redirected to login. Current URL:', currentUrl); - - // Check what's visible on the page - const bodyText = await page.locator('body').textContent(); - console.log('šŸ“ Page content:', bodyText?.substring(0, 200)); - - // Take another screenshot - await page.screenshot({ path: 'test-results/auth-flow-02-stuck.png', fullPage: true }); - - // Look for specific elements - const loadingText = await page.locator('text=Loading').count(); - if (loadingText > 0) { - console.log('āš ļø Found "Loading" text - stuck in loading state'); - } - - const loginButton = await page.locator('button:has-text("Sign in")').count(); - if (loginButton > 0) { - console.log('āœ… Found login button on page'); - } - } - - // Now explicitly navigate to login if not already there - if (!currentUrl.includes('/login') && !currentUrl.includes('auth0')) { - console.log('šŸ”„ Manually navigating to /login'); - await page.goto('http://localhost:5173/login'); - await page.waitForLoadState('networkidle'); - await page.screenshot({ path: 'test-results/auth-flow-03-login-page.png', fullPage: true }); - } - - // Click Auth0 login button - console.log('šŸ”‘ Looking for Auth0 login button'); - const auth0Button = page.locator('button:has-text("Sign in with Auth0")'); - await expect(auth0Button).toBeVisible({ timeout: 10000 }); - - await page.screenshot({ path: 'test-results/auth-flow-04-before-click.png', fullPage: true }); - await auth0Button.click(); - - console.log('ā³ Waiting for Auth0 page to load'); - await page.waitForTimeout(2000); - await page.screenshot({ path: 'test-results/auth-flow-05-auth0-page.png', fullPage: true }); - - // Fill in Auth0 credentials - console.log('šŸ“ Filling in credentials'); - await page.locator('input[name="username"]').fill('test@test.com'); - await page.locator('input[name="password"]').fill('P@ssw0rd!'); - - await page.screenshot({ path: 'test-results/auth-flow-06-credentials-filled.png', fullPage: true }); - - // Submit login (use the primary Continue button, not Google) - console.log('āœ‰ļø Submitting login form'); - await page.locator('button[type="submit"][data-action-button-primary="true"]').click(); - - // Wait for redirect back to app - console.log('ā³ Waiting for redirect to dashboard'); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - - await page.screenshot({ path: 'test-results/auth-flow-07-dashboard.png', fullPage: true }); - - // Verify we're on the dashboard - console.log('āœ… Checking dashboard loaded'); - await expect(page.locator('h1:has-text("Dashboard")')).toBeVisible({ timeout: 10000 }); - - console.log('šŸŽ‰ Authentication flow completed successfully!'); - - // Verify user profile loaded - const userEmail = await page.locator('text=test@test.com').count(); - if (userEmail > 0) { - console.log('āœ… User email visible on page'); - } - - // Final screenshot - await page.screenshot({ path: 'test-results/auth-flow-08-final.png', fullPage: true }); - }); - - test('should show console errors when accessing root URL', async ({ page }) => { - console.log('\nšŸ” Testing root URL for console errors'); - - // Go to root URL - await page.goto('http://localhost:5173/', { waitUntil: 'domcontentloaded' }); - - // Wait to capture console output - await page.waitForTimeout(5000); - - // Take screenshot - await page.screenshot({ path: 'test-results/auth-console-errors.png', fullPage: true }); - - console.log(`\nšŸ“Š Captured ${consoleMessages.length} console messages`); - console.log(`šŸ“Š Captured ${consoleErrors.length} console errors/warnings`); - - // The afterEach hook will print all console output - }); - - test('should detect "Loading user profile" stuck state', async ({ page, context }) => { - console.log('\nšŸ” Testing for stuck "Loading user profile..." state'); - - // First, login normally to get Auth0 state - await page.goto('http://localhost:5173/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - await page.waitForTimeout(2000); - - await page.locator('input[name="username"]').fill('test@test.com'); - await page.locator('input[name="password"]').fill('P@ssw0rd!'); - await page.locator('button[type="submit"][data-action-button-primary="true"]').click(); - - // Wait for successful login - await page.waitForURL('**/dashboard', { timeout: 30000 }); - console.log('āœ… Successfully logged in'); - - // Now close and reopen to simulate browser refresh - await page.close(); - const newPage = await context.newPage(); - - // Set up console capture on new page - newPage.on('console', msg => { - const text = `[${msg.type()}] ${msg.text()}`; - consoleMessages.push(text); - if (msg.type() === 'error' || msg.type() === 'warning') { - consoleErrors.push(text); - } - }); - - newPage.on('pageerror', error => { - const text = `[PAGE ERROR] ${error.message}\n${error.stack}`; - consoleErrors.push(text); - }); - - console.log('šŸ”„ Reopening app (simulating browser refresh)'); - await newPage.goto('http://localhost:5173/', { waitUntil: 'domcontentloaded' }); - - // Check if stuck on loading - await newPage.waitForTimeout(3000); - - const loadingVisible = await newPage.locator('text=Loading user profile').isVisible().catch(() => false); - if (loadingVisible) { - console.log('āš ļø STUCK on "Loading user profile..." screen!'); - await newPage.screenshot({ path: 'test-results/auth-stuck-loading.png', fullPage: true }); - - // Wait longer to see if it resolves - await newPage.waitForTimeout(5000); - - const stillLoading = await newPage.locator('text=Loading user profile').isVisible().catch(() => false); - if (stillLoading) { - console.log('āŒ Still stuck after 8 seconds total'); - } else { - console.log('āœ… Eventually loaded successfully'); - } - } else { - console.log('āœ… No loading state detected - app loaded successfully'); - const dashboardVisible = await newPage.locator('h1:has-text("Dashboard")').isVisible().catch(() => false); - if (dashboardVisible) { - console.log('āœ… Dashboard is visible'); - } - } - - await newPage.screenshot({ path: 'test-results/auth-after-refresh.png', fullPage: true }); - }); -}); diff --git a/frontend/e2e/auth.setup.ts b/frontend/e2e/auth.setup.ts deleted file mode 100644 index d018e49..0000000 --- a/frontend/e2e/auth.setup.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { test as setup, expect } from '@playwright/test'; -import path from 'path'; - -/** - * Authentication setup for Playwright tests - * - * This file handles Auth0 login once and saves the authentication state - * so we don't have to log in for every test. - */ - -const authFile = path.join(__dirname, '../.auth/user.json'); - -setup('authenticate', async ({ page }) => { - // For now, we'll skip actual Auth0 login since it requires real credentials - // In production, you would: - // 1. Go to login page - // 2. Enter credentials - // 3. Wait for redirect - // 4. Save storage state - - // TODO: Implement actual Auth0 login when we have test credentials - console.log('Auth setup skipped - implement with real credentials'); -}); - -// Export helper function for tests that need authentication -export async function login(page: any, email: string = 'test@example.com') { - // This is a placeholder - implement actual Auth0 login flow - await page.goto('/login'); - - // For local testing without Auth0, you can mock the authentication - // by directly setting localStorage - if (process.env.MOCK_AUTH === 'true') { - await page.evaluate(() => { - localStorage.setItem('auth0_token', 'mock-token'); - localStorage.setItem( - 'auth0_user', - JSON.stringify({ - email: 'test@example.com', - name: 'Test User', - role: 'ADMINISTRATOR', - isApproved: true, - }) - ); - }); - await page.goto('/dashboard'); - } -} diff --git a/frontend/e2e/driver-selector.spec.ts b/frontend/e2e/driver-selector.spec.ts deleted file mode 100644 index 94269d9..0000000 --- a/frontend/e2e/driver-selector.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Inline Driver Selector', () => { - test.beforeEach(async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('http://localhost:5173/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - await page.waitForTimeout(2000); - - await page.locator('input[name="username"]').fill('test@test.com'); - await page.locator('input[name="password"]').fill('P@ssw0rd!'); - await page.locator('button[type="submit"][data-action-button-primary="true"]').click(); - - await page.waitForURL('**/dashboard', { timeout: 30000 }); - console.log('āœ… Logged in successfully'); - }); - - test('should display and click driver selector on schedule page', async ({ page }) => { - console.log('\nšŸ” Testing inline driver selector'); - - // Navigate to Schedule page - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - - await page.screenshot({ path: 'test-results/driver-selector-01-schedule-page.png', fullPage: true }); - - // Find the first event row - const firstRow = page.locator('tbody tr').first(); - await expect(firstRow).toBeVisible(); - - // Find driver cell in the first row - const driverCell = firstRow.locator('td').nth(2); // Driver is 3rd column (0-indexed) - console.log('šŸ“‹ Driver cell text:', await driverCell.textContent()); - - await page.screenshot({ path: 'test-results/driver-selector-02-before-click.png', fullPage: true }); - - // Click on the driver selector button - const driverButton = driverCell.locator('button'); - await expect(driverButton).toBeVisible({ timeout: 5000 }); - - console.log('šŸ–±ļø Clicking driver selector button'); - await driverButton.click(); - await page.waitForTimeout(500); - - await page.screenshot({ path: 'test-results/driver-selector-03-after-click.png', fullPage: true }); - - // Check if modal appeared - const modal = page.locator('text=Assign Driver'); - const isModalVisible = await modal.isVisible({ timeout: 3000 }).catch(() => false); - - if (isModalVisible) { - console.log('āœ… Modal is visible'); - await page.screenshot({ path: 'test-results/driver-selector-04-modal-visible.png', fullPage: true }); - - // Check drivers list - const driversList = await page.locator('button').filter({ hasText: /John Smith|Jane Doe|Unassigned/ }).all(); - console.log(`āœ… Found ${driversList.length} driver options in modal`); - - // Try to click "Unassigned" - const unassignedOption = page.locator('button:has-text("Unassigned")').last(); - await unassignedOption.click(); - await page.waitForTimeout(1500); - - console.log('āœ… Clicked Unassigned option'); - await page.screenshot({ path: 'test-results/driver-selector-05-after-selection.png', fullPage: true }); - } else { - console.log('āŒ Modal NOT visible after click'); - - // Debug: Take screenshot - await page.screenshot({ path: 'test-results/driver-selector-debug-no-modal.png', fullPage: true }); - } - }); - - test('should show conflict dialog when assigning conflicting driver', async ({ page }) => { - console.log('\nšŸ” Testing conflict detection'); - - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - await page.screenshot({ path: 'test-results/driver-selector-conflict-01-schedule.png', fullPage: true }); - - // Get the first two events (they have the same time, so assigning same driver creates conflict) - const rows = await page.locator('tbody tr').all(); - console.log(`šŸ“Š Found ${rows.length} events`); - - if (rows.length >= 2) { - // Assign a driver to the first event - console.log('šŸ”¹ Assigning driver to first event'); - const firstDriverCell = rows[0].locator('td').nth(2); - await firstDriverCell.locator('button').click(); - await page.waitForTimeout(500); - - // Select Amanda Washington - const amandaOption = page.locator('button').filter({ hasText: 'Amanda Washington' }); - await expect(amandaOption).toBeVisible(); - await amandaOption.click(); - await page.waitForTimeout(2000); - - console.log('āœ… Assigned Amanda Washington to first event'); - await page.screenshot({ path: 'test-results/driver-selector-conflict-02-first-assigned.png', fullPage: true }); - - // Now try to assign the same driver to the second event (same time = conflict!) - console.log('šŸ”¹ Trying to assign same driver to second event'); - const secondDriverCell = rows[1].locator('td').nth(2); - await secondDriverCell.locator('button').click(); - await page.waitForTimeout(500); - - await page.screenshot({ path: 'test-results/driver-selector-conflict-03-modal.png', fullPage: true }); - - // Select Amanda Washington again - const amandaOption2 = page.locator('button').filter({ hasText: 'Amanda Washington' }); - await amandaOption2.click(); - await page.waitForTimeout(1500); - - await page.screenshot({ path: 'test-results/driver-selector-conflict-04-after-select.png', fullPage: true }); - - // Check for conflict dialog - const conflictDialog = page.locator('text=Scheduling Conflict Detected'); - const hasConflict = await conflictDialog.isVisible({ timeout: 3000 }).catch(() => false); - - if (hasConflict) { - console.log('āœ… Conflict dialog appeared!'); - await page.screenshot({ path: 'test-results/driver-selector-conflict-05-dialog.png', fullPage: true }); - - // Verify conflict details are shown - const conflictTitle = await page.locator('.bg-yellow-50').first().textContent(); - console.log(`šŸ“‹ Conflict details: ${conflictTitle}`); - - // Click "Assign Anyway" - const assignAnywayButton = page.locator('button:has-text("Assign Anyway")'); - await assignAnywayButton.click(); - await page.waitForTimeout(1500); - - console.log('āœ… Clicked Assign Anyway - driver should now be double-booked'); - await page.screenshot({ path: 'test-results/driver-selector-conflict-06-assigned.png', fullPage: true }); - - // Verify both events now show Amanda Washington - const firstEventDriver = await rows[0].locator('td').nth(2).textContent(); - const secondEventDriver = await rows[1].locator('td').nth(2).textContent(); - - console.log(`āœ… First event driver: ${firstEventDriver?.trim()}`); - console.log(`āœ… Second event driver: ${secondEventDriver?.trim()}`); - } else { - console.log('āš ļø No conflict dialog appeared - events may not have overlapping times'); - await page.screenshot({ path: 'test-results/driver-selector-no-conflict.png', fullPage: true }); - } - } else { - console.log('āš ļø Not enough events found - skipping conflict test'); - } - }); -}); diff --git a/frontend/e2e/event-management.spec.ts b/frontend/e2e/event-management.spec.ts deleted file mode 100644 index 0078f41..0000000 --- a/frontend/e2e/event-management.spec.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * Event Management System Test Suite - * - * Tests for Event Templates and Common Events functionality: - * 1. Event Template CRUD operations - * 2. Common Event creation from templates - * 3. Adding VIPs to events (with auto-creation of transport tasks) - * 4. Removing VIPs from events - */ - -test.describe('Event Management System', () => { - test.beforeEach(async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"], button:has-text("Continue"), button:has-text("Log in")').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - }); - - test('should display event templates page with seeded data', async ({ page }) => { - await page.goto('/event-templates'); - await page.waitForLoadState('networkidle'); - - // Verify page title - await expect(page.locator('h1:has-text("Event Templates")')).toBeVisible(); - - // Verify seeded templates exist - const templateNames = ['Breakfast', 'Lunch', 'Dinner', 'Campfire Night', 'Opening Ceremony']; - - for (const name of templateNames) { - await expect(page.locator(`text=${name}`).first()).toBeVisible(); - } - - console.log('āœ“ All 5 seeded event templates are visible'); - - // Verify "New Template" button is visible - await expect(page.locator('button:has-text("New Template")')).toBeVisible(); - - await page.screenshot({ path: 'test-results/event-templates-list.png', fullPage: true }); - }); - - test('should create a new event template', async ({ page }) => { - await page.goto('/event-templates'); - await page.waitForLoadState('networkidle'); - - // Click "New Template" button - await page.locator('button:has-text("New Template")').click(); - await page.waitForTimeout(500); - - // Verify modal opened - await expect(page.locator('text=New Event Template')).toBeVisible(); - - // Fill form - await page.locator('input[placeholder*="Breakfast"]').fill('Test Activity'); - await page.locator('textarea').first().fill('A test activity for Playwright'); - await page.locator('select').first().selectOption('EVENT'); - await page.locator('input[type="number"]').fill('45'); - await page.locator('input[placeholder*="Main Dining Hall"]').fill('Test Location'); - - // Submit form - await page.locator('button:has-text("Create")').click(); - await page.waitForTimeout(1000); - - // Verify template was created in the table - await expect(page.locator('table').locator('text=Test Activity').first()).toBeVisible(); - console.log('āœ“ Successfully created new event template'); - - await page.screenshot({ path: 'test-results/event-template-created.png', fullPage: true }); - }); - - test('should edit an existing event template', async ({ page }) => { - await page.goto('/event-templates'); - await page.waitForLoadState('networkidle'); - - // Find the Breakfast template and click edit - const breakfastRow = page.locator('tr:has-text("Breakfast")').first(); - await breakfastRow.locator('button[title="Edit"]').click(); - await page.waitForTimeout(500); - - // Verify modal opened with existing data - await expect(page.locator('text=Edit Template')).toBeVisible(); - await expect(page.locator('input[value="Breakfast"]')).toBeVisible(); - - // Update description - await page.locator('textarea').first().fill('Updated breakfast description'); - - // Submit - await page.locator('button:has-text("Update")').click(); - await page.waitForTimeout(1000); - - console.log('āœ“ Successfully updated event template'); - - await page.screenshot({ path: 'test-results/event-template-updated.png', fullPage: true }); - }); - - test('should display common events page with seeded data', async ({ page }) => { - await page.goto('/common-events'); - await page.waitForLoadState('networkidle'); - - // Verify page title - await expect(page.locator('h1:has-text("Common Events")')).toBeVisible(); - - // Verify quick create template buttons - await expect(page.locator('text=Quick Create from Template:')).toBeVisible(); - await expect(page.locator('button:has-text("Breakfast")')).toBeVisible(); - await expect(page.locator('button:has-text("Lunch")')).toBeVisible(); - - // Verify seeded events (use heading selector to avoid matching buttons) - await expect(page.locator('h3:has-text("Opening Ceremony - Day 1")')).toBeVisible(); - await expect(page.locator('h3:has-text("Lunch - Day 1")')).toBeVisible(); - await expect(page.locator('h3:has-text("Campfire Night")')).toBeVisible(); - - console.log('āœ“ All seeded common events are visible'); - - await page.screenshot({ path: 'test-results/common-events-list.png', fullPage: true }); - }); - - test('should create event from template', async ({ page }) => { - await page.goto('/common-events'); - await page.waitForLoadState('networkidle'); - - // Click quick create button for Dinner template - await page.locator('button:has-text("Dinner")').click(); - await page.waitForTimeout(500); - - // Verify modal opened with pre-filled data - await expect(page.locator('text=Create New Event')).toBeVisible(); - await expect(page.locator('input[value="Dinner"]')).toBeVisible(); - - // Update event name - await page.locator('input[value="Dinner"]').fill('Dinner - Day 1'); - - // Fill datetime fields (using current date + time) - const now = new Date(); - const startTime = new Date(now); - startTime.setHours(18, 0, 0); // 6 PM - const endTime = new Date(startTime); - endTime.setHours(20, 0, 0); // 8 PM - - const formatDateTime = (date: Date) => { - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${year}-${month}-${day}T${hours}:${minutes}`; - }; - - await page.locator('input[type="datetime-local"]').first().fill(formatDateTime(startTime)); - await page.locator('input[type="datetime-local"]').nth(1).fill(formatDateTime(endTime)); - - // Submit - await page.locator('button:has-text("Create Event")').click(); - await page.waitForTimeout(1000); - - // Verify event was created (look for h3 heading in event card) - await expect(page.locator('h3:has-text("Dinner - Day 1")').first()).toBeVisible(); - console.log('āœ“ Successfully created event from template'); - - await page.screenshot({ path: 'test-results/event-created-from-template.png', fullPage: true }); - }); - - test('should add VIPs to event and create transport tasks', async ({ page }) => { - await page.goto('/common-events'); - await page.waitForLoadState('networkidle'); - - // Find Lunch - Day 1 event - const lunchEvent = page.locator('text=Lunch - Day 1').locator('..').locator('..').locator('..'); - - // Check current attendee count - const attendeesBefore = await lunchEvent.locator('text=/Attendees \\(\\d+\\)/').textContent(); - console.log(`Attendees before: ${attendeesBefore}`); - - // Click "Manage Attendees" - await lunchEvent.locator('button:has-text("Manage Attendees")').click(); - await page.waitForTimeout(500); - - // Verify modal opened - await expect(page.locator('text=Manage Attendees: Lunch - Day 1')).toBeVisible(); - - // Count currently selected VIPs - const checkboxes = await page.locator('input[type="checkbox"]').count(); - console.log(`Total VIPs available: ${checkboxes}`); - - // Select all VIPs (check all checkboxes) - const allCheckboxes = page.locator('input[type="checkbox"]'); - for (let i = 0; i < await allCheckboxes.count(); i++) { - const checkbox = allCheckboxes.nth(i); - if (!(await checkbox.isChecked())) { - await checkbox.click(); - } - } - - // Verify pickup minutes field - await expect(page.locator('input[type="number"]').first()).toHaveValue('15'); - - // Click "Add VIPs" - await page.locator('button:has-text("Add VIPs")').click(); - await page.waitForTimeout(2000); - - // Wait for success (either modal closes or alert appears) - // Check if attendees increased - console.log('āœ“ VIPs added to event (transport tasks auto-created)'); - - await page.screenshot({ path: 'test-results/vips-added-to-event.png', fullPage: true }); - }); - - test('should remove VIP from event', async ({ page }) => { - await page.goto('/common-events'); - await page.waitForLoadState('networkidle'); - - // Find Lunch - Day 1 event - const lunchEvent = page.locator('text=Lunch - Day 1').locator('..').locator('..').locator('..'); - - // Find first attendee chip with remove button (Ɨ) - const firstAttendee = lunchEvent.locator('.bg-gray-100').first(); - const attendeeName = await firstAttendee.textContent(); - console.log(`Removing attendee: ${attendeeName}`); - - // Handle the confirm dialog - page.on('dialog', dialog => dialog.accept()); - - // Click the Ɨ button - await firstAttendee.locator('button').click(); - await page.waitForTimeout(1000); - - console.log('āœ“ VIP removed from event'); - - await page.screenshot({ path: 'test-results/vip-removed-from-event.png', fullPage: true }); - }); - - test('should display event attendees and transport tasks count', async ({ page }) => { - await page.goto('/common-events'); - await page.waitForLoadState('networkidle'); - - // Find Lunch - Day 1 event - const lunchEvent = page.locator('text=Lunch - Day 1').locator('..').locator('..').locator('..'); - - // Verify attendees section exists - await expect(lunchEvent.locator('text=/Attendees \\(\\d+\\)/')).toBeVisible(); - - // Verify transport tasks count - await expect(lunchEvent.locator('text=/Transport Tasks:/')).toBeVisible(); - - // Get the counts - const attendeeText = await lunchEvent.locator('text=/Attendees \\(\\d+\\)/').textContent(); - const transportText = await lunchEvent.locator('text=/Transport Tasks: \\d+/').textContent(); - - console.log(`Event details: ${attendeeText}, ${transportText}`); - - await page.screenshot({ path: 'test-results/event-details.png', fullPage: true }); - }); - - test('should show event type badges with correct colors', async ({ page }) => { - await page.goto('/common-events'); - await page.waitForLoadState('networkidle'); - - // Check for MEAL type badge (green) - const mealBadge = page.locator('.bg-green-100').first(); - await expect(mealBadge).toBeVisible(); - await expect(mealBadge).toContainText('MEAL'); - - // Check for EVENT type badge (blue) - const eventBadge = page.locator('.bg-blue-100').first(); - await expect(eventBadge).toBeVisible(); - - console.log('āœ“ Event type badges displaying correctly'); - - await page.screenshot({ path: 'test-results/event-type-badges.png', fullPage: true }); - }); - - test('should navigate between event templates and common events', async ({ page }) => { - // Start at Event Templates - await page.goto('/event-templates'); - await page.waitForLoadState('networkidle'); - await expect(page.locator('h1:has-text("Event Templates")')).toBeVisible(); - - // Click navigation link to Common Events - await page.locator('a[href="/common-events"]').click(); - await page.waitForLoadState('networkidle'); - await expect(page.locator('h1:has-text("Common Events")')).toBeVisible(); - - // Navigate back to Event Templates - await page.locator('a[href="/event-templates"]').click(); - await page.waitForLoadState('networkidle'); - await expect(page.locator('h1:has-text("Event Templates")')).toBeVisible(); - - console.log('āœ“ Navigation between pages works correctly'); - - await page.screenshot({ path: 'test-results/event-navigation.png', fullPage: true }); - }); - - test('should verify auto-created transport tasks link to events', async ({ page }) => { - // This test verifies that when we add VIPs to an event, - // the transport tasks are created and linked to the event - - await page.goto('/common-events'); - await page.waitForLoadState('networkidle'); - - // Get transport tasks count for Campfire Night (use heading to avoid matching button) - const campfireEvent = page.locator('h3:has-text("Campfire Night")').locator('..').locator('..').locator('..'); - const transportCountText = await campfireEvent.locator('text=/Transport Tasks: \\d+/').first().textContent(); - const transportCount = parseInt(transportCountText?.match(/\d+/)?.[0] || '0'); - - console.log(`Campfire Night has ${transportCount} transport tasks`); - - // Now go to Schedule page and verify transport tasks exist for this event - await page.goto('/events'); - await page.waitForLoadState('networkidle'); - - // Look for transport tasks with "Campfire Night" in the title - const campfireTransportTasks = page.locator('text=/Transport to Campfire Night/'); - const taskCount = await campfireTransportTasks.count(); - - console.log(`Found ${taskCount} transport tasks for Campfire Night in schedule`); - - if (taskCount > 0) { - console.log('āœ“ Transport tasks are linked to events correctly'); - } - - await page.screenshot({ path: 'test-results/linked-transport-tasks.png', fullPage: true }); - }); -}); diff --git a/frontend/e2e/filter-modal.spec.ts b/frontend/e2e/filter-modal.spec.ts deleted file mode 100644 index f18bc7f..0000000 --- a/frontend/e2e/filter-modal.spec.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * Filter Modal Implementation Test - * - * Verifies the new filter modal system works correctly on the VIP page: - * 1. Search bar and filter button are visible - * 2. Filter button opens modal - * 3. Filter selections work - * 4. Apply and clear functionality works - */ - -test.describe('VIP List Filter Modal', () => { - test('should show search bar and filter button', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"], button:has-text("Continue"), button:has-text("Log in")').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs page - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Verify search bar exists - const searchBar = page.locator('input[placeholder*="Search by name"]'); - await expect(searchBar).toBeVisible(); - console.log('āœ“ Search bar is visible'); - - // Verify filter button exists - const filterButton = page.locator('button:has-text("Filters")'); - await expect(filterButton).toBeVisible(); - console.log('āœ“ Filter button is visible'); - - // Verify results count is visible - const resultsCount = page.locator('text=/Showing \\d+ of \\d+ VIPs/'); - await expect(resultsCount).toBeVisible(); - console.log('āœ“ Results count is visible'); - - await page.screenshot({ path: 'test-results/filter-modal-search-bar.png', fullPage: true }); - }); - - test('should open filter modal when clicking filter button', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs page - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Click filter button - const filterButton = page.locator('button:has-text("Filters")'); - await filterButton.click(); - await page.waitForTimeout(500); - - // Verify modal is visible - const modalDialog = page.locator('div.bg-white.rounded-lg.shadow-xl').filter({ hasText: 'Filters' }); - await expect(modalDialog).toBeVisible(); - console.log('āœ“ Filter modal opened'); - - // Verify modal has filter groups (scope to modal dialog only) - await expect(modalDialog.locator('h3', { hasText: 'Department' })).toBeVisible(); - await expect(modalDialog.locator('h3', { hasText: 'Arrival Mode' })).toBeVisible(); - console.log('āœ“ Filter groups are visible'); - - // Verify filter options (scope to modal only) - await expect(modalDialog.getByText('Office of Development')).toBeVisible(); - await expect(modalDialog.getByText('Admin', { exact: true })).toBeVisible(); - await expect(modalDialog.getByText('Flight')).toBeVisible(); - await expect(modalDialog.getByText('Self Driving')).toBeVisible(); - console.log('āœ“ Filter options are visible'); - - // Verify modal buttons - await expect(page.locator('button:has-text("Clear All")')).toBeVisible(); - await expect(page.locator('button:has-text("Apply Filters")')).toBeVisible(); - console.log('āœ“ Modal action buttons are visible'); - - await page.screenshot({ path: 'test-results/filter-modal-open.png', fullPage: true }); - }); - - test('should select filters and apply them', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs page - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Open filter modal - await page.locator('button:has-text("Filters")').click(); - await page.waitForTimeout(500); - - // Select Department filter - const devOfficeCheckbox = page.locator('label:has-text("Office of Development") input[type="checkbox"]'); - await devOfficeCheckbox.click(); - console.log('āœ“ Selected Department filter'); - - // Select Arrival Mode filter - const flightCheckbox = page.locator('label:has-text("Flight") input[type="checkbox"]'); - await flightCheckbox.click(); - console.log('āœ“ Selected Arrival Mode filter'); - - await page.screenshot({ path: 'test-results/filter-modal-selected.png', fullPage: true }); - - // Apply filters - await page.locator('button:has-text("Apply Filters")').click(); - await page.waitForTimeout(500); - - // Verify modal is closed - const modal = page.locator('div.fixed.inset-0.bg-black').first(); - await expect(modal).not.toBeVisible(); - console.log('āœ“ Modal closed after applying filters'); - - // Verify filter button shows active count - const filterButton = page.locator('button:has-text("Filters")'); - const badge = filterButton.locator('span.bg-primary'); - await expect(badge).toBeVisible(); - const badgeText = await badge.textContent(); - expect(badgeText).toBe('2'); - console.log('āœ“ Filter button shows active count: 2'); - - await page.screenshot({ path: 'test-results/filter-modal-applied.png', fullPage: true }); - }); - - test('should clear all filters', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs page - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Open filter modal and select filters - await page.locator('button:has-text("Filters")').click(); - await page.waitForTimeout(500); - await page.locator('label:has-text("Office of Development") input[type="checkbox"]').click(); - await page.locator('button:has-text("Apply Filters")').click(); - await page.waitForTimeout(500); - - // Verify Clear button is visible - const clearButton = page.locator('button:has-text("Clear")'); - await expect(clearButton).toBeVisible(); - console.log('āœ“ Clear button is visible'); - - // Click Clear button - await clearButton.click(); - await page.waitForTimeout(500); - - // Verify badge is gone - const filterButton = page.locator('button:has-text("Filters")'); - const badge = filterButton.locator('span.bg-primary'); - await expect(badge).not.toBeVisible(); - console.log('āœ“ Filter badge removed after clearing'); - - await page.screenshot({ path: 'test-results/filter-modal-cleared.png', fullPage: true }); - }); - - test('should close modal when clicking X button', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs page - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Open filter modal - await page.locator('button:has-text("Filters")').click(); - await page.waitForTimeout(500); - - // Click X button - const closeButton = page.locator('button[aria-label="Close"]').last(); - await closeButton.click(); - await page.waitForTimeout(500); - - // Verify modal is closed - const modalHeader = page.locator('h2:has-text("Filters")'); - await expect(modalHeader).not.toBeVisible(); - console.log('āœ“ Modal closed when clicking X button'); - - await page.screenshot({ path: 'test-results/filter-modal-x-closed.png', fullPage: true }); - }); -}); diff --git a/frontend/e2e/ipad-ui.spec.ts b/frontend/e2e/ipad-ui.spec.ts deleted file mode 100644 index dc3ffe2..0000000 --- a/frontend/e2e/ipad-ui.spec.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * iPad UI Optimization Test - * - * Tests the UI responsiveness on iPad viewport sizes: - * - Portrait iPad: 768x1024 - * - Landscape iPad: 1024x768 - * - * Verifies: - * 1. Mobile navigation drawer works on portrait iPad - * 2. Desktop navigation appears on landscape iPad - * 3. Tables convert to cards on portrait, show as tables on landscape - * 4. Touch targets are properly sized - * 5. Modals are properly sized for both orientations - */ - -test.describe('iPad UI - Portrait Mode (768x1024)', () => { - test.use({ viewport: { width: 768, height: 1024 } }); - - test('should show mobile navigation drawer', async ({ page }) => { - test.setTimeout(120000); // 2 minutes - - // Login first - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - // Try automatic login - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"], button:has-text("Continue"), button:has-text("Log in")').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - // Manual login fallback - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Verify we're on dashboard - expect(page.url()).toContain('/dashboard'); - - // Hamburger menu button should be visible on portrait iPad - const hamburgerButton = page.locator('button[aria-label="Open menu"]'); - await expect(hamburgerButton).toBeVisible(); - - // Desktop navigation should be hidden - const desktopNav = page.locator('nav a:has-text("VIPs")').first(); - await expect(desktopNav).not.toBeVisible(); - - // Click hamburger to open drawer - await hamburgerButton.click(); - - // Drawer should appear - const drawer = page.locator('text=VIP Coordinator').nth(1); // Second instance (in drawer) - await expect(drawer).toBeVisible(); - - // Drawer should have navigation links - await expect(page.locator('a:has-text("Dashboard")').nth(1)).toBeVisible(); - await expect(page.locator('a:has-text("VIPs")').nth(1)).toBeVisible(); - - // Close button should be visible and properly sized - const closeButton = page.locator('button[aria-label="Close menu"]'); - await expect(closeButton).toBeVisible(); - - // Verify close button size (should be at least 44x44px) - const closeBox = await closeButton.boundingBox(); - expect(closeBox?.width).toBeGreaterThanOrEqual(44); - expect(closeBox?.height).toBeGreaterThanOrEqual(44); - - await page.screenshot({ path: 'test-results/ipad-portrait-drawer-open.png', fullPage: true }); - }); - - test('should show card layout for VIP list', async ({ page }) => { - test.setTimeout(120000); - - // Login and navigate - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Open drawer and navigate to VIPs - await page.locator('button[aria-label="Open menu"]').click(); - await page.locator('a:has-text("VIPs")').nth(1).click(); - await page.waitForLoadState('networkidle'); - - // Desktop table should be hidden - const desktopTable = page.locator('table').first(); - await expect(desktopTable).not.toBeVisible(); - - // Card layout should be visible - // Look for card-specific elements (cards have rounded-lg class and shadow) - const cards = page.locator('.bg-white.shadow.rounded-lg.p-4'); - const cardCount = await cards.count(); - - if (cardCount > 0) { - console.log(`āœ“ Found ${cardCount} VIP cards in portrait mode`); - - // Verify first card has proper touch targets - const firstCard = cards.first(); - const editButton = firstCard.locator('button:has-text("Edit")'); - const editBox = await editButton.boundingBox(); - - expect(editBox?.height).toBeGreaterThanOrEqual(44); - console.log(`āœ“ Edit button height: ${editBox?.height}px (minimum 44px)`); - } else { - console.log('ℹ No VIPs in database - card layout not tested'); - } - - await page.screenshot({ path: 'test-results/ipad-portrait-vip-cards.png', fullPage: true }); - }); - - test('should properly size modal forms', async ({ page }) => { - test.setTimeout(120000); - - // Login and navigate - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs page - await page.locator('button[aria-label="Open menu"]').click(); - await page.locator('a:has-text("VIPs")').nth(1).click(); - await page.waitForLoadState('networkidle'); - - // Click "Add VIP" button - await page.locator('button:has-text("Add VIP")').click(); - await page.waitForTimeout(500); - - // Modal should appear - const modal = page.locator('text=Add New VIP').first(); - await expect(modal).toBeVisible(); - - // Verify form inputs have proper height - const nameInput = page.locator('input[name="name"]'); - const inputBox = await nameInput.boundingBox(); - expect(inputBox?.height).toBeGreaterThanOrEqual(44); - console.log(`āœ“ Input height: ${inputBox?.height}px (minimum 44px)`); - - // Verify submit button has proper height - const submitButton = page.locator('button:has-text("Create VIP")'); - const buttonBox = await submitButton.boundingBox(); - expect(buttonBox?.height).toBeGreaterThanOrEqual(44); - console.log(`āœ“ Button height: ${buttonBox?.height}px (minimum 44px)`); - - // Modal should not be wider than viewport - const modalContainer = page.locator('.bg-white.rounded-lg.shadow-xl').first(); - const modalBox = await modalContainer.boundingBox(); - expect(modalBox?.width).toBeLessThanOrEqual(768); - console.log(`āœ“ Modal width: ${modalBox?.width}px (viewport: 768px)`); - - await page.screenshot({ path: 'test-results/ipad-portrait-modal.png', fullPage: true }); - }); -}); - -test.describe('iPad UI - Landscape Mode (1024x768)', () => { - test.use({ viewport: { width: 1024, height: 768 } }); - - test('should show desktop navigation', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Desktop navigation should be visible - const desktopNav = page.locator('nav a:has-text("VIPs")').first(); - await expect(desktopNav).toBeVisible(); - - // Hamburger menu should be hidden - const hamburgerButton = page.locator('button[aria-label="Open menu"]'); - await expect(hamburgerButton).not.toBeVisible(); - - // Verify navigation has clickable links - await expect(page.locator('nav a:has-text("Dashboard")')).toBeVisible(); - await expect(page.locator('nav a:has-text("War Room")')).toBeVisible(); - await expect(page.locator('nav a:has-text("Drivers")')).toBeVisible(); - - await page.screenshot({ path: 'test-results/ipad-landscape-navigation.png', fullPage: true }); - }); - - test('should show table layout for VIP list', async ({ page }) => { - test.setTimeout(120000); - - // Login and navigate - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs using desktop nav - await page.locator('nav a:has-text("VIPs")').first().click(); - await page.waitForLoadState('networkidle'); - - // Desktop table should be visible - const desktopTable = page.locator('table').first(); - await expect(desktopTable).toBeVisible(); - - // Table headers should be visible - await expect(page.locator('th:has-text("Name")')).toBeVisible(); - await expect(page.locator('th:has-text("Organization")')).toBeVisible(); - await expect(page.locator('th:has-text("Department")')).toBeVisible(); - - await page.screenshot({ path: 'test-results/ipad-landscape-vip-table.png', fullPage: true }); - }); - - test('should show stats in 4-column grid', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Should be on dashboard - expect(page.url()).toContain('/dashboard'); - - // Stats cards should be visible - await expect(page.locator('text=Total VIPs')).toBeVisible(); - await expect(page.locator('text=Active Drivers')).toBeVisible(); - await expect(page.locator('text=Events Today')).toBeVisible(); - await expect(page.locator('text=Flights Today')).toBeVisible(); - - await page.screenshot({ path: 'test-results/ipad-landscape-dashboard.png', fullPage: true }); - }); -}); - -test.describe('iPad UI - Touch Target Verification', () => { - test.use({ viewport: { width: 768, height: 1024 } }); - - test('all interactive elements meet 44px minimum', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - const touchTargets = [ - { name: 'Hamburger Menu', selector: 'button[aria-label="Open menu"]' }, - { name: 'Sign Out Button', selector: 'button:has-text("Sign Out")' }, - ]; - - console.log('\nšŸŽÆ Verifying Touch Target Sizes:'); - console.log('================================'); - - for (const target of touchTargets) { - const element = page.locator(target.selector).first(); - - if (await element.isVisible()) { - const box = await element.boundingBox(); - - if (box) { - const meetsMinimum = box.height >= 44 && box.width >= 44; - const status = meetsMinimum ? 'āœ“' : 'āœ—'; - - console.log(`${status} ${target.name}:`); - console.log(` Width: ${box.width}px, Height: ${box.height}px`); - - expect(box.height).toBeGreaterThanOrEqual(44); - } - } - } - - console.log('================================\n'); - }); -}); diff --git a/frontend/e2e/last-name-sorting.spec.ts b/frontend/e2e/last-name-sorting.spec.ts deleted file mode 100644 index 725bfb4..0000000 --- a/frontend/e2e/last-name-sorting.spec.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * Last Name Sorting Test - * - * Verifies that sorting by Name column sorts by last name, - * ignoring titles and honorifics like "Dr.", "Mr.", etc. - */ - -test.describe('Last Name Sorting', () => { - test.beforeEach(async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"], button:has-text("Continue"), button:has-text("Log in")').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - }); - - test('should sort VIPs by last name ascending', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Note: Table starts already sorted by name ascending by default - // We need to click twice to get back to ascending after starting state - const nameHeader = page.locator('th:has-text("Name")').first(); - - // First click goes to descending - await nameHeader.click(); - await page.waitForTimeout(300); - - // Second click goes back to ascending - await nameHeader.click(); - await page.waitForTimeout(300); - - // Verify sort indicator is visible and shows ascending - const sortIndicator = nameHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - const sortText = await sortIndicator.textContent(); - expect(sortText).toBe('↑'); - console.log('āœ“ Sort indicator shows ascending (↑)'); - - // Get names after sorting - const namesAfterSort = await page.locator('tbody tr td:first-child').allTextContents(); - console.log('āœ“ VIP names after sort (by last name A-Z):', namesAfterSort.slice(0, 5)); - - // Extract last names for verification - const getLastName = (fullName: string) => { - const parts = fullName.trim().split(/\s+/); - return parts[parts.length - 1]; - }; - - const lastNamesAfterSort = namesAfterSort.map(getLastName); - console.log('āœ“ Last names after sort:', lastNamesAfterSort.slice(0, 5)); - - // Verify last names are in alphabetical order - for (let i = 0; i < lastNamesAfterSort.length - 1; i++) { - const current = lastNamesAfterSort[i].toLowerCase(); - const next = lastNamesAfterSort[i + 1].toLowerCase(); - expect(current <= next).toBeTruthy(); - } - console.log('āœ“ Last names are in alphabetical order A-Z'); - - await page.screenshot({ path: 'test-results/vip-sort-lastname-asc.png', fullPage: true }); - }); - - test('should sort VIPs by last name descending', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Table starts sorted ascending, so one click goes to descending - const nameHeader = page.locator('th:has-text("Name")').first(); - await nameHeader.click(); - await page.waitForTimeout(300); - - // Verify sort indicator shows descending - const sortIndicator = nameHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - const sortText = await sortIndicator.textContent(); - expect(sortText).toBe('↓'); - console.log('āœ“ Sort indicator shows descending (↓)'); - - // Get names after sorting - const namesAfterSort = await page.locator('tbody tr td:first-child').allTextContents(); - console.log('āœ“ VIP names after sort (by last name Z-A):', namesAfterSort.slice(0, 5)); - - // Extract last names for verification - const getLastName = (fullName: string) => { - const parts = fullName.trim().split(/\s+/); - return parts[parts.length - 1]; - }; - - const lastNamesAfterSort = namesAfterSort.map(getLastName); - console.log('āœ“ Last names after sort:', lastNamesAfterSort.slice(0, 5)); - - // Verify last names are in reverse alphabetical order - for (let i = 0; i < lastNamesAfterSort.length - 1; i++) { - const current = lastNamesAfterSort[i].toLowerCase(); - const next = lastNamesAfterSort[i + 1].toLowerCase(); - expect(current >= next).toBeTruthy(); - } - console.log('āœ“ Last names are in reverse alphabetical order Z-A'); - - await page.screenshot({ path: 'test-results/vip-sort-lastname-desc.png', fullPage: true }); - }); - - test('should sort Drivers by last name', async ({ page }) => { - await page.goto('/drivers'); - await page.waitForLoadState('networkidle'); - - // Table starts sorted ascending, click twice to ensure ascending state - const nameHeader = page.locator('th:has-text("Name")').first(); - await nameHeader.click(); - await page.waitForTimeout(300); - await nameHeader.click(); - await page.waitForTimeout(300); - - // Verify sort indicator shows ascending - const sortIndicator = nameHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - const sortText = await sortIndicator.textContent(); - expect(sortText).toBe('↑'); - console.log('āœ“ Drivers sorted by last name (ascending)'); - - // Get driver names - const driverNames = await page.locator('tbody tr td:first-child').allTextContents(); - console.log('āœ“ Driver names (sorted by last name):', driverNames.slice(0, 5)); - - // Extract and verify last names are sorted - const getLastName = (fullName: string) => { - const parts = fullName.trim().split(/\s+/); - return parts[parts.length - 1]; - }; - - const lastNames = driverNames.map(getLastName); - for (let i = 0; i < lastNames.length - 1; i++) { - const current = lastNames[i].toLowerCase(); - const next = lastNames[i + 1].toLowerCase(); - expect(current <= next).toBeTruthy(); - } - console.log('āœ“ Driver last names are in alphabetical order'); - - await page.screenshot({ path: 'test-results/driver-sort-lastname.png', fullPage: true }); - }); - - test('should handle names with titles correctly', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Ensure ascending sort (click twice from initial state) - const nameHeader = page.locator('th:has-text("Name")').first(); - await nameHeader.click(); - await page.waitForTimeout(300); - await nameHeader.click(); - await page.waitForTimeout(300); - - // Get all names - const allNames = await page.locator('tbody tr td:first-child').allTextContents(); - - // Check if any names have titles (Dr., Mr., Ms., etc.) - const namesWithTitles = allNames.filter(name => - /^(Dr\.|Mr\.|Ms\.|Mrs\.|Prof\.)/.test(name.trim()) - ); - - if (namesWithTitles.length > 0) { - console.log('āœ“ Found names with titles:', namesWithTitles); - - // Verify these names are sorted by last name, not by title - const getLastName = (fullName: string) => { - const parts = fullName.trim().split(/\s+/); - return parts[parts.length - 1]; - }; - - const lastNames = allNames.map(getLastName); - - // Check that sorting is correct - for (let i = 0; i < lastNames.length - 1; i++) { - const current = lastNames[i].toLowerCase(); - const next = lastNames[i + 1].toLowerCase(); - expect(current <= next).toBeTruthy(); - } - - console.log('āœ“ Names with titles are correctly sorted by last name, ignoring titles'); - } else { - console.log('āœ“ No titles found in current data (test would work with titled names)'); - } - - await page.screenshot({ path: 'test-results/vip-sort-titles.png', fullPage: true }); - }); -}); diff --git a/frontend/e2e/multi-vip-events.spec.ts b/frontend/e2e/multi-vip-events.spec.ts deleted file mode 100644 index 0adedda..0000000 --- a/frontend/e2e/multi-vip-events.spec.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Multi-VIP Event Management', () => { - test.beforeEach(async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('http://localhost:5173/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - await page.waitForTimeout(2000); - - await page.locator('input[name="username"]').fill('test@test.com'); - await page.locator('input[name="password"]').fill('P@ssw0rd!'); - await page.locator('button[type="submit"][data-action-button-primary="true"]').click(); - - await page.waitForURL('**/dashboard', { timeout: 30000 }); - console.log('āœ… Logged in successfully'); - }); - - test('should create event with multiple VIPs and show capacity', async ({ page }) => { - console.log('\nšŸ” Testing multi-VIP event creation'); - - // Navigate to Events page - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - - await page.screenshot({ path: 'test-results/multi-vip-01-events-page.png', fullPage: true }); - - // Click Add Event button - console.log('āž• Clicking Add Event'); - await page.locator('button:has-text("Add Event")').click(); - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/multi-vip-02-event-form.png', fullPage: true }); - - // Verify form opened - const formTitle = page.locator('h2:has-text("Add New Event")'); - await expect(formTitle).toBeVisible(); - - // Select multiple VIPs (checkboxes) - console.log('šŸ‘„ Selecting multiple VIPs'); - - // Select Dr. Robert Johnson - const robertCheckbox = page.locator('label:has-text("Dr. Robert Johnson")').locator('input[type="checkbox"]'); - await robertCheckbox.check(); - await page.waitForTimeout(300); - - // Select Ms. Sarah Williams - const sarahCheckbox = page.locator('label:has-text("Ms. Sarah Williams")').locator('input[type="checkbox"]'); - await sarahCheckbox.check(); - await page.waitForTimeout(300); - - // Select Emily Richardson - const emilyCheckbox = page.locator('label:has-text("Emily Richardson")').locator('input[type="checkbox"]'); - await emilyCheckbox.check(); - await page.waitForTimeout(300); - - await page.screenshot({ path: 'test-results/multi-vip-03-vips-selected.png', fullPage: true }); - - // Verify selected count shows 3 VIPs - const selectedText = await page.locator('text=Selected (3)').textContent(); - console.log(`āœ… Selected VIPs: ${selectedText}`); - - // Fill in event title - await page.locator('input[name="title"]').fill('Group Transport to Conference'); - - // Fill in pickup and dropoff - await page.locator('input[name="pickupLocation"]').fill('Grand Hotel Lobby'); - await page.locator('input[name="dropoffLocation"]').fill('Conference Center'); - - // Set start and end times - await page.locator('input[name="startTime"]').fill('2026-02-15T14:00'); - await page.locator('input[name="endTime"]').fill('2026-02-15T14:30'); - - // Select vehicle - Black Suburban (6 seats) - console.log('šŸš— Selecting vehicle'); - // Get the option that contains "Black Suburban" and extract its value - const suburbanOption = await page.locator('select[name="vehicleId"] option:has-text("Black Suburban")').first(); - const suburbanValue = await suburbanOption.getAttribute('value'); - await page.locator('select[name="vehicleId"]').selectOption(suburbanValue || ''); - await page.waitForTimeout(500); - - await page.screenshot({ path: 'test-results/multi-vip-04-vehicle-selected.png', fullPage: true }); - - // Verify capacity display shows 3/6 seats - const capacityText = page.locator('text=Capacity: 3/6 seats used'); - await expect(capacityText).toBeVisible(); - console.log('āœ… Capacity display shows 3/6 seats'); - - // Select driver - const johnOption = await page.locator('select[name="driverId"] option:has-text("John Smith")').first(); - const johnValue = await johnOption.getAttribute('value'); - await page.locator('select[name="driverId"]').selectOption(johnValue || ''); - - await page.screenshot({ path: 'test-results/multi-vip-05-form-filled.png', fullPage: true }); - - // Submit form - console.log('šŸ’¾ Submitting event'); - await page.locator('button:has-text("Create Event")').click(); - await page.waitForTimeout(2000); - - await page.screenshot({ path: 'test-results/multi-vip-06-after-submit.png', fullPage: true }); - - // Verify event appears in list with comma-separated VIPs - const eventRow = page.locator('tbody tr', { hasText: 'Group Transport to Conference' }); - await expect(eventRow).toBeVisible({ timeout: 5000 }); - - const vipCell = eventRow.locator('td').nth(1); // VIPs column - const vipCellText = await vipCell.textContent(); - console.log(`āœ… VIP cell text: ${vipCellText}`); - - // Should contain all three names - expect(vipCellText).toContain('Dr. Robert Johnson'); - expect(vipCellText).toContain('Ms. Sarah Williams'); - expect(vipCellText).toContain('Emily Richardson'); - - // Verify vehicle capacity shows in table - const vehicleCell = eventRow.locator('td').nth(2); // Vehicle column - const vehicleCellText = await vehicleCell.textContent(); - console.log(`āœ… Vehicle cell: ${vehicleCellText}`); - expect(vehicleCellText).toContain('Black Suburban'); - expect(vehicleCellText).toContain('3/6 seats'); - - await page.screenshot({ path: 'test-results/multi-vip-07-event-in-list.png', fullPage: true }); - }); - - test('should warn when vehicle capacity exceeded', async ({ page }) => { - console.log('\nāš ļø Testing capacity warning'); - - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - - // Click Add Event - await page.locator('button:has-text("Add Event")').click(); - await page.waitForTimeout(1000); - - // Select ALL 4 VIPs - console.log('šŸ‘„ Selecting 4 VIPs for a 4-seat sedan'); - const checkboxes = await page.locator('input[type="checkbox"]').all(); - for (const checkbox of checkboxes) { - await checkbox.check(); - await page.waitForTimeout(200); - } - - await page.screenshot({ path: 'test-results/capacity-01-all-vips-selected.png', fullPage: true }); - - // Fill form - await page.locator('input[name="title"]').fill('Over Capacity Test'); - await page.locator('input[name="startTime"]').fill('2026-02-16T10:00'); - await page.locator('input[name="endTime"]').fill('2026-02-16T10:30'); - - // Select Blue Camry (4 seats) - exactly at capacity - const camryOption = await page.locator('select[name="vehicleId"] option:has-text("Blue Camry")').first(); - const camryValue = await camryOption.getAttribute('value'); - await page.locator('select[name="vehicleId"]').selectOption(camryValue || ''); - await page.waitForTimeout(500); - - await page.screenshot({ path: 'test-results/capacity-02-4-seats-exact.png', fullPage: true }); - - // Capacity should show 4/4 - let capacityText = await page.locator('text=/Capacity:.*seats used/').textContent(); - console.log(`āœ… Capacity at limit: ${capacityText}`); - expect(capacityText).toContain('4/4'); - - // Now add one more VIP to exceed capacity - but wait, we already selected all 4 - // Instead, let's create a test where we manually try to add a 5th VIP - - // For now, just verify the warning message appears when at capacity - // The system should allow submission but show a warning - - const janeOption = await page.locator('select[name="driverId"] option:has-text("Jane Doe")').first(); - const janeValue = await janeOption.getAttribute('value'); - await page.locator('select[name="driverId"]').selectOption(janeValue || ''); - - await page.screenshot({ path: 'test-results/capacity-03-ready-to-submit.png', fullPage: true }); - - console.log('āœ… Capacity warning test complete - form allows submission at capacity'); - }); - - test('should show conflict dialog when driver double-booked', async ({ page }) => { - console.log('\nāš ļø Testing driver conflict detection from EventForm'); - - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - - // First, find an existing event with a driver assignment - const firstRow = page.locator('tbody tr').first(); - const driverCellText = await firstRow.locator('td').nth(3).textContent(); // Driver column - console.log(`šŸ“‹ Existing driver: ${driverCellText}`); - - // Get the start time of the first event - const firstEventRow = await page.locator('tbody tr').first(); - const startTimeText = await firstEventRow.locator('td').nth(4).textContent(); - console.log(`šŸ“… First event start time: ${startTimeText}`); - - // Click Add Event - await page.locator('button:has-text("Add Event")').click(); - await page.waitForTimeout(1000); - - // Select one VIP - const firstCheckbox = page.locator('input[type="checkbox"]').first(); - await firstCheckbox.check(); - - // Fill form with same time as first event - await page.locator('input[name="title"]').fill('Conflict Test Event'); - await page.locator('input[name="startTime"]').fill('2026-02-15T19:45'); - await page.locator('input[name="endTime"]').fill('2026-02-15T20:00'); - - // Assign same driver (Amanda Washington from seed data) - const amandaOption = await page.locator('select[name="driverId"] option:has-text("Amanda Washington")').first(); - const amandaValue = await amandaOption.getAttribute('value'); - await page.locator('select[name="driverId"]').selectOption(amandaValue || ''); - - await page.screenshot({ path: 'test-results/conflict-01-form-filled.png', fullPage: true }); - - // Submit - console.log('šŸ’¾ Submitting conflicting event'); - await page.locator('button:has-text("Create Event")').click(); - await page.waitForTimeout(2000); - - await page.screenshot({ path: 'test-results/conflict-02-after-submit.png', fullPage: true }); - - // Check if conflict dialog appeared - const conflictDialog = page.locator('text=Scheduling Conflict Detected'); - const hasConflict = await conflictDialog.isVisible({ timeout: 3000 }).catch(() => false); - - if (hasConflict) { - console.log('āœ… Conflict dialog appeared!'); - await page.screenshot({ path: 'test-results/conflict-03-dialog-shown.png', fullPage: true }); - - // Click "Assign Anyway" - const assignAnywayButton = page.locator('button:has-text("Assign Anyway")'); - await assignAnywayButton.click(); - await page.waitForTimeout(2000); - - console.log('āœ… Clicked Assign Anyway - event should now be created'); - await page.screenshot({ path: 'test-results/conflict-04-assigned-anyway.png', fullPage: true }); - - // Verify event was created - const newEventRow = page.locator('tbody tr', { hasText: 'Conflict Test Event' }); - await expect(newEventRow).toBeVisible({ timeout: 5000 }); - } else { - console.log('ā„¹ļø No conflict detected - driver may not have overlapping events in seed data'); - } - }); - - test('should edit existing event and add more VIPs', async ({ page }) => { - console.log('\nāœļø Testing editing event to add more VIPs'); - - await page.goto('http://localhost:5173/events'); - await page.waitForLoadState('networkidle'); - - await page.screenshot({ path: 'test-results/edit-01-events-list.png', fullPage: true }); - - // Find event with only 1 VIP - const singleVipEvent = page.locator('tbody tr').first(); - - // Click Edit button - const editButton = singleVipEvent.locator('button:has-text("Edit")'); - await editButton.click(); - await page.waitForTimeout(1000); - - await page.screenshot({ path: 'test-results/edit-02-form-opened.png', fullPage: true }); - - // Verify Edit Event form opened - const formTitle = page.locator('h2:has-text("Edit Event")'); - await expect(formTitle).toBeVisible(); - - // Add another VIP by checking additional checkbox - const uncheckedBoxes = await page.locator('input[type="checkbox"]:not(:checked)').all(); - if (uncheckedBoxes.length > 0) { - console.log(`āž• Adding ${uncheckedBoxes.length} more VIP(s)`); - await uncheckedBoxes[0].check(); - await page.waitForTimeout(500); - - await page.screenshot({ path: 'test-results/edit-03-vip-added.png', fullPage: true }); - - // Submit - await page.locator('button:has-text("Update Event")').click(); - await page.waitForTimeout(2000); - - console.log('āœ… Event updated with additional VIP'); - await page.screenshot({ path: 'test-results/edit-04-updated.png', fullPage: true }); - } else { - console.log('ā„¹ļø Event already has all VIPs selected'); - } - }); -}); diff --git a/frontend/e2e/navigation.spec.ts b/frontend/e2e/navigation.spec.ts deleted file mode 100644 index 0b2b381..0000000 --- a/frontend/e2e/navigation.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * Navigation Tests - * - * Tests the main navigation and routing functionality - */ - -test.describe('Navigation', () => { - test('should redirect to login when not authenticated', async ({ page }) => { - // Listen to console logs - page.on('console', (msg) => { - console.log(`[BROWSER ${msg.type()}]:`, msg.text()); - }); - - // Listen to page errors - page.on('pageerror', (error) => { - console.error('[BROWSER ERROR]:', error.message); - }); - - await page.goto('/dashboard'); - - // Should redirect to login - await expect(page).toHaveURL(/\/login/); - }); - - test('should show login page', async ({ page }) => { - // Listen to all logs - page.on('console', (msg) => { - console.log(`[BROWSER ${msg.type()}]:`, msg.text()); - }); - - page.on('pageerror', (error) => { - console.error('[BROWSER ERROR]:', error.message); - }); - - // Listen to network requests - page.on('request', (request) => { - console.log(`[REQUEST] ${request.method()} ${request.url()}`); - }); - - page.on('response', (response) => { - console.log(`[RESPONSE] ${response.status()} ${response.url()}`); - }); - - await page.goto('/login'); - - // Check for login elements - await expect(page.locator('text=VIP Coordinator')).toBeVisible(); - }); - - test('should have no console errors on login page', async ({ page }) => { - const errors: string[] = []; - - page.on('pageerror', (error) => { - errors.push(error.message); - console.error('[PAGE ERROR]:', error.message); - }); - - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - console.error('[CONSOLE ERROR]:', msg.text()); - } - }); - - await page.goto('/login'); - await page.waitForLoadState('networkidle'); - - // Report any errors found - if (errors.length > 0) { - console.log('\n=== ERRORS FOUND ==='); - errors.forEach((error, i) => { - console.log(`${i + 1}. ${error}`); - }); - console.log('===================\n'); - } - - expect(errors.length).toBe(0); - }); -}); diff --git a/frontend/e2e/ui-enhancements.spec.ts b/frontend/e2e/ui-enhancements.spec.ts deleted file mode 100644 index 8a4e6db..0000000 --- a/frontend/e2e/ui-enhancements.spec.ts +++ /dev/null @@ -1,333 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * UI Enhancements Test Suite - * - * Tests for the new UI improvements: - * 1. Filter chips (active filter display + removal) - * 2. Debounced search (search indicator) - * 3. Loading skeletons - * 4. Sortable columns with hover effects - */ - -test.describe('UI Enhancements', () => { - test.beforeEach(async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"], button:has-text("Continue"), button:has-text("Log in")').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - }); - - test('should show loading skeletons on VIP page', async ({ page }) => { - // Set up route to delay API response - let continueRoute: () => void; - const routePromise = new Promise((resolve) => { - continueRoute = resolve; - }); - - await page.route('**/api/v1/vips', async (route) => { - await routePromise; // Wait for our signal - await route.continue(); - }); - - // Navigate and immediately check for skeletons - const navigationPromise = page.goto('/vips'); - await page.waitForTimeout(100); // Small delay to let page start rendering - - // Check for skeleton elements while loading - const skeletonElements = page.locator('.animate-pulse'); - const count = await skeletonElements.count(); - - // Release the route to complete navigation - continueRoute!(); - await navigationPromise; - - if (count > 0) { - console.log(`āœ“ Found ${count} skeleton loading elements during load`); - } else { - console.log('āœ“ Page loaded too fast to capture skeletons (feature works)'); - } - - await page.waitForLoadState('networkidle'); - await page.screenshot({ path: 'test-results/ui-loading-complete.png', fullPage: true }); - }); - - test('should display filter chips when filters are applied', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Open filter modal - await page.locator('button:has-text("Filters")').click(); - await page.waitForTimeout(500); - - // Select a filter - const modalDialog = page.locator('div.bg-white.rounded-lg.shadow-xl').filter({ hasText: 'Filters' }); - await modalDialog.locator('label:has-text("Office of Development") input[type="checkbox"]').click(); - console.log('āœ“ Selected Department filter'); - - // Apply filters - await page.locator('button:has-text("Apply Filters")').click(); - await page.waitForTimeout(500); - - // Verify filter chip appears - const filterChip = page.locator('text=Active filters:').locator('..').locator('text=Office of Development'); - await expect(filterChip).toBeVisible(); - console.log('āœ“ Filter chip is visible'); - - await page.screenshot({ path: 'test-results/ui-filter-chips.png', fullPage: true }); - }); - - test('should remove filter when clicking X on filter chip', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Apply a filter - await page.locator('button:has-text("Filters")').click(); - await page.waitForTimeout(500); - const modalDialog = page.locator('div.bg-white.rounded-lg.shadow-xl').filter({ hasText: 'Filters' }); - await modalDialog.locator('label:has-text("Admin") input[type="checkbox"]').click(); - await page.locator('button:has-text("Apply Filters")').click(); - await page.waitForTimeout(500); - - // Verify chip exists - const chipContainer = page.locator('text=Active filters:').locator('..'); - await expect(chipContainer.locator('text=Admin')).toBeVisible(); - console.log('āœ“ Filter chip appears'); - - // Click X button on chip - const removeButton = chipContainer.locator('span:has-text("Admin")').locator('button'); - await removeButton.click(); - await page.waitForTimeout(300); - - // Verify chip is removed - await expect(chipContainer.locator('text=Admin')).not.toBeVisible(); - console.log('āœ“ Filter chip removed after clicking X'); - - await page.screenshot({ path: 'test-results/ui-filter-chip-removed.png', fullPage: true }); - }); - - test('should show searching indicator during debounced search', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Type into search box - const searchInput = page.locator('input[placeholder*="Search by name"]'); - await searchInput.fill('test'); - - // Immediately check for searching indicator (before debounce completes) - const searchingIndicator = page.locator('text=(searching...)'); - - // The indicator should appear briefly - // Note: This might be flaky depending on timing, but it demonstrates the feature - console.log('āœ“ Search input filled, debounce active'); - - // Wait for debounce to complete - await page.waitForTimeout(500); - - // Verify results are filtered - const resultsText = page.locator('text=/Showing \\d+ of \\d+ VIPs/'); - await expect(resultsText).toBeVisible(); - console.log('āœ“ Search results updated after debounce'); - - await page.screenshot({ path: 'test-results/ui-debounced-search.png', fullPage: true }); - }); - - test('should sort VIP table by name column', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Get first VIP name before sorting - const firstRowBefore = page.locator('tbody tr').first().locator('td').first(); - const firstNameBefore = await firstRowBefore.textContent(); - console.log(`āœ“ First VIP before sort: ${firstNameBefore}`); - - // Click Name column header to sort - const nameHeader = page.locator('th:has-text("Name")').first(); - await nameHeader.click(); - await page.waitForTimeout(300); - - // Click again to reverse sort - await nameHeader.click(); - await page.waitForTimeout(300); - - // Get first VIP name after sorting - const firstRowAfter = page.locator('tbody tr').first().locator('td').first(); - const firstNameAfter = await firstRowAfter.textContent(); - console.log(`āœ“ First VIP after sort: ${firstNameAfter}`); - - // Verify sort indicator is visible - const sortIndicator = nameHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - console.log('āœ“ Sort indicator visible on Name column'); - - await page.screenshot({ path: 'test-results/ui-sortable-column.png', fullPage: true }); - }); - - test('should highlight table row on hover', async ({ page }) => { - await page.goto('/drivers'); - await page.waitForLoadState('networkidle'); - - // Get a table row - const tableRow = page.locator('tbody tr').first(); - - // Verify row has hover class - const className = await tableRow.getAttribute('class'); - expect(className).toContain('hover:bg-gray-50'); - console.log('āœ“ Table row has hover effect class'); - - // Hover over the row - await tableRow.hover(); - await page.waitForTimeout(200); - - await page.screenshot({ path: 'test-results/ui-table-row-hover.png', fullPage: true }); - }); - - test('should sort Driver table by multiple columns', async ({ page }) => { - await page.goto('/drivers'); - await page.waitForLoadState('networkidle'); - - // Sort by Name - const nameHeader = page.locator('th:has-text("Name")').first(); - await nameHeader.click(); - await page.waitForTimeout(300); - - // Verify sort indicator on Name - let sortIndicator = nameHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - console.log('āœ“ Sorted by Name (ascending)'); - - // Sort by Phone - const phoneHeader = page.locator('th:has-text("Phone")').first(); - await phoneHeader.click(); - await page.waitForTimeout(300); - - // Verify sort indicator moved to Phone - sortIndicator = phoneHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - console.log('āœ“ Sorted by Phone (ascending)'); - - // Sort by Department - const deptHeader = page.locator('th:has-text("Department")').first(); - await deptHeader.click(); - await page.waitForTimeout(300); - - // Verify sort indicator moved to Department - sortIndicator = deptHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - console.log('āœ“ Sorted by Department (ascending)'); - - await page.screenshot({ path: 'test-results/ui-multiple-column-sort.png', fullPage: true }); - }); - - test('should sort Flight table by status', async ({ page }) => { - await page.goto('/flights'); - await page.waitForLoadState('networkidle'); - - // Wait for flights to load (if any exist) - const flightCount = await page.locator('tbody tr').count(); - - if (flightCount > 0) { - // Sort by Status - const statusHeader = page.locator('th:has-text("Status")').first(); - await statusHeader.click(); - await page.waitForTimeout(300); - - // Verify sort indicator - const sortIndicator = statusHeader.locator('span.text-primary'); - await expect(sortIndicator).toBeVisible(); - console.log('āœ“ Sorted flights by Status'); - - await page.screenshot({ path: 'test-results/ui-flight-sort.png', fullPage: true }); - } else { - console.log('āœ“ No flights to sort (test skipped)'); - } - }); - - test('should show filter chips for flight status filters', async ({ page }) => { - await page.goto('/flights'); - await page.waitForLoadState('networkidle'); - - // Check if there are flights (filter button only shows when flights exist) - const filterButton = page.locator('button:has-text("Filters")'); - const filterButtonCount = await filterButton.count(); - - if (filterButtonCount === 0) { - console.log('āœ“ No flights to filter (test skipped - add flights to test this feature)'); - return; - } - - // Open filter modal - await filterButton.click(); - await page.waitForTimeout(500); - - // Select multiple status filters - const modalDialog = page.locator('div.bg-white.rounded-lg.shadow-xl').filter({ hasText: 'Filters' }); - await modalDialog.locator('label:has-text("Scheduled") input[type="checkbox"]').click(); - await modalDialog.locator('label:has-text("Landed") input[type="checkbox"]').click(); - console.log('āœ“ Selected 2 flight status filters'); - - // Apply filters - await page.locator('button:has-text("Apply Filters")').click(); - await page.waitForTimeout(500); - - // Verify multiple filter chips appear - const chipContainer = page.locator('text=Active filters:').locator('..'); - await expect(chipContainer.locator('text=Scheduled')).toBeVisible(); - await expect(chipContainer.locator('text=Landed')).toBeVisible(); - console.log('āœ“ Multiple filter chips visible'); - - // Verify badge shows count of 2 - const badge = filterButton.locator('span.bg-primary'); - const badgeText = await badge.textContent(); - expect(badgeText).toBe('2'); - console.log('āœ“ Filter badge shows correct count: 2'); - - await page.screenshot({ path: 'test-results/ui-multiple-filter-chips.png', fullPage: true }); - }); - - test('should clear all filters and chips', async ({ page }) => { - await page.goto('/vips'); - await page.waitForLoadState('networkidle'); - - // Apply multiple filters - await page.locator('button:has-text("Filters")').click(); - await page.waitForTimeout(500); - const modalDialog = page.locator('div.bg-white.rounded-lg.shadow-xl').filter({ hasText: 'Filters' }); - await modalDialog.locator('label:has-text("Office of Development") input[type="checkbox"]').click(); - await modalDialog.locator('label:has-text("Flight") input[type="checkbox"]').click(); - await page.locator('button:has-text("Apply Filters")').click(); - await page.waitForTimeout(500); - - // Verify chips appear - const chipContainer = page.locator('text=Active filters:').locator('..'); - await expect(chipContainer.locator('text=Office of Development')).toBeVisible(); - await expect(chipContainer.locator('text=Flight')).toBeVisible(); - console.log('āœ“ Multiple filter chips visible'); - - // Click "Clear All" button - await page.locator('button:has-text("Clear All")').click(); - await page.waitForTimeout(300); - - // Verify all chips are removed - await expect(page.locator('text=Active filters:')).not.toBeVisible(); - console.log('āœ“ All filter chips removed'); - - // Verify badge is gone - const filterButton = page.locator('button:has-text("Filters")'); - const badge = filterButton.locator('span.bg-primary'); - await expect(badge).not.toBeVisible(); - console.log('āœ“ Filter badge removed'); - - await page.screenshot({ path: 'test-results/ui-clear-all-filters.png', fullPage: true }); - }); -}); diff --git a/frontend/e2e/user-workflow.spec.ts b/frontend/e2e/user-workflow.spec.ts deleted file mode 100644 index a54163b..0000000 --- a/frontend/e2e/user-workflow.spec.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * User Workflow Test - * - * Tests complete user workflow: - * 1. Login with Auth0 - * 2. Refresh browser - * 3. Create a VIP entry - * 4. Save it - * 5. Refresh browser - */ - -test.describe('User Workflow', () => { - test('login, refresh, create VIP, save, refresh', async ({ page }) => { - // Increase timeout to 5 minutes for manual login + workflow - test.setTimeout(300000); - // ======================================== - // SETUP: Comprehensive logging - // ======================================== - const logs: string[] = []; - const errors: string[] = []; - const networkErrors: any[] = []; - - page.on('console', (msg) => { - const text = `[BROWSER ${msg.type()}]: ${msg.text()}`; - logs.push(text); - console.log(text); - }); - - page.on('pageerror', (error) => { - const text = `[PAGE ERROR]: ${error.message}\n${error.stack}`; - errors.push(text); - console.error(text); - }); - - page.on('request', (request) => { - const text = `[→ REQUEST] ${request.method()} ${request.url()}`; - console.log(text); - }); - - page.on('response', async (response) => { - const request = response.request(); - const text = `[← RESPONSE] ${response.status()} ${request.method()} ${request.url()}`; - console.log(text); - - // Log failed requests - if (response.status() >= 400) { - const errorInfo = { - status: response.status(), - url: request.url(), - method: request.method(), - }; - networkErrors.push(errorInfo); - console.error(`[NETWORK ERROR] ${response.status()} ${request.url()}`); - - // Try to log response body for API errors - if (request.url().includes('/api/')) { - try { - const body = await response.text(); - console.error(`[ERROR BODY] ${body}`); - } catch (e) { - // Can't read body - } - } - } - }); - - // ======================================== - // STEP 1: Navigate to Login Page - // ======================================== - console.log('\n========================================'); - console.log('STEP 1: Navigate to Login Page'); - console.log('========================================\n'); - - await page.goto('/login'); - await page.waitForLoadState('networkidle'); - - // Take screenshot of login page - await page.screenshot({ path: 'test-results/01-login-page.png', fullPage: true }); - console.log('Screenshot saved: 01-login-page.png'); - - // Check if we see the login button - const loginButton = page.locator('button:has-text("Sign in with Auth0")'); - await expect(loginButton).toBeVisible({ timeout: 10000 }); - console.log('āœ“ Login button is visible'); - - // ======================================== - // STEP 2: Click Login and Authenticate - // ======================================== - console.log('\n========================================'); - console.log('STEP 2: Click Login (Auth0)'); - console.log('========================================\n'); - - // Click login button - await loginButton.click(); - console.log('Clicked "Sign in with Auth0" button'); - - // Wait for Auth0 redirect or dashboard - // This will either go to Auth0 login page or directly to dashboard if already logged in - await page.waitForTimeout(3000); - - await page.screenshot({ path: 'test-results/02-after-login-click.png', fullPage: true }); - console.log('Screenshot saved: 02-after-login-click.png'); - - // Check current URL - const currentUrl = page.url(); - console.log(`Current URL: ${currentUrl}`); - - // If we're on Auth0 login page, we need to authenticate - if (currentUrl.includes('auth0.com') || currentUrl.includes('login')) { - console.log('⚠ Auth0 login page detected - attempting automatic login'); - - try { - // Fill in email - const emailInput = page.locator('input[name="username"], input[type="email"]').first(); - await emailInput.fill('test@test.com'); - console.log('āœ“ Entered email'); - - // Fill in password - const passwordInput = page.locator('input[name="password"], input[type="password"]').first(); - await passwordInput.fill('P@ssw0rd!'); - console.log('āœ“ Entered password'); - - // Click submit button - const submitButton = page.locator('button[type="submit"], button:has-text("Continue"), button:has-text("Log in")').first(); - await submitButton.click(); - console.log('āœ“ Clicked login button'); - - // Wait for navigation to dashboard (indicating successful login) - await page.waitForURL('**/dashboard', { timeout: 30000 }); - console.log('āœ“ Successfully logged in - redirected to dashboard'); - } catch (error) { - console.error('āŒ Automatic login failed:', error); - console.log('\nšŸ”µ PAUSING TEST - Please log in manually in the browser window'); - console.log('šŸ”µ After logging in, the test will continue automatically\n'); - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - } else if (currentUrl.includes('dashboard')) { - console.log('āœ“ Already authenticated - on dashboard'); - } else if (currentUrl.includes('pending-approval')) { - console.log('⚠ User is not approved yet - on pending approval page'); - throw new Error('User needs to be approved by an administrator first'); - } - - await page.screenshot({ path: 'test-results/03-logged-in.png', fullPage: true }); - console.log('Screenshot saved: 03-logged-in.png'); - - // ======================================== - // STEP 3: Refresh Browser - // ======================================== - console.log('\n========================================'); - console.log('STEP 3: Refresh Browser'); - console.log('========================================\n'); - - await page.reload(); - await page.waitForLoadState('networkidle'); - console.log('āœ“ Browser refreshed'); - - await page.screenshot({ path: 'test-results/04-after-refresh.png', fullPage: true }); - console.log('Screenshot saved: 04-after-refresh.png'); - - // Verify we're still logged in by checking for Sign Out button - const signOutButton = page.locator('button:has-text("Sign Out")'); - await expect(signOutButton).toBeVisible({ timeout: 10000 }); - console.log('āœ“ Still logged in after refresh'); - - // ======================================== - // STEP 4: Navigate to VIPs Page - // ======================================== - console.log('\n========================================'); - console.log('STEP 4: Navigate to VIPs Page'); - console.log('========================================\n'); - - // Click on VIPs in navigation - const vipsLink = page.locator('a:has-text("VIPs")').first(); - await vipsLink.click(); - console.log('Clicked VIPs navigation link'); - - await page.waitForLoadState('networkidle'); - await page.screenshot({ path: 'test-results/05-vips-page.png', fullPage: true }); - console.log('Screenshot saved: 05-vips-page.png'); - - // ======================================== - // STEP 5: Create New VIP - // ======================================== - console.log('\n========================================'); - console.log('STEP 5: Create New VIP'); - console.log('========================================\n'); - - // Look for "Add VIP" or "New VIP" button - const addVipButton = page.locator('button').filter({ hasText: /Add VIP|New VIP|Create VIP|\+/i }).first(); - await expect(addVipButton).toBeVisible({ timeout: 10000 }); - console.log('āœ“ Found Add VIP button'); - - await addVipButton.click(); - console.log('Clicked Add VIP button'); - - await page.waitForTimeout(1000); - await page.screenshot({ path: 'test-results/06-vip-form.png', fullPage: true }); - console.log('Screenshot saved: 06-vip-form.png'); - - // ======================================== - // STEP 6: Fill Out VIP Form - // ======================================== - console.log('\n========================================'); - console.log('STEP 6: Fill Out VIP Form'); - console.log('========================================\n'); - - // Generate unique test data - const timestamp = Date.now(); - const testVipName = `Test VIP ${timestamp}`; - const testVipOrg = `Test Organization ${timestamp}`; - - console.log(`Creating VIP: ${testVipName}`); - - // Fill in the form fields - // Note: Adjust selectors based on your actual form structure - - // Name field - const nameInput = page.locator('input[name="name"], input[id="name"]').first(); - await nameInput.fill(testVipName); - console.log(`āœ“ Filled name: ${testVipName}`); - - // Organization field (if exists) - const orgInput = page.locator('input[name="organization"], input[id="organization"]').first(); - if (await orgInput.count() > 0) { - await orgInput.fill(testVipOrg); - console.log(`āœ“ Filled organization: ${testVipOrg}`); - } - - // Contact Info field (if exists) - const contactInput = page.locator('input[name="contactInfo"], input[id="contactInfo"], input[placeholder*="contact" i]').first(); - if (await contactInput.count() > 0) { - await contactInput.fill('test@example.com'); - console.log('āœ“ Filled contact info'); - } - - // Arrival Mode dropdown (if exists) - const arrivalSelect = page.locator('select[name="arrivalMode"], select[id="arrivalMode"]').first(); - if (await arrivalSelect.count() > 0) { - await arrivalSelect.selectOption('FLIGHT'); - console.log('āœ“ Selected arrival mode: FLIGHT'); - } - - // Expected Arrival date (if exists) - const arrivalDateInput = page.locator('input[name="expectedArrival"], input[id="expectedArrival"], input[type="datetime-local"]').first(); - if (await arrivalDateInput.count() > 0) { - const futureDate = new Date(); - futureDate.setDate(futureDate.getDate() + 7); - const dateString = futureDate.toISOString().slice(0, 16); - await arrivalDateInput.fill(dateString); - console.log(`āœ“ Set expected arrival: ${dateString}`); - } - - await page.screenshot({ path: 'test-results/07-vip-form-filled.png', fullPage: true }); - console.log('Screenshot saved: 07-vip-form-filled.png'); - - // ======================================== - // STEP 7: Save VIP - // ======================================== - console.log('\n========================================'); - console.log('STEP 7: Save VIP'); - console.log('========================================\n'); - - // Find and click Save button - const saveButton = page.locator('button').filter({ hasText: /Save|Create|Submit/i }).first(); - await expect(saveButton).toBeVisible({ timeout: 5000 }); - console.log('āœ“ Found Save button'); - - await saveButton.click(); - console.log('Clicked Save button'); - - // Wait for save to complete (look for success message or redirect) - await page.waitForTimeout(2000); - - await page.screenshot({ path: 'test-results/08-after-save.png', fullPage: true }); - console.log('Screenshot saved: 08-after-save.png'); - - // Check for success message or new VIP in list - const successIndicators = [ - page.locator('text=/success/i'), - page.locator('text=/created/i'), - page.locator(`text="${testVipName}"`), - ]; - - let saveSuccess = false; - for (const indicator of successIndicators) { - if (await indicator.count() > 0) { - saveSuccess = true; - console.log('āœ“ VIP save appears successful'); - break; - } - } - - if (!saveSuccess) { - console.log('⚠ Could not confirm VIP was saved - check screenshots'); - } - - // ======================================== - // STEP 8: Final Browser Refresh - // ======================================== - console.log('\n========================================'); - console.log('STEP 8: Final Browser Refresh'); - console.log('========================================\n'); - - await page.reload(); - await page.waitForLoadState('networkidle'); - console.log('āœ“ Browser refreshed'); - - await page.screenshot({ path: 'test-results/09-final-refresh.png', fullPage: true }); - console.log('Screenshot saved: 09-final-refresh.png'); - - // Verify VIP is still visible after refresh - const vipInList = page.locator(`text="${testVipName}"`); - if (await vipInList.count() > 0) { - console.log('āœ“ VIP still visible after refresh - data persisted!'); - } else { - console.log('⚠ VIP not visible after refresh - may need to navigate back to VIPs list'); - } - - // ======================================== - // FINAL REPORT - // ======================================== - console.log('\n========================================'); - console.log('TEST COMPLETE - FINAL REPORT'); - console.log('========================================\n'); - - console.log(`Total console logs: ${logs.length}`); - console.log(`Page errors: ${errors.length}`); - console.log(`Network errors: ${networkErrors.length}`); - - if (errors.length > 0) { - console.log('\nāŒ PAGE ERRORS FOUND:'); - errors.forEach((error, i) => { - console.log(`\n${i + 1}. ${error}`); - }); - } - - if (networkErrors.length > 0) { - console.log('\nāŒ NETWORK ERRORS FOUND:'); - networkErrors.forEach((error, i) => { - console.log(`\n${i + 1}. ${error.status} ${error.method} ${error.url}`); - }); - } - - if (errors.length === 0 && networkErrors.length === 0) { - console.log('\nāœ… NO ERRORS DETECTED - Test completed successfully!'); - } - - console.log('\nšŸ“ø Screenshots saved in test-results/ directory'); - console.log('========================================\n'); - - // Fail test if there were errors - expect(errors.length).toBe(0); - expect(networkErrors.length).toBe(0); - }); -}); diff --git a/frontend/e2e/vip-filter-check.spec.ts b/frontend/e2e/vip-filter-check.spec.ts deleted file mode 100644 index 442a6c7..0000000 --- a/frontend/e2e/vip-filter-check.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('Check VIP page filter layout', async ({ page }) => { - test.setTimeout(120000); - - // Login - await page.goto('/login'); - await page.locator('button:has-text("Sign in with Auth0")').click(); - - try { - await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); - await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); - await page.locator('button[type="submit"]').first().click(); - await page.waitForURL('**/dashboard', { timeout: 30000 }); - } catch { - await page.waitForURL('**/dashboard', { timeout: 180000 }); - } - - // Navigate to VIPs page with cache bypass - await page.goto('/vips', { waitUntil: 'networkidle' }); - await page.reload({ waitUntil: 'networkidle' }); - - // Wait a bit for any dynamic content - await page.waitForTimeout(2000); - - // Take screenshot - await page.screenshot({ path: 'test-results/vip-page-current-state.png', fullPage: true }); - - // Log the HTML of the filter section - const filterSection = await page.locator('.bg-white.shadow.rounded-lg').first().innerHTML(); - console.log('Filter section HTML:'); - console.log(filterSection); - - // Check what buttons exist on the page - const buttons = await page.locator('button').allTextContents(); - console.log('All buttons on page:', buttons); - - // Check for Filter button specifically - const hasFilterButton = buttons.some(text => text.includes('Filters')); - console.log('Has Filter button:', hasFilterButton); - - // Check for inline checkboxes - const hasInlineCheckboxes = await page.locator('label:has-text("Office of Development")').count(); - console.log('Has inline "Office of Development" checkbox:', hasInlineCheckboxes > 0); -});