81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { resolve } from 'node:path';
|
|
|
|
// Wir stubben den Extract-Endpoint server-side nicht (das Feature liegt auf
|
|
// dev hinter dem echten Gemini-Key), sondern auf context-Ebene: Playwright
|
|
// fängt alle Requests auf /api/recipes/extract-from-photo ab und liefert
|
|
// einen deterministischen JSON-Body zurück. Keine Gemini-Kosten in CI.
|
|
|
|
test('Foto-Import Happy-Path mit gestubtem Extract-Endpoint', async ({
|
|
page,
|
|
context
|
|
}) => {
|
|
await context.route('**/api/recipes/extract-from-photo', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
recipe: {
|
|
id: null,
|
|
title: 'E2E Testrezept',
|
|
description: 'Aus dem Bild herbeigezaubert.',
|
|
source_url: null,
|
|
source_domain: null,
|
|
image_path: null,
|
|
servings_default: 2,
|
|
servings_unit: 'Portionen',
|
|
prep_time_min: 5,
|
|
cook_time_min: 10,
|
|
total_time_min: null,
|
|
cuisine: null,
|
|
category: null,
|
|
ingredients: [
|
|
{
|
|
position: 1,
|
|
quantity: 1,
|
|
unit: 'Stk',
|
|
name: 'E2E-Apfel',
|
|
note: null,
|
|
raw_text: '1 Stk E2E-Apfel',
|
|
section_heading: null
|
|
}
|
|
],
|
|
steps: [{ position: 1, text: 'Apfel waschen.' }],
|
|
tags: []
|
|
}
|
|
})
|
|
});
|
|
});
|
|
|
|
await page.goto('/new/from-photo');
|
|
await expect(page.getByRole('heading', { name: 'Rezept aus Foto' })).toBeVisible();
|
|
|
|
const fixture = resolve(__dirname, '../../fixtures/photo-recipe/sample-printed.jpg');
|
|
await page.locator('input[type="file"]').setInputFiles(fixture);
|
|
|
|
await expect(page.getByText('Aus Foto erstellt')).toBeVisible({ timeout: 5000 });
|
|
// Titel-Feld (das erste text-input im Editor)
|
|
await expect(page.locator('input[type="text"]').first()).toHaveValue(
|
|
'E2E Testrezept'
|
|
);
|
|
});
|
|
|
|
test('Camera-Icon im Header wird disabled, wenn der Client offline geht', async ({
|
|
page,
|
|
context
|
|
}) => {
|
|
await page.goto('/');
|
|
const icon = page.locator('[aria-label="Rezept aus Foto erstellen"]');
|
|
// Nur relevant, wenn der Dev-Server einen Gemini-Key hat — andernfalls ist
|
|
// das Icon per Graceful-Degradation gar nicht gerendert und der Test wird
|
|
// hier early-skipped. (Im Prod und Dev mit Key gilt der zweite Pfad.)
|
|
if ((await icon.count()) === 0) {
|
|
test.skip(true, 'Dev-Env hat keinen GEMINI_API_KEY gesetzt.');
|
|
return;
|
|
}
|
|
await expect(icon).toBeVisible();
|
|
await context.setOffline(true);
|
|
await page.waitForFunction(() => !navigator.onLine);
|
|
await expect(icon).toHaveClass(/disabled/);
|
|
});
|