import { test, expect, Page } from '@playwright/test'; // Helper function to login async function login(page: Page) { await page.goto('/login'); await page.fill('input[name="email"]', 'test@example.com'); await page.fill('input[name="password"]', 'TestPassword123!'); await page.click('button[type="submit"]'); await page.waitForURL('/'); } // Helper function to create a test recipe async function createTestRecipe(page: Page, title: string) { await page.goto('/recipes/new'); await page.fill('input[name="title"]', title); await page.fill('textarea[name="description"]', `Delicious ${title}`); // Add ingredient await page.click('button:has-text("Add Ingredient")'); await page.fill('input[name="ingredients[0].name"]', 'Test Ingredient'); await page.fill('input[name="ingredients[0].amount"]', '2'); await page.fill('input[name="ingredients[0].unit"]', 'cups'); // Add instruction await page.click('button:has-text("Add Step")'); await page.fill('textarea[name="instructions[0].text"]', 'Mix ingredients'); // Set servings await page.fill('input[name="servings"]', '4'); // Submit await page.click('button[type="submit"]:has-text("Save Recipe")'); await page.waitForURL(/\/recipes\/[a-z0-9]+/); } test.describe('Meal Planner E2E Tests', () => { test.beforeEach(async ({ page }) => { // Create test user if needed and login await page.goto('/register'); const timestamp = Date.now(); const email = `mealplanner-e2e-${timestamp}@example.com`; try { await page.fill('input[name="email"]', email); await page.fill('input[name="password"]', 'TestPassword123!'); await page.fill('input[name="name"]', 'E2E Test User'); await page.click('button[type="submit"]'); await page.waitForURL('/'); } catch (error) { // User might already exist, try logging in await login(page); } }); test('should display meal planner page', async ({ page }) => { await page.goto('/meal-planner'); await expect(page.locator('h1:has-text("Meal Planner")')).toBeVisible(); await expect(page.locator('button:has-text("Calendar")')).toBeVisible(); await expect(page.locator('button:has-text("Weekly List")')).toBeVisible(); }); test('should toggle between calendar and weekly views', async ({ page }) => { await page.goto('/meal-planner'); // Should start in calendar view await expect(page.locator('.calendar-view')).toBeVisible(); // Click Weekly List button await page.click('button:has-text("Weekly List")'); // Should show weekly view await expect(page.locator('.weekly-list-view')).toBeVisible(); await expect(page.locator('.calendar-view')).not.toBeVisible(); // Click Calendar button await page.click('button:has-text("Calendar")'); // Should show calendar view again await expect(page.locator('.calendar-view')).toBeVisible(); }); test('should navigate between months', async ({ page }) => { await page.goto('/meal-planner'); // Get current month text const currentMonthText = await page.locator('.date-range h2').textContent(); // Click Next button await page.click('button:has-text("Next")'); // Month should have changed const nextMonthText = await page.locator('.date-range h2').textContent(); expect(nextMonthText).not.toBe(currentMonthText); // Click Previous button await page.click('button:has-text("Previous")'); // Should be back to original month const backToMonthText = await page.locator('.date-range h2').textContent(); expect(backToMonthText).toBe(currentMonthText); }); test('should navigate to today', async ({ page }) => { await page.goto('/meal-planner'); // Navigate to next month await page.click('button:has-text("Next")'); // Click Today button await page.click('button:has-text("Today")'); // Should have a cell with "today" class await expect(page.locator('.calendar-cell.today')).toBeVisible(); }); test('should add meal to meal plan', async ({ page }) => { // First, create a test recipe await createTestRecipe(page, 'E2E Test Pancakes'); // Go to meal planner await page.goto('/meal-planner'); // Click "Add Meal" button on a date await page.click('.calendar-cell .btn-add-meal').first(); // Wait for modal to appear await expect(page.locator('.add-meal-modal')).toBeVisible(); // Search for the recipe await page.fill('input[placeholder*="Search"]', 'E2E Test Pancakes'); // Wait for recipe to appear and click it await page.click('.recipe-item:has-text("E2E Test Pancakes")'); // Select meal type await page.selectOption('select#mealType', 'BREAKFAST'); // Set servings await page.fill('input#servings', '6'); // Add notes await page.fill('textarea#notes', 'Extra syrup'); // Click Add Meal button await page.click('button[type="submit"]:has-text("Add Meal")'); // Wait for modal to close await expect(page.locator('.add-meal-modal')).not.toBeVisible(); // Verify meal appears in calendar await expect(page.locator('.meal-card:has-text("E2E Test Pancakes")')).toBeVisible(); await expect(page.locator('.meal-type-label:has-text("BREAKFAST")')).toBeVisible(); }); test('should remove meal from meal plan', async ({ page }) => { // First, add a meal (reusing the setup from previous test) await createTestRecipe(page, 'E2E Test Sandwich'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await expect(page.locator('.add-meal-modal')).toBeVisible(); await page.fill('input[placeholder*="Search"]', 'E2E Test Sandwich'); await page.click('.recipe-item:has-text("E2E Test Sandwich")'); await page.click('button[type="submit"]:has-text("Add Meal")'); await expect(page.locator('.add-meal-modal')).not.toBeVisible(); // Verify meal is visible await expect(page.locator('.meal-card:has-text("E2E Test Sandwich")')).toBeVisible(); // Click remove button await page.click('.btn-remove-meal').first(); // Confirm the dialog page.on('dialog', dialog => dialog.accept()); // Verify meal is removed await expect(page.locator('.meal-card:has-text("E2E Test Sandwich")')).not.toBeVisible(); }); test('should display meals in weekly list view', async ({ page }) => { // Add a meal first await createTestRecipe(page, 'E2E Test Salad'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await expect(page.locator('.add-meal-modal')).toBeVisible(); await page.fill('input[placeholder*="Search"]', 'E2E Test Salad'); await page.click('.recipe-item:has-text("E2E Test Salad")'); await page.selectOption('select#mealType', 'LUNCH'); await page.click('button[type="submit"]:has-text("Add Meal")'); // Switch to weekly view await page.click('button:has-text("Weekly List")'); // Verify meal appears in weekly view await expect(page.locator('.weekly-list-view')).toBeVisible(); await expect(page.locator('.meal-card:has-text("E2E Test Salad")')).toBeVisible(); await expect(page.locator('h3:has-text("LUNCH")')).toBeVisible(); }); test('should generate shopping list', async ({ page }) => { // Add a meal with ingredients first await createTestRecipe(page, 'E2E Test Soup'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await expect(page.locator('.add-meal-modal')).toBeVisible(); await page.fill('input[placeholder*="Search"]', 'E2E Test Soup'); await page.click('.recipe-item:has-text("E2E Test Soup")'); await page.click('button[type="submit"]:has-text("Add Meal")'); await expect(page.locator('.add-meal-modal')).not.toBeVisible(); // Click Generate Shopping List button await page.click('button:has-text("Generate Shopping List")'); // Wait for shopping list modal await expect(page.locator('.shopping-list-modal')).toBeVisible(); // Wait for list to generate await expect(page.locator('.shopping-list-items')).toBeVisible(); // Verify ingredient appears await expect(page.locator('.ingredient-name:has-text("Test Ingredient")')).toBeVisible(); // Verify amount await expect(page.locator('.ingredient-amount:has-text("2 cups")')).toBeVisible(); // Verify recipe source await expect(page.locator('.ingredient-recipes:has-text("E2E Test Soup")')).toBeVisible(); }); test('should check off items in shopping list', async ({ page }) => { // Setup: add a meal await createTestRecipe(page, 'E2E Test Pasta'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await page.fill('input[placeholder*="Search"]', 'E2E Test Pasta'); await page.click('.recipe-item:has-text("E2E Test Pasta")'); await page.click('button[type="submit"]:has-text("Add Meal")'); // Open shopping list await page.click('button:has-text("Generate Shopping List")'); await expect(page.locator('.shopping-list-modal')).toBeVisible(); // Find and check a checkbox const checkbox = page.locator('.shopping-list-item input[type="checkbox"]').first(); await checkbox.check(); await expect(checkbox).toBeChecked(); // Uncheck it await checkbox.uncheck(); await expect(checkbox).not.toBeChecked(); }); test('should regenerate shopping list with custom date range', async ({ page }) => { await page.goto('/meal-planner'); // Open shopping list await page.click('button:has-text("Generate Shopping List")'); await expect(page.locator('.shopping-list-modal')).toBeVisible(); // Change date range const today = new Date(); const nextWeek = new Date(today); nextWeek.setDate(today.getDate() + 7); await page.fill('input#startDate', today.toISOString().split('T')[0]); await page.fill('input#endDate', nextWeek.toISOString().split('T')[0]); // Click Regenerate button await page.click('button:has-text("Regenerate")'); // Should show loading state briefly await expect(page.locator('.loading:has-text("Generating")')).toBeVisible(); // Should complete await expect(page.locator('.loading:has-text("Generating")')).not.toBeVisible({ timeout: 5000 }); }); test('should copy shopping list to clipboard', async ({ page }) => { // Setup: add a meal await createTestRecipe(page, 'E2E Test Pizza'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await page.fill('input[placeholder*="Search"]', 'E2E Test Pizza'); await page.click('.recipe-item:has-text("E2E Test Pizza")'); await page.click('button[type="submit"]:has-text("Add Meal")'); // Open shopping list await page.click('button:has-text("Generate Shopping List")'); await expect(page.locator('.shopping-list-modal')).toBeVisible(); // Grant clipboard permissions await page.context().grantPermissions(['clipboard-read', 'clipboard-write']); // Mock the alert dialog page.on('dialog', dialog => dialog.accept()); // Click Copy to Clipboard button await page.click('button:has-text("Copy to Clipboard")'); // Verify clipboard content (this requires clipboard permissions) const clipboardText = await page.evaluate(() => navigator.clipboard.readText()); expect(clipboardText).toContain('Test Ingredient'); expect(clipboardText).toContain('2 cups'); }); test('should display meal notes in meal plan', async ({ page }) => { // Add a meal with notes await createTestRecipe(page, 'E2E Test Steak'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await page.fill('input[placeholder*="Search"]', 'E2E Test Steak'); await page.click('.recipe-item:has-text("E2E Test Steak")'); await page.fill('textarea#notes', 'Cook medium rare'); await page.click('button[type="submit"]:has-text("Add Meal")'); // Switch to weekly view to see full details await page.click('button:has-text("Weekly List")'); // Verify notes appear await expect(page.locator('.meal-notes:has-text("Cook medium rare")')).toBeVisible(); }); test('should navigate to recipe from meal card', async ({ page }) => { // Add a meal await createTestRecipe(page, 'E2E Test Burrito'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await page.fill('input[placeholder*="Search"]', 'E2E Test Burrito'); await page.click('.recipe-item:has-text("E2E Test Burrito")'); await page.click('button[type="submit"]:has-text("Add Meal")'); // Click on the meal card await page.click('.meal-card:has-text("E2E Test Burrito") .meal-card-content'); // Should navigate to recipe page await page.waitForURL(/\/recipes\/[a-z0-9]+/); await expect(page.locator('h1:has-text("E2E Test Burrito")')).toBeVisible(); }); test('should close modals when clicking overlay', async ({ page }) => { await page.goto('/meal-planner'); // Open add meal modal await page.click('.calendar-cell .btn-add-meal').first(); await expect(page.locator('.add-meal-modal')).toBeVisible(); // Click overlay (outside modal) await page.click('.modal-overlay', { position: { x: 10, y: 10 } }); // Modal should close await expect(page.locator('.add-meal-modal')).not.toBeVisible(); // Open shopping list modal await page.click('button:has-text("Generate Shopping List")'); await expect(page.locator('.shopping-list-modal')).toBeVisible(); // Click overlay await page.click('.modal-overlay', { position: { x: 10, y: 10 } }); // Modal should close await expect(page.locator('.shopping-list-modal')).not.toBeVisible(); }); test('should persist meals after page reload', async ({ page }) => { // Add a meal await createTestRecipe(page, 'E2E Test Tacos'); await page.goto('/meal-planner'); await page.click('.calendar-cell .btn-add-meal').first(); await page.fill('input[placeholder*="Search"]', 'E2E Test Tacos'); await page.click('.recipe-item:has-text("E2E Test Tacos")'); await page.click('button[type="submit"]:has-text("Add Meal")'); // Verify meal is visible await expect(page.locator('.meal-card:has-text("E2E Test Tacos")')).toBeVisible(); // Reload page await page.reload(); // Meal should still be visible await expect(page.locator('.meal-card:has-text("E2E Test Tacos")')).toBeVisible(); }); });