Files
kochwas/tests/e2e/remote/recipe-detail.spec.ts
hsiegeln a7ad159c69
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 1m42s
test(e2e): Playwright Smoketests gegen kochwas-dev (remote)
Automatisierte End-to-End-Tests gegen ein deployed Environment. Loest
die manuellen MCP-Playwright-Runs ab. 42 Tests in 9 Files:

- homepage: H1, Sektionen, Sort-Tabs, Console-Errors
- search: lokaler Treffer, Web-Fallback, Empty-State, Deep-Link
- profile: Switcher, Auswahl-Persistenz, Favoriten-Section, Guard-Dialog
- recipe-detail: Header, Portionen-Scaling (4->6), Favorit-Toggle,
  Rating-Persistenz ueber Reload, Gekocht-Counter, Wunschliste-Toggle
- comments: eigenen erstellen+loeschen via UI, fremder hat kein Delete
- wishlist: Seite, Sort-Tabs, Badge-Sync, requireProfile-Custom-Message
- preview: Guard ohne ?url=, echte URL parst, unparsbare zeigt error-box
- admin: alle 4 Subrouten + /admin redirect
- api-errors: parsePositiveIntParam (4x Invalid id), validateBody (4x
  Invalid body + issues), 404, Sanity /health /profiles /domains

Architektur:
- Separate playwright.remote.config.ts (getrennt von local preview)
- workers: 1 + afterEach API-Cleanup (rating, favorite, wishlist, comments)
- Hardcoded Recipe-ID 66 + Profile 1/2/3 — stabile Dev-DB-Seeds
- E2E_REMOTE_URL ueberschreibt die Ziel-URL

Ausfuehrung: npm run test:e2e:remote

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:14:04 +02:00

80 lines
3.2 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { setActiveProfile, HENDRIK_ID } from './fixtures/profile';
import {
clearFavorite,
clearRating,
removeFromWishlist
} from './fixtures/api-cleanup';
// Chicken Teriyaki auf kochwas-dev: 4 Portionen, 500 g Haehnchen, 100 ml Soja.
const RECIPE_ID = 66;
test.describe('Rezept-Detail', () => {
test.beforeEach(async ({ page }) => {
await setActiveProfile(page, HENDRIK_ID);
});
test.afterEach(async ({ request }) => {
await clearRating(request, RECIPE_ID, HENDRIK_ID);
await clearFavorite(request, RECIPE_ID, HENDRIK_ID);
await removeFromWishlist(request, RECIPE_ID, HENDRIK_ID);
});
test('Header + Zutaten sichtbar', async ({ page }) => {
await page.goto(`/recipes/${RECIPE_ID}`);
await expect(
page.getByRole('heading', { level: 1, name: /Chicken Teriyaki/i })
).toBeVisible();
await expect(page.getByText('Hähnchenbrustfilet').first()).toBeVisible();
});
test('Portionen-Scaler: 4 -> 6 skaliert Mengen proportional', async ({ page }) => {
await page.goto(`/recipes/${RECIPE_ID}`);
// Start: 4 Portionen, 500 g Haehnchen, 100 ml Soja.
await expect(page.locator('.srv-value strong').first()).toHaveText('4');
await page.getByRole('button', { name: 'Mehr' }).first().click();
await page.getByRole('button', { name: 'Mehr' }).first().click();
await expect(page.locator('.srv-value strong').first()).toHaveText('6');
// Skalierte Mengen 1.5x
await expect(page.getByText(/\b750 g/).first()).toBeVisible();
await expect(page.getByText(/\b150 ml/).first()).toBeVisible();
});
test('Favorit toggelt heart-Klasse sauber', async ({ page }) => {
await page.goto(`/recipes/${RECIPE_ID}`);
const favBtn = page.getByRole('button', { name: 'Favorit' });
await expect(favBtn).not.toHaveClass(/heart/);
await favBtn.click();
await expect(favBtn).toHaveClass(/heart/);
await favBtn.click();
await expect(favBtn).not.toHaveClass(/heart/);
});
test('Rating persistiert ueber Reload', async ({ page }) => {
await page.goto(`/recipes/${RECIPE_ID}`);
await page.getByRole('button', { name: '4 Sterne' }).click();
await expect(page.getByRole('button', { name: '4 Sterne' })).toHaveClass(/filled/);
await page.reload();
await expect(page.getByRole('button', { name: '4 Sterne' })).toHaveClass(/filled/);
});
test('Heute gekocht inkrementiert Counter', async ({ page }) => {
await page.goto(`/recipes/${RECIPE_ID}`);
const cookedBtn = page.getByRole('button', { name: /Heute gekocht/i });
const before = (await cookedBtn.innerText()).trim();
await cookedBtn.click();
// Der Button bekommt einen "(N)"-Suffix bzw. der existierende zaehler
// steigt. Wir pruefen nur, dass sich der Text aendert.
await expect(cookedBtn).not.toHaveText(before);
});
test('Auf Wunschliste-Toggle funktioniert', async ({ page }) => {
await page.goto(`/recipes/${RECIPE_ID}`);
const wishBtn = page.getByRole('button', { name: /Auf Wunschliste/i });
const initialLabel = (await wishBtn.getAttribute('aria-label')) ?? '';
await wishBtn.click();
// aria-label wechselt zwischen "setzen" und "Von der Wunschliste entfernen"
await expect(wishBtn).not.toHaveAttribute('aria-label', initialLabel);
});
});