import { test, expect } from '@playwright/test'; import { setActiveProfile, HENDRIK_ID } from './fixtures/profile'; import { clearShoppingCart } from './fixtures/api-cleanup'; test.describe('Einkaufsliste E2E', () => { test.beforeEach(async ({ request }) => { await clearShoppingCart(request); }); test.afterEach(async ({ request }) => { await clearShoppingCart(request); }); test('Cart-Button auf der Wunschliste erzeugt Header-Badge', async ({ page, request }) => { await setActiveProfile(page, HENDRIK_ID); // Voraussetzung: Dev-System hat mindestens einen Wunschlisten-Eintrag const wlRes = await request.get('/api/wishlist?sort=popular'); const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] }; test.skip(wlBody.entries.length === 0, 'Wunschliste leer auf Dev — Test uebersprungen'); await page.goto('/wishlist'); await page.getByLabel('In den Einkaufswagen').first().click(); await expect(page.getByLabel(/Einkaufsliste \(\d+\)/)).toBeVisible(); }); test('Shopping-List-Seite zeigt Rezept-Chip + Zutaten', async ({ page, request }) => { await setActiveProfile(page, HENDRIK_ID); const wlRes = await request.get('/api/wishlist?sort=popular'); const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] }; test.skip(wlBody.entries.length === 0, 'Wunschliste leer'); const recipeId = wlBody.entries[0].recipe_id; await request.post('/api/shopping-list/recipe', { data: { recipe_id: recipeId } }); await page.goto('/shopping-list'); await expect(page.getByRole('heading', { level: 1, name: 'Einkaufsliste' })).toBeVisible(); // Chip fuers Rezept sichtbar await expect(page.getByLabel('Portion weniger').first()).toBeVisible(); // Mindestens eine Zutatenzeile await expect(page.locator('.row').first()).toBeVisible(); }); test('Portions-Stepper veraendert Mengen live', async ({ page, request }) => { await setActiveProfile(page, HENDRIK_ID); const wlRes = await request.get('/api/wishlist?sort=popular'); const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] }; test.skip(wlBody.entries.length === 0, 'Wunschliste leer'); await request.post('/api/shopping-list/recipe', { data: { recipe_id: wlBody.entries[0].recipe_id, servings: 4 } }); await page.goto('/shopping-list'); // Menge der ersten Zeile "vorher" lesen const qtyBefore = await page.locator('.qty').first().textContent(); // Portion +1 await page.getByLabel('Portion mehr').first().click(); // Nach Fetch+Rerender muss die Menge sich aendern (ungleich dem Vorher-Wert) await expect .poll(async () => (await page.locator('.qty').first().textContent())?.trim()) .not.toBe(qtyBefore?.trim()); }); test('Abhaken: Zeile durchgestrichen, Badge-Count sinkt, persistiert nach Reload', async ({ page, request }) => { await setActiveProfile(page, HENDRIK_ID); const wlRes = await request.get('/api/wishlist?sort=popular'); const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] }; test.skip(wlBody.entries.length === 0, 'Wunschliste leer'); await request.post('/api/shopping-list/recipe', { data: { recipe_id: wlBody.entries[0].recipe_id } }); await page.goto('/shopping-list'); const countBadge = page.getByLabel(/Einkaufsliste \(\d+\)/); const badgeTextBefore = await countBadge.textContent(); const numBefore = Number((badgeTextBefore ?? '').replace(/\D+/g, '')) || 0; // Anzahl abgehakter Zeilen vorher (sollte 0 sein, weil beforeEach cart leert) const checkedBefore = await page.locator('label.row.checked').count(); // Erste Zeile abhaken — Playwright laesst die Checkbox direkt interagieren await page.locator('label.row').first().locator('input[type=checkbox]').check(); // Nach Store-Refresh sortiert SQL "ORDER BY checked ASC" abgehakte ans // Ende, also pruefen wir die Gesamtzahl, nicht die Position. await expect(page.locator('label.row.checked')).toHaveCount(checkedBefore + 1); // Badge muss sinken (nach Store-Refresh) await expect .poll(async () => { const t = (await countBadge.textContent()) ?? ''; return Number(t.replace(/\D+/g, '')) || 0; }) .toBeLessThan(numBefore); // Reload persistiert await page.reload(); await expect(page.locator('label.row.checked').first()).toBeVisible(); }); test('Liste leeren: Confirm + Empty-State + Badge weg', async ({ page, request }) => { await setActiveProfile(page, HENDRIK_ID); const wlRes = await request.get('/api/wishlist?sort=popular'); const wlBody = (await wlRes.json()) as { entries: { recipe_id: number }[] }; test.skip(wlBody.entries.length === 0, 'Wunschliste leer'); await request.post('/api/shopping-list/recipe', { data: { recipe_id: wlBody.entries[0].recipe_id } }); await page.goto('/shopping-list'); await page.getByRole('button', { name: 'Liste leeren' }).click(); // Confirm-Dialog (ConfirmAction nutzt einen App-eigenen Dialog, kein native) await page.getByRole('button', { name: 'Leeren', exact: true }).click(); await expect(page.getByText('Einkaufswagen ist leer.')).toBeVisible(); await expect(page.getByLabel(/Einkaufsliste \(\d+\)/)).toHaveCount(0); }); });