diff --git a/docs/ui-testing-automation.md b/docs/ui-testing-automation.md new file mode 100644 index 0000000..0d4426f --- /dev/null +++ b/docs/ui-testing-automation.md @@ -0,0 +1,651 @@ +# Technisches Konzept: UI Test Automation – Effigenix ERP + +**Stand:** 2026-03-27 +**Branch:** `experiment/test-automation` + +--- + +## 1. Ausgangslage & Ziel + +### Ist-Zustand + +Das Projekt besteht aus drei Frontend-Targets: + +| App | Technologie | Status | +|---|---|---| +| `apps/cli` | Ink + React (TUI) | Primäre operative UI – vollständig ausgebaut | +| `apps/web` | React + Vite + Tailwind v4 | Frühstadium – aktuell nur Component-Showcase | +| `apps/scanner` | Tauri v2 (mobil) | Prototype | + +Die manuellen Testfälle (GitHub Issues mit Label `manual-testing`: #62–#70) decken alle implementierten Bounded Contexts ab: + +- `TC-CAT` – Produktkategorien +- `TC-SUP` – Lieferanten +- `TC-CUS` – Kunden +- `TC-B2B` – Rahmenverträge +- `TC-AUTH` – Autorisierung Masterdata +- `TC-ART` – Artikel *(offen)* +- `TC-CROSS` – Übergreifend Masterdata/Inventory/Production *(deferred)* + +Weiterhin enthalten alle Feature-Issues (US-Pxx, Story x.x) strukturierte **Akzeptanzkriterien**. + +### Ziel + +Automatisierte UI/E2E-Tests, die: +1. Aus den GitHub-Issues (Akzeptanzkriterien + manuelle Testfälle) abgeleitet werden +2. Repeatable und CI-fähig sind +3. Per Docker reproduzierbar laufen +4. Mit wachsender Web-UI mitwachsen können + +--- + +## 2. Framework-Entscheidung: Playwright + +### Bewertungsmatrix + +| Kriterium | Playwright | Robot Framework | +|---|---|---| +| TypeScript-native | ✅ Erstklassig | ⚠️ via robotframework-browser (Python wrapper) | +| Monorepo-Integration (pnpm) | ✅ Als pnpm Package | ⚠️ Eigenes Python-Ökosystem | +| React/Vite-Support | ✅ Exzellent | ⚠️ Indirekt via Browser-Automation | +| API-Testing (REST) | ✅ `request` Context eingebaut | ✅ Eigene HTTP-Library | +| Lernkurve im Team | ✅ Gering (TypeScript-Kenntnisse vorhanden) | ⚠️ Keyword-DSL + Python | +| Docker-Image Größe | ~600 MB | ~800 MB+ | +| Parallelisierung | ✅ Built-in | ⚠️ Konfigurationsaufwand | +| Community / Ökosystem | ✅ Stark (Microsoft-backed) | ✅ Stabil (älteres Ökosystem) | + +**Entscheidung: Playwright** + +Begründung: Das Team arbeitet bereits in TypeScript. Playwright lässt sich als Workspace-Package in den bestehenden pnpm-Monorepo integrieren. Die API-Testing-Fähigkeit erlaubt es, Backend-Akzeptanzkriterien ohne Browser zu testen – ideal für den aktuellen Stand, wo die Web-UI noch im Aufbau ist. + +--- + +## 3. Architektur + +### 3.1 Monorepo-Integration + +``` +frontend/ +└── apps/ + └── e2e/ # Neues Workspace-Package + ├── package.json + ├── playwright.config.ts + ├── tests/ + │ ├── api/ # API-E2E Tests (kein Browser nötig) + │ │ ├── masterdata/ + │ │ │ ├── categories.spec.ts # aus TC-CAT + │ │ │ ├── suppliers.spec.ts # aus TC-SUP + │ │ │ ├── customers.spec.ts # aus TC-CUS + │ │ │ └── articles.spec.ts # aus TC-ART + │ │ ├── inventory/ + │ │ │ ├── stock.spec.ts + │ │ │ ├── movements.spec.ts + │ │ │ └── reservations.spec.ts + │ │ ├── production/ + │ │ │ ├── recipes.spec.ts + │ │ │ ├── batches.spec.ts + │ │ │ └── orders.spec.ts + │ │ └── auth/ + │ │ └── authorization.spec.ts # aus TC-AUTH + │ └── web/ # Browser-UI Tests (Web App) + │ └── .gitkeep # Platzhalter – wächst mit der Web-App + ├── fixtures/ + │ ├── auth.fixture.ts # Login-Helper (JWT) + │ └── seed.fixture.ts # Testdaten-Setup + └── helpers/ + └── api-client.ts # Typisierter API-Wrapper +``` + +### 3.2 Test-Ebenen + +``` +┌─────────────────────────────────────────────────────┐ +│ Phase 2: Web UI Tests (Browser) │ +│ Playwright + React – sobald Web-App ausgebaut │ +├─────────────────────────────────────────────────────┤ +│ Phase 1 (jetzt): API E2E Tests │ +│ Playwright request context → Spring Boot REST API │ +│ Direkte Ableitung aus Akzeptanzkriterien in Issues │ +├─────────────────────────────────────────────────────┤ +│ Bestehend: Unit + Integration Tests (Maven/vitest) │ +└─────────────────────────────────────────────────────┘ +``` + +**Phase 1** startet mit API-Level-Tests, da die Web-UI noch nicht ausgebaut ist. Diese Tests validieren denselben Scope wie die manuellen Testfälle, ohne einen Browser zu benötigen. + +--- + +## 4. Ticket → Test Mapping + +### 4.1 Struktur der Issues + +Die GitHub-Issues enthalten zwei Test-relevante Formate: + +**Format A – Feature Stories (US-Pxx / Story x.x):** +```markdown +## Akzeptanzkriterien +- [ ] POST `/api/production-orders` → 201, Status PLANNED +- [ ] PlannedDate gestern → 400 (PlannedDateInPast) +- [ ] PlannedQuantity 0 → 400 +``` +→ Direkt in API-Tests übersetzbar (HTTP-Verb, Endpoint, Expected Status Code) + +**Format B – Manuelle Testfälle (TC-xxx):** +```markdown +### TC-SUP-01: Lieferant erstellen – Pflichtfelder +1. Name: `Frisch AG`, Telefon: `+49 30 12345` → Enter +- [x] Erwartung: Lieferant erscheint in Liste, Status AKTIV +``` +→ Schritte + Erwartungen → Playwright `test()` Blöcke + +### 4.2 Mapping-Tabelle: Issues → Spec-Dateien + +| GitHub Issue | Label | Spec-Datei | Priorität | +|---|---|---|---| +| #62 TC-CAT | manual-testing | `api/masterdata/categories.spec.ts` | Hoch | +| #63 TC-SUP | manual-testing | `api/masterdata/suppliers.spec.ts` | Hoch | +| #65 TC-CUS | manual-testing | `api/masterdata/customers.spec.ts` | Hoch | +| #66 TC-B2B | manual-testing | `api/masterdata/contracts.spec.ts` | Mittel | +| #67 TC-AUTH | manual-testing | `api/auth/authorization.spec.ts` | Hoch | +| #64 TC-ART | manual-testing (offen) | `api/masterdata/articles.spec.ts` | Hoch | +| #38–#42 US-P13–P17 | epic:production-order | `api/production/orders.spec.ts` | Mittel | +| #33–#36 US-P09–P12 | epic:batch | `api/production/batches.spec.ts` | Mittel | +| #26–#32 US-P02–P08 | epic:recipe | `api/production/recipes.spec.ts` | Mittel | +| #43–#44 US-P18–P19 | epic:traceability | `api/production/traceability.spec.ts` | Mittel | +| #4–#20 Story 2.x–6.x | epic:stock/inventory | `api/inventory/*.spec.ts` | Mittel | + +### 4.3 Beispiel-Übersetzung + +**Issue #62, TC-CAT-01 → Playwright:** + +```typescript +// tests/api/masterdata/categories.spec.ts +import { test, expect } from '@playwright/test'; + +test.describe('TC-CAT: Produktkategorien', () => { + test('TC-CAT-01: Kategorie erstellen – Happy Path', async ({ request }) => { + const res = await request.post('/api/product-categories', { + data: { name: 'Obst & Gemüse', description: 'Frische Produkte' }, + }); + expect(res.status()).toBe(201); + const body = await res.json(); + expect(body.name).toBe('Obst & Gemüse'); + expect(body.description).toBe('Frische Produkte'); + }); + + test('TC-CAT-02: Kategorie erstellen – ohne Beschreibung', async ({ request }) => { + const res = await request.post('/api/product-categories', { + data: { name: 'Milchprodukte' }, + }); + expect(res.status()).toBe(201); + }); + + test('TC-CAT-04: Doppelter Name wird abgelehnt', async ({ request }) => { + await request.post('/api/product-categories', { data: { name: 'Duplikat-Test' } }); + const res = await request.post('/api/product-categories', { data: { name: 'Duplikat-Test' } }); + expect(res.status()).toBe(409); // oder 422 je nach Backend-Impl. + }); + + test('TC-CAT-06: Leerer Name wird abgelehnt', async ({ request }) => { + const res = await request.post('/api/product-categories', { data: { name: '' } }); + expect(res.status()).toBe(400); + }); +}); +``` + +--- + +## 5. Docker-Setup + +### 5.1 Image-Strategie + +Zwei Images: + +``` +docker-compose.e2e.yml +├── db – postgres:15-alpine (gleiche Config wie dev) +├── backend – effigenix-backend:test (mit test-Profile + Seed-Daten) +└── e2e-runner – effigenix-e2e:latest (Playwright + Tests) +``` + +### 5.2 Playwright Dockerfile + +```dockerfile +# e2e/Dockerfile +FROM mcr.microsoft.com/playwright:v1.51.0-noble + +WORKDIR /app + +# pnpm installieren +RUN npm install -g pnpm@9 + +# Workspace-Dependencies kopieren +COPY frontend/package.json frontend/pnpm-workspace.yaml ./ +COPY frontend/apps/e2e/package.json ./apps/e2e/ +# Andere packages die e2e braucht +COPY frontend/packages/types/package.json ./packages/types/ + +# Install +RUN pnpm install --frozen-lockfile + +# Test-Code kopieren +COPY frontend/apps/e2e/ ./apps/e2e/ +COPY frontend/packages/types/ ./packages/types/ + +WORKDIR /app/apps/e2e + +ENTRYPOINT ["pnpm", "exec", "playwright", "test"] +``` + +### 5.3 docker-compose.e2e.yml + +```yaml +version: '3.8' + +services: + db: + image: postgres:15-alpine + environment: + POSTGRES_DB: effigenix + POSTGRES_USER: effigenix + POSTGRES_PASSWORD: effigenix + healthcheck: + test: ["CMD-SHELL", "pg_isready -U effigenix"] + interval: 5s + timeout: 3s + retries: 10 + + backend: + build: + context: ./backend + dockerfile: Dockerfile + environment: + SPRING_PROFILES_ACTIVE: test + SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/effigenix + SPRING_DATASOURCE_USERNAME: effigenix + SPRING_DATASOURCE_PASSWORD: effigenix + depends_on: + db: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"] + interval: 10s + timeout: 5s + retries: 12 + ports: + - "8080:8080" + + e2e-runner: + build: + context: . + dockerfile: e2e/Dockerfile + environment: + BASE_URL: http://backend:8080 + TEST_USER_ADMIN: admin + TEST_USER_ADMIN_PASS: admin123 + TEST_USER_VIEWER: viewer + TEST_USER_VIEWER_PASS: viewer123 + depends_on: + backend: + condition: service_healthy + volumes: + - ./e2e-results:/app/apps/e2e/playwright-report + - ./e2e-results:/app/apps/e2e/test-results +``` + +### 5.4 Ausführung + +```bash +# Alle E2E-Tests lokal starten +docker compose -f docker-compose.e2e.yml up --abort-on-container-exit + +# Nur bestimmte Tests +docker compose -f docker-compose.e2e.yml run e2e-runner --grep "TC-SUP" + +# Report anzeigen (nach Test-Run) +pnpm exec playwright show-report e2e-results +``` + +--- + +## 6. Playwright-Konfiguration + +```typescript +// frontend/apps/e2e/playwright.config.ts +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 4 : undefined, + reporter: [ + ['html', { outputFolder: 'playwright-report' }], + ['junit', { outputFile: 'test-results/junit.xml' }], // für CI + ], + use: { + baseURL: process.env.BASE_URL ?? 'http://localhost:8080', + extraHTTPHeaders: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }, + projects: [ + { + name: 'api', + testMatch: 'tests/api/**/*.spec.ts', + // Kein Browser benötigt für API-Tests + }, + { + name: 'web-chromium', + testMatch: 'tests/web/**/*.spec.ts', + use: { browserName: 'chromium' }, + }, + ], +}); +``` + +--- + +## 7. Auth-Fixtures + +```typescript +// frontend/apps/e2e/fixtures/auth.fixture.ts +import { test as base, expect } from '@playwright/test'; + +type AuthFixtures = { + adminToken: string; + viewerToken: string; +}; + +export const test = base.extend({ + adminToken: async ({ request }, use) => { + const res = await request.post('/api/auth/login', { + data: { + username: process.env.TEST_USER_ADMIN ?? 'admin', + password: process.env.TEST_USER_ADMIN_PASS ?? 'admin123', + }, + }); + expect(res.status()).toBe(200); + const { token } = await res.json(); + await use(token); + }, + viewerToken: async ({ request }, use) => { + const res = await request.post('/api/auth/login', { + data: { + username: process.env.TEST_USER_VIEWER ?? 'viewer', + password: process.env.TEST_USER_VIEWER_PASS ?? 'viewer123', + }, + }); + expect(res.status()).toBe(200); + const { token } = await res.json(); + await use(token); + }, +}); + +export { expect }; +``` + +--- + +## 8. Test-Generierung aus Issues + +### 8.1 Workflow + +``` +GitHub Issue (body mit AC/TC) + │ + ▼ + gh issue view --json title,body + │ + ▼ + Claude API (claude-sonnet-4-6) + Prompt: "Übersetze diese Akzeptanzkriterien + in Playwright TypeScript Tests..." + │ + ▼ + Generierter Test-Stub → Review → Commit +``` + +### 8.2 Generierungs-Skript (Konzept) + +```typescript +// scripts/generate-tests-from-issue.ts +import { execSync } from 'child_process'; +import Anthropic from '@anthropic-ai/sdk'; +import fs from 'fs'; + +const issueNumber = process.argv[2]; +if (!issueNumber) throw new Error('Usage: tsx generate-tests-from-issue.ts '); + +// Issue-Body via gh CLI laden +const issue = JSON.parse( + execSync(`gh issue view ${issueNumber} --json title,body`).toString() +); + +const client = new Anthropic(); + +const prompt = ` +Du bist ein Senior QA Engineer. Übersetze die folgenden GitHub Issue-Inhalte +in Playwright TypeScript API-Tests (Playwright request context, kein Browser). + +Regeln: +- Verwende test.describe() mit dem Issue-Titel +- Jeden Testfall (TC-xxx oder Akzeptanzkriterium) als eigenständigen test() +- Auth via Bearer Token (Fixture: adminToken oder viewerToken) +- baseURL kommt aus Playwright-Config (nicht hardcoden) +- Nur den Test-Code ausgeben, kein Markdown-Wrapper + +Issue #${issueNumber}: ${issue.title} + +${issue.body} +`; + +const response = await client.messages.create({ + model: 'claude-sonnet-4-6', + max_tokens: 4096, + messages: [{ role: 'user', content: prompt }], +}); + +const code = response.content[0].type === 'text' ? response.content[0].text : ''; +const filename = `tests/api/generated/issue-${issueNumber}.spec.ts`; + +fs.mkdirSync('tests/api/generated', { recursive: true }); +fs.writeFileSync(filename, code); + +console.log(`✓ Test generiert: ${filename}`); +console.log(' → Bitte Review und manuelle Anpassung vor Commit!'); +``` + +### 8.3 Nutzung + +```bash +# Test für ein Issue generieren +cd frontend/apps/e2e +tsx ../../scripts/generate-tests-from-issue.ts 63 + +# Output: tests/api/generated/issue-63.spec.ts +# → Review → in tests/api/masterdata/suppliers.spec.ts einpflegen +``` + +### 8.4 Kandidaten für sofortige Generierung + +Folgende Issues enthalten vollständige, abgehakte Testfälle (alle `[x]`) und sind direkt verwertbar: + +| Issue | Testfälle | Status | +|---|---|---| +| #62 TC-CAT | 6 TCs | ✅ Alle abgenommen | +| #63 TC-SUP | 12 TCs | ✅ Alle abgenommen | +| #65 TC-CUS | Kunden-CRUD | ✅ | +| #66 TC-B2B | Rahmenverträge | ✅ | +| #38 US-P13 | 3 ACs | ✅ | +| #39–#42 US-P14–P17 | je 2–4 ACs | ✅ | + +--- + +## 9. TUI-Testing (Terminal UI) + +### 9.1 Machbarkeit + +**Ja, TUI-Testing ist möglich und bereits vorbereitet.** + +`ink-testing-library` ist bereits im `@effigenix/cli`-Package als `devDependency` installiert und wird in 4 bestehenden Tests genutzt (z.B. `ConfirmDialog.test.tsx`). + +Das Pattern ist: +```typescript +import { render } from 'ink-testing-library'; +const { lastFrame, stdin } = render(); +``` + +`lastFrame()` gibt die aktuelle Terminal-Ausgabe als String zurück. `stdin` ermöglicht simulierte Tastatureingaben. + +### 9.2 Was testbar ist + +| Art | Testbar | Methode | +|---|---|---| +| Render-Output (Text, Farben) | ✅ | `lastFrame()` | +| Tastaturnavigation (`↑↓`, Enter, `n`, `e`) | ✅ | `stdin.write(...)` | +| Zustandsübergänge (Formular → Liste) | ✅ | Async `lastFrame()` nach Input | +| API-Calls (echte HTTP) | ⚠️ | Erfordert Mock via `vi.mock()` | +| Vollständiger E2E-Flow mit echtem Backend | ❌ | Nicht sinnvoll (Domäne von Playwright-API-Tests) | + +### 9.3 Teststruktur für TUI + +``` +frontend/apps/cli/src/__tests__/ +├── shared/ # Bestehend +│ ├── ConfirmDialog.test.tsx +│ ├── ErrorDisplay.test.tsx +│ └── SuccessDisplay.test.tsx +├── hooks/ # Bestehend +│ └── useRoles.test.ts +└── screens/ # Neu – aus TC-xxx Issues ableiten + ├── masterdata/ + │ ├── SupplierList.test.tsx # aus TC-SUP-06 (Filter) + │ ├── SupplierForm.test.tsx # aus TC-SUP-01/02/03 (Formular) + │ └── CategoryForm.test.tsx # aus TC-CAT-01/06 (Validierung) + └── production/ + └── RecipeForm.test.tsx +``` + +### 9.4 Beispiel: TC-SUP-03 als TUI-Test + +```typescript +// __tests__/screens/masterdata/SupplierForm.test.tsx +import { render } from 'ink-testing-library'; +import { vi, describe, it, expect } from 'vitest'; +import React from 'react'; +import { SupplierCreateForm } from '../../../components/masterdata/SupplierCreateForm.js'; + +vi.mock('@effigenix/api-client', () => ({ + suppliersApi: { + create: vi.fn().mockResolvedValue({ status: 201, data: { id: '1', name: 'Frisch AG' } }), + }, +})); + +describe('TC-SUP: Lieferanten-Formular', () => { + it('TC-SUP-03: Leerer Name → kein API-Call', async () => { + const onSuccess = vi.fn(); + const { stdin, lastFrame } = render( + React.createElement(SupplierCreateForm, { onSuccess }) + ); + + // Enter ohne Name + stdin.write('\r'); + await new Promise(r => setTimeout(r, 50)); + + expect(lastFrame()).toContain('Pflichtfeld'); + expect(onSuccess).not.toHaveBeenCalled(); + }); + + it('TC-SUP-01: Pflichtfelder ausgefüllt → Lieferant wird angelegt', async () => { + const onSuccess = vi.fn(); + const { stdin, lastFrame } = render( + React.createElement(SupplierCreateForm, { onSuccess }) + ); + + // Name eingeben + stdin.write('Frisch AG\r'); + // Telefon eingeben + stdin.write('+49 30 12345\r'); + // Speichern + stdin.write('\r'); + await new Promise(r => setTimeout(r, 100)); + + expect(onSuccess).toHaveBeenCalled(); + }); +}); +``` + +### 9.5 Abgrenzung TUI-Tests vs. API-Tests + +``` +TUI-Tests (vitest + ink-testing-library) + → "Rendert der Screen korrekt?" + → "Werden Validierungsfehler angezeigt?" + → "Reagiert Tastatur-Navigation richtig?" + → API-Calls werden gemockt + +API E2E-Tests (Playwright) + → "Funktioniert das Backend korrekt?" + → "Werden alle Akzeptanzkriterien aus dem Ticket erfüllt?" + → Kein UI-Rendering +``` + +Beide Ebenen ergänzen sich: TUI-Tests sichern das Verhalten der UI-Komponenten, API-Tests sichern den Backend-Vertrag. + +--- + +## 10. Umsetzungsplan (Phasen) + +### Phase 0 – TUI-Tests ausbauen (sofort, ohne neues Setup) + +- [ ] `__tests__/screens/` Verzeichnis anlegen +- [ ] Formular-Tests für `SupplierCreateForm` (TC-SUP-01/02/03) +- [ ] Formular-Tests für `CategoryForm` (TC-CAT-01/04/06) +- [ ] Filter-Tests für Listen-Screens (TC-SUP-06) +- [ ] Bestehende 4 Tests als Vorlage nutzen + +### Phase 1 – API E2E Grundgerüst + +- [ ] `frontend/apps/e2e` Workspace-Package anlegen +- [ ] `playwright.config.ts` einrichten (API-Projekt) +- [ ] Auth-Fixture implementieren +- [ ] `docker-compose.e2e.yml` erstellen +- [ ] Backend `Dockerfile` (test-Profile, Seed-Daten) +- [ ] Erste Spec: `api/masterdata/categories.spec.ts` (TC-CAT, Issue #62) +- [ ] Erste Spec: `api/masterdata/suppliers.spec.ts` (TC-SUP, Issue #63) +- [ ] `just test-e2e` Recipe in `justfile` ergänzen + +### Phase 2 – Vollständige API-Coverage + +- [ ] Alle `manual-testing`-Issues in Specs übersetzen (#62–#67) +- [ ] Production BC Specs aus US-P13–P19 (#38–#44) +- [ ] Inventory BC Specs aus Story 2.x–6.x (#4–#20) +- [ ] Auth/Authorization Spec (TC-AUTH, #67) +- [ ] Generierungs-Skript finalisieren und in `justfile` integrieren + +### Phase 3 – CI/CD Integration + +- [ ] `.github/workflows/e2e.yml` erstellen +- [ ] Trigger: auf `main`-Push und PRs gegen `main` +- [ ] JUnit-Report als CI-Artefakt +- [ ] Playwright HTML-Report als GitHub Pages (optional) + +### Phase 4 – Web UI Tests (wenn App ausgebaut) + +- [ ] Browser-Projekt in `playwright.config.ts` aktivieren +- [ ] Page Object Models für die Web-App-Seiten +- [ ] Visuelle Regression (optional: Playwright screenshots) + +--- + +## 10. Offene Punkte & Entscheidungen + +| Punkt | Status | Anmerkung | +|---|---|---| +| Backend `Dockerfile` | Fehlt noch | Nötig für E2E-Docker-Setup | +| Seed-Testdaten Isolation | Offen | Aktuell globale Seed-DB; für E2E per Test isolieren oder DB-Reset vor Suite | +| `read:project` Scope für `gh` | Token fehlt Scope | Für Projektstatus-Abfrage (welche Issues in "Test") nötig; `gh auth refresh -s read:project` | +| TUI-Testing | Out of scope | Ink-TUI-Testing via `ink-testing-library` ist möglich, aber separates Konzept | +| Scanner (Tauri/mobil) | Out of scope | Appium oder Tauri-eigene Test-Tools; separates Konzept |