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