test(shopping): E2E-Spec + clearShoppingCart-Fixture
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 33s
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 33s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,3 +65,10 @@ export async function cleanupE2EComments(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leert den haushaltsweiten Einkaufswagen. Idempotent.
|
||||
*/
|
||||
export async function clearShoppingCart(api: APIRequestContext): Promise<void> {
|
||||
await api.delete('/api/shopping-list');
|
||||
}
|
||||
|
||||
109
tests/e2e/remote/shopping.spec.ts
Normal file
109
tests/e2e/remote/shopping.spec.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setActiveProfile, HENDRIK_ID } from './fixtures/profile';
|
||||
import { clearShoppingCart } from './fixtures/api-cleanup';
|
||||
|
||||
test.describe('Einkaufsliste E2E', () => {
|
||||
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;
|
||||
|
||||
const firstRow = page.locator('label.row').first();
|
||||
await firstRow.locator('input[type=checkbox]').check();
|
||||
await expect(firstRow).toHaveClass(/checked/);
|
||||
|
||||
// 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' }).click();
|
||||
|
||||
await expect(page.getByText('Einkaufswagen ist leer.')).toBeVisible();
|
||||
await expect(page.getByLabel(/Einkaufsliste \(\d+\)/)).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user