import { test, expect } from '@playwright/test'; /** * E2E tests for the Tracker dashboard. * * Tests cover login page, authentication redirects, public roadmap, * and dashboard structure. */ test.describe('Tracker Login Page', () => { test('shows login form with correct branding', async ({ page }) => { await page.goto('/login'); await expect(page.getByText('Tracker')).toBeVisible(); await expect(page.getByText('Feature requests, bugs & task management')).toBeVisible(); await expect(page.getByLabel('Email')).toBeVisible(); await expect(page.getByLabel('Password')).toBeVisible(); await expect(page.getByRole('button', { name: /sign in/i })).toBeVisible(); }); test('shows credentials hint', async ({ page }) => { await page.goto('/login'); await expect(page.getByText(/platform-service credentials/i)).toBeVisible(); }); test('shows error for invalid credentials', async ({ page }) => { await page.goto('/login'); await page.getByLabel('Email').fill('bad@user.com'); await page.getByLabel('Password').fill('wrongpassword'); await page.getByRole('button', { name: /sign in/i }).click(); await expect(page.getByText(/failed|error|invalid/i)).toBeVisible({ timeout: 10000, }); }); test('shows loading state on submit', async ({ page }) => { // Block API to keep loading state visible await page.route( '**/api/auth/**', route => new Promise(resolve => setTimeout(() => resolve(route.abort()), 3000)) ); await page.goto('/login'); await page.getByLabel('Email').fill('test@example.com'); await page.getByLabel('Password').fill('password123'); await page.getByRole('button', { name: /sign in/i }).click(); await expect(page.getByText('Signing in...')).toBeVisible(); }); }); test.describe('Tracker — Protected Routes', () => { test('/ redirects to login when not authenticated', async ({ page }) => { await page.goto('/'); await expect(page).toHaveURL(/\/login/, { timeout: 10000 }); }); test('/dashboard redirects to login when not authenticated', async ({ page }) => { await page.goto('/dashboard'); await expect(page).toHaveURL(/\/login/, { timeout: 10000 }); }); }); test.describe('Tracker — Public Roadmap', () => { test('roadmap page renders board layout', async ({ page }) => { await page.goto('/roadmap'); // The roadmap page is public — no auth required await expect(page.getByText(/roadmap/i).first()).toBeVisible({ timeout: 10000, }); }); test('roadmap page has search and filter controls', async ({ page }) => { await page.goto('/roadmap'); // Should have a search input await expect(page.getByPlaceholder(/search/i).first()).toBeVisible({ timeout: 10000 }); }); test('roadmap page has submit suggestion button', async ({ page }) => { await page.goto('/roadmap'); await expect(page.getByRole('button', { name: /suggest|submit|new/i }).first()).toBeVisible({ timeout: 10000, }); }); test('roadmap page shows status columns in board view', async ({ page }) => { await page.goto('/roadmap'); // Board view shows Planned, In Progress, Complete columns await expect(page.getByText('Planned').first()).toBeVisible({ timeout: 10000, }); await expect(page.getByText('In Progress').first()).toBeVisible(); await expect(page.getByText('Complete').first()).toBeVisible(); }); test('roadmap page can toggle between board and list view', async ({ page }) => { await page.goto('/roadmap'); // Look for view toggle buttons const listBtn = page.getByRole('button', { name: /list/i }); if (await listBtn.isVisible()) { await listBtn.click(); // Should now show list view await expect(page.locator("table, [role='list']").first()).toBeVisible({ timeout: 5000, }); } }); }); test.describe('Tracker — Health', () => { test('GET /api/health returns ok', async ({ request }) => { const res = await request.get('/api/health'); expect(res.ok()).toBeTruthy(); const body = await res.json(); expect(body.status).toBe('ok'); }); });