import { test, expect } from '@playwright/test'; // ── Helpers ────────────────────────────────────────────────────── /** Wait for the app to hydrate (ChronoMind header visible). */ async function waitForApp(page: import('@playwright/test').Page) { await page.waitForSelector('text=ChronoMind', { timeout: 15_000 }); } /** Open the "New Timer" modal. */ async function openCreateModal(page: import('@playwright/test').Page) { await page.click('button:has-text("New Timer")'); await page.waitForSelector('text=New Timer'); } // ── 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.click('button:has-text("Alarm")'); // Fill in the label const labelInput = page.locator('input[placeholder="Timer label"]'); await labelInput.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'); const timeInput = page.locator('input[type="time"]'); await timeInput.fill(`${hh}:${mm}`); // Create await page.click('button:has-text("Create Alarm")'); // Verify it appears in the active timers section await expect(page.locator('text=Test Alarm E2E')).toBeVisible(); }); }); // ── 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 const labelInput = page.locator('input[placeholder="Timer label"]'); await labelInput.fill('Test Countdown E2E'); // Set 1 minute (default fields: hours=0, minutes=25, seconds=0) // Clear minutes field and type 1 const minutesInput = page.locator('input[type="number"]').nth(1); await minutesInput.fill('1'); // Hours should be 0 const hoursInput = page.locator('input[type="number"]').nth(0); await hoursInput.fill('0'); // Seconds = 0 const secondsInput = page.locator('input[type="number"]').nth(2); await secondsInput.fill('0'); // Create await page.click('button:has-text("Create Countdown")'); // Verify it appears await expect(page.locator('text=Test Countdown E2E')).toBeVisible(); // Wait 2 seconds and verify it's still counting (not immediately dismissed) await page.waitForTimeout(2000); await expect(page.locator('text=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.click('button:has-text("Pomodoro")'); // Fill label const labelInput = page.locator('input[placeholder="Timer label"]'); await labelInput.fill('Focus E2E'); // Create with defaults (25m work, 5m break, 4 rounds) await page.click('button:has-text("Start Pomodoro")'); // Verify Pomodoro appears with round info await expect(page.locator('text=Focus E2E')).toBeVisible(); // Pomodoro view shows round info like "Round 1/4" await expect(page.locator('text=/Round 1/i')).toBeVisible(); }); }); // ── 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.locator('input[placeholder*="meeting in 30 min"]'); await nlInput.fill('meeting in 30 min'); // Verify parse preview appears (should show "Countdown" type) await expect(page.locator('text=/Countdown.*Meeting/i')).toBeVisible({ timeout: 3000 }); // Click the Create button that appears on successful parse const createBtn = page.locator('button:has-text("Create")').first(); await createBtn.click(); // Verify timer was created and modal closed await expect(page.locator('text=/Meeting/i').first()).toBeVisible(); }); }); // ── 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.click('a[title="Routines"]'); await page.waitForURL('**/routines'); // Verify routines page loaded await expect(page.locator('text=/Routines/i').first()).toBeVisible(); // Check that templates are visible await expect(page.locator('text=/Morning/i').first()).toBeVisible({ timeout: 5000 }); }); }); // ── 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.click('a[title="History & Stats"]'); await page.waitForURL('**/history'); // Verify page loaded with Statistics tab by default await expect(page.locator('text=History & Stats')).toBeVisible(); await expect(page.locator('button:has-text("Statistics")')).toBeVisible(); // Switch to History tab await page.click('button:has-text("History")'); // Should show "No completed timers yet" or a timer list await expect(page.locator('text=/timer|history|completed/i').first()).toBeVisible(); // Switch to Import/Export tab await page.click('button:has-text("Import / Export")'); await expect(page.locator('text=Export Timers')).toBeVisible(); await expect(page.locator('text=Export CSV')).toBeVisible(); // Verify the local storage warning is present await expect(page.locator('text=/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.click('a[title="Focus Mode"]'); await page.waitForURL('**/focus'); // Verify focus page loaded await expect(page.locator('text=/Focus/i').first()).toBeVisible(); // Verify duration presets are available await expect(page.locator('text=/15m|25m|30m|45m|60m|90m/i').first()).toBeVisible({ timeout: 5000 }); }); }); // ── 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.click('button:has-text("Event")'); // Fill label const labelInput = page.locator('input[placeholder="Timer label"]'); await labelInput.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]; const dateInput = page.locator('input[type="date"]'); await dateInput.fill(dateStr); // Verify preview shows days await expect(page.locator('text=/days from now/i')).toBeVisible(); // Create await page.click('button:has-text("Create Event")'); // Verify event timer appears with days display await expect(page.locator('text=Vacation Countdown')).toBeVisible(); await expect(page.locator('text=/day/i').first()).toBeVisible(); }); }); // ── Test 9: Settings Page ─────────────────────────────────── test.describe('Settings', () => { test('navigates to settings and toggles compact mode', async ({ page }) => { await page.goto('/settings'); await page.waitForSelector('text=Settings', { timeout: 15_000 }); // Verify sections are visible await expect(page.locator('text=Appearance')).toBeVisible(); await expect(page.locator('text=Compact Mode')).toBeVisible(); await expect(page.locator('text=Notifications')).toBeVisible(); await expect(page.locator('text=Sound Preview')).toBeVisible(); // Toggle compact mode on const compactButton = page.locator('button:has-text("Off")').first(); await compactButton.click(); await expect(page.locator('button:has-text("On")').first()).toBeVisible(); // Toggle back off await page.locator('button:has-text("On")').first().click(); await expect(page.locator('button:has-text("Off")').first()).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 await page.keyboard.press('?'); // Verify shortcuts overlay appears await expect(page.locator('text=Keyboard Shortcuts')).toBeVisible(); }); });