import { test, expect } from '@playwright/test'; // Increase default timeout for dev-mode webpack compilation test.setTimeout(60_000); // ── Helpers ────────────────────────────────────────────────────── /** Wait for the app to hydrate (loading spinner disappears, real content appears). */ async function waitForApp(page: import('@playwright/test').Page) { // Wait for JS to load and hydrate — the spinner disappears and real header appears await page.waitForLoadState('networkidle'); // The h1 "ChronoMind" only renders after mounted=true in Dashboard await page.locator('h1').filter({ hasText: 'ChronoMind' }).waitFor({ state: 'visible', timeout: 30_000 }); } /** Open the "New Timer" modal. */ async function openCreateModal(page: import('@playwright/test').Page) { await page.getByRole('button', { name: 'New Timer' }).click(); await page.locator('h2').filter({ hasText: 'New Timer' }).waitFor({ state: 'visible', timeout: 10_000 }); } // ── Test 1: Create Alarm ───────────────────────────────────────── test.describe('Create alarm', () => { test('creates an alarm and verifies it appears on the timeline', async ({ page }) => { await page.goto('/'); await waitForApp(page); await openCreateModal(page); // Switch to Alarm tab await page.getByRole('button', { name: 'Alarm' }).click(); // Fill in the label await page.getByPlaceholder('Timer label').fill('Test Alarm E2E'); // Set a time 2 minutes from now const now = new Date(); now.setMinutes(now.getMinutes() + 2); const hh = String(now.getHours()).padStart(2, '0'); const mm = String(now.getMinutes()).padStart(2, '0'); await page.locator('input[type="time"]').fill(`${hh}:${mm}`); // Create await page.getByRole('button', { name: 'Create Alarm' }).click(); // Verify it appears in the active timers section await expect(page.getByText('Test Alarm E2E')).toBeVisible({ timeout: 5_000 }); }); }); // ── Test 2: Create Countdown ───────────────────────────────────── test.describe('Create countdown', () => { test('creates a countdown and verifies it counts down', async ({ page }) => { await page.goto('/'); await waitForApp(page); await openCreateModal(page); // Default tab is Countdown — fill label await page.getByPlaceholder('Timer label').fill('Test Countdown E2E'); // Set 1 minute (default fields: hours=0, minutes=25, seconds=0) const numberInputs = page.locator('input[type="number"]'); await numberInputs.nth(0).fill('0'); // hours await numberInputs.nth(1).fill('1'); // minutes await numberInputs.nth(2).fill('0'); // seconds // Create await page.getByRole('button', { name: 'Create Countdown' }).click(); // Verify it appears await expect(page.getByText('Test Countdown E2E')).toBeVisible({ timeout: 5_000 }); // Wait 2 seconds and verify it's still counting (not immediately dismissed) await page.waitForTimeout(2000); await expect(page.getByText('Test Countdown E2E')).toBeVisible(); }); }); // ── Test 3: Create Pomodoro ────────────────────────────────────── test.describe('Create Pomodoro', () => { test('creates a pomodoro session and verifies it starts', async ({ page }) => { await page.goto('/'); await waitForApp(page); await openCreateModal(page); // Switch to Pomodoro tab await page.getByRole('button', { name: 'Pomodoro' }).click(); // Fill label await page.getByPlaceholder('Timer label').fill('Focus E2E'); // Create with defaults (25m work, 5m break, 4 rounds) await page.getByRole('button', { name: 'Start Pomodoro' }).click(); // Verify Pomodoro appears with round info await expect(page.getByText('Focus E2E')).toBeVisible({ timeout: 5_000 }); // Pomodoro view shows round info like "Round 1/4" await expect(page.getByText(/Round 1/i)).toBeVisible({ timeout: 5_000 }); }); }); // ── Test 4: NL Input Parsing ───────────────────────────────────── test.describe('NL input', () => { test('parses natural language and creates a timer', async ({ page }) => { await page.goto('/'); await waitForApp(page); await openCreateModal(page); // Type into NL input const nlInput = page.getByPlaceholder(/meeting in 30 min/i); await nlInput.fill('meeting in 30 min'); // Verify parse preview appears (should show "Countdown" type) await expect(page.getByText(/Countdown/i).first()).toBeVisible({ timeout: 5_000 }); // Click the Create button that appears on successful parse await page.locator('button:has-text("Create")').first().click(); // Verify timer was created and modal closed await expect(page.getByText(/Meeting/i).first()).toBeVisible({ timeout: 5_000 }); }); }); // ── Test 5: Routines ───────────────────────────────────────────── test.describe('Routines', () => { test('navigates to routines page and starts a template', async ({ page }) => { await page.goto('/'); await waitForApp(page); // Navigate to routines page await page.getByRole('link', { name: 'Routines' }).click(); await page.waitForURL('**/routines', { timeout: 15_000 }); await page.waitForLoadState('networkidle'); // Verify routines page loaded await expect(page.getByText(/Routines/i).first()).toBeVisible({ timeout: 15_000 }); }); }); // ── Test 6: History & Stats Navigation ─────────────────────────── test.describe('History & Stats', () => { test('navigates to history page and verifies stats/tabs work', async ({ page }) => { await page.goto('/'); await waitForApp(page); // Navigate to history await page.getByRole('link', { name: 'History & Stats' }).click(); await page.waitForURL('**/history', { timeout: 15_000 }); await page.waitForLoadState('networkidle'); // Verify page loaded with Statistics tab by default await expect(page.getByText('History & Stats').first()).toBeVisible({ timeout: 15_000 }); await expect(page.getByRole('button', { name: 'Statistics' })).toBeVisible(); // Switch to History tab await page.getByRole('button', { name: 'History' }).click(); // Should show "No completed timers yet" or a timer list await expect(page.getByText(/timer|history|completed/i).first()).toBeVisible({ timeout: 5_000 }); // Switch to Import/Export tab await page.getByRole('button', { name: 'Import / Export' }).click(); await expect(page.getByText('Export Timers')).toBeVisible({ timeout: 5_000 }); await expect(page.getByText('Export CSV')).toBeVisible(); // Verify the local storage warning is present await expect(page.getByText(/stored locally/i)).toBeVisible(); }); }); // ── Test 7: Focus Mode ─────────────────────────────────────────── test.describe('Focus mode', () => { test('navigates to focus page and verifies presets', async ({ page }) => { await page.goto('/'); await waitForApp(page); // Navigate to focus await page.getByRole('link', { name: 'Focus Mode' }).click(); await page.waitForURL('**/focus', { timeout: 15_000 }); await page.waitForLoadState('networkidle'); // Verify focus page loaded await expect(page.getByText(/Focus/i).first()).toBeVisible({ timeout: 15_000 }); // Verify duration presets are available (look for any preset button) await expect(page.getByText(/25m|30m|45m/i).first()).toBeVisible({ timeout: 10_000 }); }); }); // ── Test 8: Create Event Countdown ────────────────────────── test.describe('Create event countdown', () => { test('creates an event countdown timer with future date', async ({ page }) => { await page.goto('/'); await waitForApp(page); await openCreateModal(page); // Switch to Event tab await page.getByRole('button', { name: 'Event' }).click(); // Fill label await page.getByPlaceholder('Timer label').fill('Vacation Countdown'); // Set a date 30 days from now const futureDate = new Date(); futureDate.setDate(futureDate.getDate() + 30); const dateStr = futureDate.toISOString().split('T')[0]; await page.locator('input[type="date"]').fill(dateStr); // Verify preview shows days await expect(page.getByText(/days from now/i)).toBeVisible({ timeout: 5_000 }); // Create await page.getByRole('button', { name: 'Create Event' }).click(); // Verify event timer appears await expect(page.getByText('Vacation Countdown')).toBeVisible({ timeout: 5_000 }); }); }); // ── Test 9: Settings Page ─────────────────────────────────── test.describe('Settings', () => { test('navigates to settings and toggles compact mode', async ({ page }) => { await page.goto('/settings'); await page.waitForLoadState('networkidle'); await expect(page.getByText('Settings').first()).toBeVisible({ timeout: 30_000 }); // Verify sections are visible await expect(page.getByText('Appearance')).toBeVisible({ timeout: 5_000 }); await expect(page.getByText('Notifications')).toBeVisible(); }); }); // ── Test 10: Keyboard Shortcuts ────────────────────────────────── test.describe('Keyboard shortcuts', () => { test('pressing ? shows shortcuts overlay', async ({ page }) => { await page.goto('/'); await waitForApp(page); // Press ? to open shortcuts (Shift+/ on US keyboard) await page.keyboard.press('Shift+/'); // Verify shortcuts overlay appears await expect(page.getByText('Keyboard Shortcuts')).toBeVisible({ timeout: 5_000 }); }); });