import { test, expect } from '@playwright/test'; import { mkdir } from 'node:fs/promises'; const id = '10000000-0000-4000-8000-000000000001'; test('editor remains usable on the current viewport in dark mode', async ({ page }, info) => { await page.goto('/admin'); await page.getByLabel('Benutzername').fill('testadmin'); await page.getByLabel('Passwort', { exact: true }).fill(process.env.TEST_PASSWORD!); await page.getByRole('button', { name: 'Anmelden', exact: true }).click(); await page.getByRole('link', { name: 'Neues Rezept' }).click(); await page.getByRole('button', { name: 'Theme wechseln', exact: true }).click(); await page.getByRole('button', { name: 'Zutaten', exact: true }).click(); await page.getByRole('textbox', { name: 'Zutat 1', exact: true }).fill('Kartoffeln'); await page.getByRole('spinbutton', { name: 'Menge 1', exact: true }).fill('400'); await page.getByRole('button', { name: 'Details für Zutat 1', exact: true }).click(); await page.getByRole('combobox', { name: 'Abschnitt 1', exact: true }).fill('Für das Gemüse'); await page .getByRole('textbox', { name: 'Hinweis 1', exact: true }) .fill('geschält und gewürfelt'); await page.getByRole('button', { name: 'Details für Zutat 1', exact: true }).click(); await page.getByRole('button', { name: 'Zutat hinzufügen' }).click(); await expect(page.getByRole('textbox', { name: 'Zutat 2', exact: true })).toBeFocused(); await page.getByRole('textbox', { name: 'Zutat 2', exact: true }).fill('Karotten'); await page.getByRole('spinbutton', { name: 'Menge 2', exact: true }).fill('200'); await page.getByRole('button', { name: 'Details für Zutat 2', exact: true }).click(); await page.getByRole('checkbox', { name: 'Optionale Zutat' }).check(); await page.getByRole('button', { name: 'Zutat 2 nach oben' }).click(); await expect(page.getByRole('textbox', { name: 'Zutat 1', exact: true })).toHaveValue('Karotten'); await page.getByRole('button', { name: 'Details für Zutat 1', exact: true }).click(); await page.getByRole('textbox', { name: 'Zutat 2', exact: true }).press('Enter'); await expect(page.getByRole('textbox', { name: 'Zutat 3', exact: true })).toBeFocused(); await page.getByRole('textbox', { name: 'Zutat 3', exact: true }).fill('Zwiebel'); await page.getByRole('spinbutton', { name: 'Menge 3', exact: true }).fill('1'); await page.getByRole('combobox', { name: 'Einheit 3', exact: true }).selectOption('Stück'); await page.getByRole('textbox', { name: 'Zutat 3', exact: true }).press('Enter'); await page.getByRole('textbox', { name: 'Zutat 4', exact: true }).fill('Olivenöl'); await page.getByRole('spinbutton', { name: 'Menge 4', exact: true }).fill('2'); await page.getByRole('combobox', { name: 'Einheit 4', exact: true }).selectOption('EL'); page.on('dialog', (dialog) => dialog.accept()); await page.reload(); await page.getByRole('button', { name: 'Zutaten', exact: true }).click(); await expect(page.getByRole('textbox', { name: 'Zutat 1', exact: true })).toHaveValue('Karotten'); await expect(page.locator('.ingredient-entry').nth(0)).toContainText('optional'); await expect(page.locator('.ingredient-entry').nth(1)).toContainText('geschält und gewürfelt'); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); await page.screenshot({ path: `docs/screenshots/editor-dark-${info.project.name}.png`, fullPage: true, animations: 'disabled', }); await page.getByRole('button', { name: 'Theme wechseln', exact: true }).click(); await page.screenshot({ path: `docs/screenshots/ingredients-light-${info.project.name}.png`, fullPage: true, animations: 'disabled', }); if (info.project.name === 'desktop') { await page.locator('.ingredient-grip').first().dragTo(page.locator('.ingredient-entry').nth(1)); await expect(page.getByRole('textbox', { name: 'Zutat 1', exact: true })).toHaveValue( 'Kartoffeln', ); } await page.getByRole('button', { name: 'Live-Vorschau', exact: true }).click(); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); }); test('public library, shareable search, favorites, themes and responsive layouts', async ({ page, }, info) => { const errors: string[] = []; page.on('pageerror', (e) => errors.push(e.message)); await page.goto('/'); await expect(page.getByRole('heading', { name: 'Zuhause schmeckt’s am besten.' })).toBeVisible(); await expect(page.locator('.recipe-card')).toHaveCount(3); await expect(page.getByText('Geheimer Entwurf')).toHaveCount(0); await page.evaluate(() => document.fonts.ready); for (const card of await page.locator('.recipe-card').all()) await expect(card).toHaveCSS('opacity', '1'); await mkdir('docs/screenshots', { recursive: true }); await page.screenshot({ path: `docs/screenshots/library-${info.project.name}.png`, fullPage: true, animations: 'disabled', }); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); await page.getByRole('textbox', { name: 'Rezepte suchen' }).fill('Basilikum'); await expect(page.locator('.recipe-card')).toHaveCount(1); await expect(page).toHaveURL(/q=Basilikum/); await page.getByRole('button', { name: 'Suche löschen' }).click(); await page.getByRole('button', { name: 'Grüne Pesto-Pasta als Favorit merken' }).click(); const nav = page.viewportSize()!.width <= 700 ? page.getByRole('navigation', { name: 'Mobile Navigation' }) : page.getByRole('navigation', { name: 'Hauptnavigation' }); await nav.getByRole('link', { name: /Favoriten|Lieblingsrezepte/ }).click(); await expect(page.locator('.recipe-card')).toHaveCount(1); await page.getByRole('button', { name: 'Theme wechseln', exact: true }).click(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); await expect(page.locator('.recipe-card')).toHaveCSS('opacity', '1'); await page.screenshot({ path: `docs/screenshots/favorites-dark-${info.project.name}.png`, fullPage: true, animations: 'disabled', }); await page.reload(); await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark'); expect(errors).toEqual([]); }); test('portion calculator, ingredient anchor, shopping list and print content', async ({ page, }, info) => { await page.goto(`/rezepte/${id}`); await page.getByRole('spinbutton', { name: 'Portionen', exact: true }).fill('8'); await page.getByRole('spinbutton', { name: 'Portionen', exact: true }).press('Tab'); await expect(page.getByRole('button', { name: 'Menge Pasta ändern' })).toHaveText('800 g'); await expect(page.locator('.steps-list')).toContainText('800 g Pasta'); await page.getByRole('button', { name: 'Nach Zutat', exact: true }).click(); await page.getByRole('button', { name: 'Menge Pasta ändern' }).click(); await page.getByRole('textbox', { name: 'Neue Menge Pasta' }).fill('0,4'); await page.getByRole('combobox', { name: 'Einheit Pasta' }).selectOption('kg'); await page.getByRole('button', { name: 'Menge übernehmen' }).click(); await expect(page.getByRole('button', { name: 'Menge Pasta ändern' })).toHaveText('400 g'); await expect(page.locator('.panel-title')).toContainText('FÜR 4 PORTIONEN'); await expect(page.getByRole('button', { name: 'Menge Olivenöl ändern' })).toHaveText('120 ml'); await page.screenshot({ path: `docs/screenshots/recipe-${info.project.name}.png`, fullPage: true, }); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); await page.getByRole('button', { name: 'Zur Einkaufsliste hinzufügen' }).click(); await page.goto('/einkaufsliste'); await expect(page.locator('.shopping-list')).toContainText('400 g'); await page.locator('.shopping-item').first().click(); await page.reload(); await expect(page.locator('.shopping-list li.is-checked')).toHaveCount(1); await page.goto(`/rezepte/${id}`); await page.emulateMedia({ media: 'print' }); await expect(page.locator('.site-header')).toBeHidden(); await expect(page.locator('.ingredients-panel')).toBeVisible(); }); test('guided cooking restores scaled progress and timestamp timers through navigation', async ({ page, }, info) => { await page.goto(`/rezepte/${id}/kochen?faktor=2`); await expect(page.locator('.cooking-instruction')).toContainText('80 g Basilikum'); await page.locator('.step-ingredients button').first().click(); await page.getByRole('button', { name: 'Nächster Schritt' }).click(); await expect(page.getByRole('heading', { name: 'Das Pesto zum Leuchten bringen' })).toBeVisible(); await page.getByRole('button', { name: 'Pinienkerne rösten starten' }).click(); await expect(page.getByRole('button', { name: 'Pinienkerne rösten pausieren' })).toBeVisible(); await page.screenshot({ path: `docs/screenshots/cooking-${info.project.name}.png`, fullPage: true, }); await page.getByRole('button', { name: 'Nächster Schritt' }).click(); await expect(page.getByRole('button', { name: 'Pinienkerne rösten pausieren' })).toBeVisible(); await page.getByRole('button', { name: 'Zurück', exact: true }).click(); await page.reload(); await expect(page.getByRole('heading', { name: 'Das Pesto zum Leuchten bringen' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Pinienkerne rösten pausieren' })).toBeVisible(); await page.getByRole('button', { name: 'Pinienkerne rösten pausieren' }).click(); await page.getByRole('button', { name: 'Pinienkerne rösten zurücksetzen' }).click(); await expect(page.getByRole('timer')).toHaveText('03:00'); await page.getByRole('button', { name: 'Nächster Schritt' }).click(); await page.getByRole('button', { name: 'Nächster Schritt' }).click(); await page.getByRole('button', { name: 'Fertig gekocht' }).click(); await expect(page.getByRole('heading', { name: 'Guten Appetit!' })).toBeVisible(); await page.reload(); await expect(page.getByRole('heading', { name: 'Guten Appetit!' })).toBeVisible(); await page.getByRole('button', { name: 'Erneut kochen' }).click(); await expect(page.getByRole('heading', { name: 'Ein bisschen Vorbereitung' })).toBeVisible(); });