mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 10:19:35 +01:00
init e2e ui tests base skeleton
This commit is contained in:
parent
83c7321c8f
commit
c84629cc4e
16 changed files with 1219 additions and 0 deletions
55
test-automation/README.md
Normal file
55
test-automation/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# test-automation
|
||||||
|
|
||||||
|
Automatisierte UI/E2E-Tests für Effigenix ERP.
|
||||||
|
|
||||||
|
> **WICHTIG – Branch-Schutz:**
|
||||||
|
> Alle Arbeiten an diesem Ordner finden ausschliesslich im Branch `experiment/test-automation` statt.
|
||||||
|
> **Ein Merge in `main` darf NUR mit ausdrücklicher menschlicher Bestätigung erfolgen.**
|
||||||
|
> Kein automatischer Merge, kein Auto-Squash, kein Force-Push auf `main`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Struktur
|
||||||
|
|
||||||
|
```
|
||||||
|
test-automation/
|
||||||
|
├── README.md # Diese Datei
|
||||||
|
├── TASKS.md # Phasen & Aufgaben (Tracking)
|
||||||
|
├── docs/
|
||||||
|
│ └── ui-testing-automation.md # Technisches Konzept
|
||||||
|
├── web-ui/ # Playwright E2E-Tests (Web + API)
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── playwright.config.ts
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ ├── tests/
|
||||||
|
│ │ ├── api/ # API-Level-Tests (kein Browser)
|
||||||
|
│ │ └── web/ # Browser-UI-Tests (wächst mit Web-App)
|
||||||
|
│ ├── fixtures/ # Auth- und Seed-Fixtures
|
||||||
|
│ └── helpers/ # Typisierter API-Wrapper
|
||||||
|
├── tui/ # TUI-Tests (vitest + ink-testing-library)
|
||||||
|
│ └── README.md
|
||||||
|
└── docker-compose.e2e.yml # Vollständiges E2E-Stack (DB + Backend + Runner)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Alle E2E-Tests per Docker starten
|
||||||
|
docker compose -f test-automation/docker-compose.e2e.yml up --abort-on-container-exit
|
||||||
|
|
||||||
|
# Nur bestimmte Tests
|
||||||
|
docker compose -f test-automation/docker-compose.e2e.yml run e2e-runner --grep "TC-SUP"
|
||||||
|
|
||||||
|
# HTML-Report anzeigen (nach Test-Run)
|
||||||
|
pnpm --filter @effigenix/e2e exec playwright show-report e2e-results
|
||||||
|
```
|
||||||
|
|
||||||
|
## Framework-Übersicht
|
||||||
|
|
||||||
|
| Bereich | Tool | Zweck |
|
||||||
|
|---|---|---|
|
||||||
|
| API E2E | Playwright (request context) | Backend-Akzeptanzkriterien validieren |
|
||||||
|
| Web UI | Playwright (Chromium) | Browser-Tests sobald Web-App ausgebaut |
|
||||||
|
| TUI | vitest + ink-testing-library | Terminal-UI Komponenten-Tests |
|
||||||
|
|
||||||
|
Detailliertes technisches Konzept: [`docs/ui-testing-automation.md`](./docs/ui-testing-automation.md)
|
||||||
96
test-automation/TASKS.md
Normal file
96
test-automation/TASKS.md
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Test Automation – Aufgaben & Phasen
|
||||||
|
|
||||||
|
Branch: `experiment/test-automation`
|
||||||
|
Konzept: [`docs/ui-testing-automation.md`](./docs/ui-testing-automation.md)
|
||||||
|
|
||||||
|
> **Merge-Regel:** Kein Merge in `main` ohne ausdrückliche menschliche Bestätigung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0 – TUI-Tests ausbauen
|
||||||
|
|
||||||
|
> Sofort umsetzbar, kein neues Setup nötig. Nutzt `ink-testing-library` + `vitest`.
|
||||||
|
> Ort: `frontend/apps/cli/src/__tests__/screens/`
|
||||||
|
|
||||||
|
- [ ] `__tests__/screens/` Verzeichnisstruktur anlegen (`masterdata/`, `production/`)
|
||||||
|
- [ ] `SupplierForm.test.tsx` – TC-SUP-01 (Pflichtfelder), TC-SUP-02, TC-SUP-03 (leerer Name)
|
||||||
|
- [ ] `CategoryForm.test.tsx` – TC-CAT-01 (Happy Path), TC-CAT-04 (Duplikat), TC-CAT-06 (leerer Name)
|
||||||
|
- [ ] `SupplierList.test.tsx` – TC-SUP-06 (Filter/Suche)
|
||||||
|
- [ ] Bestehende 4 Tests (`ConfirmDialog`, `ErrorDisplay`, `SuccessDisplay`, `useRoles`) als Vorlage prüfen
|
||||||
|
- [ ] Alle neuen TUI-Tests laufen durch (`pnpm --filter @effigenix/cli test`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1 – API E2E Grundgerüst
|
||||||
|
|
||||||
|
> Playwright-Setup in `test-automation/web-ui/`. API-Tests ohne Browser.
|
||||||
|
|
||||||
|
- [ ] `web-ui/package.json` finalisieren (Abhängigkeiten prüfen, ggf. pnpm workspace eintragen)
|
||||||
|
- [ ] `web-ui/playwright.config.ts` validieren und anpassen
|
||||||
|
- [ ] `web-ui/fixtures/auth.fixture.ts` implementieren und testen
|
||||||
|
- [ ] `web-ui/fixtures/seed.fixture.ts` implementieren (DB-Reset-Strategie klären)
|
||||||
|
- [ ] `web-ui/helpers/api-client.ts` typisiert implementieren
|
||||||
|
- [ ] Backend `Dockerfile` erstellen (test-Profil, mit Seed-Daten)
|
||||||
|
- [ ] `docker-compose.e2e.yml` validieren (DB → Backend → e2e-runner Healthchecks)
|
||||||
|
- [ ] `web-ui/Dockerfile` validieren
|
||||||
|
- [ ] Erste Spec: `tests/api/masterdata/categories.spec.ts` (TC-CAT, Issue #62)
|
||||||
|
- [ ] Erste Spec: `tests/api/masterdata/suppliers.spec.ts` (TC-SUP, Issue #63)
|
||||||
|
- [ ] End-to-end-Run lokal erfolgreich: `docker compose -f test-automation/docker-compose.e2e.yml up`
|
||||||
|
- [ ] `just test-e2e` Recipe im `justfile` ergänzen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2 – Vollständige API-Coverage
|
||||||
|
|
||||||
|
> Alle manual-testing Issues und Feature-Stories als Playwright-Specs.
|
||||||
|
|
||||||
|
- [ ] `tests/api/masterdata/customers.spec.ts` – TC-CUS, Issue #65
|
||||||
|
- [ ] `tests/api/masterdata/contracts.spec.ts` – TC-B2B, Issue #66
|
||||||
|
- [ ] `tests/api/masterdata/articles.spec.ts` – TC-ART, Issue #64
|
||||||
|
- [ ] `tests/api/auth/authorization.spec.ts` – TC-AUTH, Issue #67
|
||||||
|
- [ ] `tests/api/production/orders.spec.ts` – US-P13–P17, Issues #38–#42
|
||||||
|
- [ ] `tests/api/production/batches.spec.ts` – US-P09–P12, Issues #33–#36
|
||||||
|
- [ ] `tests/api/production/recipes.spec.ts` – US-P02–P08, Issues #26–#32
|
||||||
|
- [ ] `tests/api/production/traceability.spec.ts` – US-P18–P19, Issues #43–#44
|
||||||
|
- [ ] `tests/api/inventory/stock.spec.ts` – Story 2.x–6.x, Issues #4–#20
|
||||||
|
- [ ] `tests/api/inventory/movements.spec.ts`
|
||||||
|
- [ ] `tests/api/inventory/reservations.spec.ts`
|
||||||
|
- [ ] Test-Generierungs-Skript (`scripts/generate-tests-from-issue.ts`) finalisieren
|
||||||
|
- [ ] Skript in `justfile` als `just generate-test <issue-nr>` integrieren
|
||||||
|
- [ ] Alle Specs im Docker-Stack grün
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 – CI/CD Integration
|
||||||
|
|
||||||
|
> GitHub Actions Workflow für automatische Test-Ausführung.
|
||||||
|
|
||||||
|
- [ ] `.github/workflows/e2e.yml` erstellen
|
||||||
|
- [ ] Trigger: Push auf `main`, PRs gegen `main`
|
||||||
|
- [ ] JUnit-Report als CI-Artefakt hochladen
|
||||||
|
- [ ] Playwright HTML-Report als GitHub Pages veröffentlichen (optional)
|
||||||
|
- [ ] Badge in README einbinden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 – Web UI Tests (Browser)
|
||||||
|
|
||||||
|
> Erst sinnvoll, wenn `apps/web` ausgebaut ist. Platzhalter bereits vorhanden.
|
||||||
|
|
||||||
|
- [ ] Browser-Projekt in `playwright.config.ts` aktivieren (`web-chromium`)
|
||||||
|
- [ ] Page Object Models für Web-App-Seiten anlegen
|
||||||
|
- [ ] Login-Flow als Browser-Test
|
||||||
|
- [ ] Erste Screen-Tests (analog zu TUI-Tests in Phase 0)
|
||||||
|
- [ ] Visuelle Regression via Playwright Screenshots (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Offene Punkte & Entscheidungen
|
||||||
|
|
||||||
|
| Punkt | Status | Massnahme |
|
||||||
|
|---|---|---|
|
||||||
|
| Backend `Dockerfile` | Fehlt | In Phase 1 erstellen |
|
||||||
|
| Seed-Testdaten Isolation | Offen | Strategie in Phase 1 klären (DB-Reset vor Suite oder pro Test) |
|
||||||
|
| `gh` Token `read:project` Scope | Fehlt | `gh auth refresh -s read:project` ausführen wenn nötig |
|
||||||
|
| TUI-Tests Abgrenzung | Klar | Vitest + ink-testing-library, gemockte API-Calls |
|
||||||
|
| Scanner (Tauri/mobil) | Out of scope | Separates Konzept, ggf. `test-automation/scanner/` später |
|
||||||
54
test-automation/docker-compose.e2e.yml
Normal file
54
test-automation/docker-compose.e2e.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
# WICHTIG: Nur im Branch experiment/test-automation verwenden.
|
||||||
|
# Merge in main nur mit ausdrücklicher menschlicher Bestätigung.
|
||||||
|
|
||||||
|
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: test-automation/web-ui/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/test-automation/web-ui/playwright-report
|
||||||
|
- ./e2e-results:/app/test-automation/web-ui/test-results
|
||||||
651
test-automation/docs/ui-testing-automation.md
Normal file
651
test-automation/docs/ui-testing-automation.md
Normal file
|
|
@ -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<AuthFixtures>({
|
||||||
|
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 <nr> --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-number>');
|
||||||
|
|
||||||
|
// 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(<MeineKomponente {...props} />);
|
||||||
|
```
|
||||||
|
|
||||||
|
`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 |
|
||||||
51
test-automation/tui/README.md
Normal file
51
test-automation/tui/README.md
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# TUI-Tests – Terminal UI
|
||||||
|
|
||||||
|
TUI-Tests für `apps/cli` (Ink + React) laufen **nicht** in diesem Ordner, sondern direkt im CLI-Paket:
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/apps/cli/src/__tests__/
|
||||||
|
├── shared/ # Bestehend: ConfirmDialog, ErrorDisplay, SuccessDisplay
|
||||||
|
├── hooks/ # Bestehend: useRoles
|
||||||
|
└── screens/ # Phase 0 – wird hier ausgebaut
|
||||||
|
├── masterdata/
|
||||||
|
│ ├── SupplierForm.test.tsx
|
||||||
|
│ ├── SupplierList.test.tsx
|
||||||
|
│ └── CategoryForm.test.tsx
|
||||||
|
└── production/
|
||||||
|
└── RecipeForm.test.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Warum nicht hier?
|
||||||
|
|
||||||
|
TUI-Tests nutzen `vitest` + `ink-testing-library` und sind eng am Quellcode der CLI-App.
|
||||||
|
Sie gehören zum CLI-Paket (`@effigenix/cli`) und laufen mit dem bestehenden Vitest-Setup.
|
||||||
|
Ein separates Package wäre unnötiger Overhead.
|
||||||
|
|
||||||
|
## Tests ausführen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Alle TUI-Tests
|
||||||
|
pnpm --filter @effigenix/cli test
|
||||||
|
|
||||||
|
# Mit Watch
|
||||||
|
pnpm --filter @effigenix/cli test --watch
|
||||||
|
|
||||||
|
# Einzelne Spec
|
||||||
|
pnpm --filter @effigenix/cli test SupplierForm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Abgrenzung
|
||||||
|
|
||||||
|
| TUI-Tests | API E2E-Tests (Playwright) |
|
||||||
|
|---|---|
|
||||||
|
| "Rendert der Screen korrekt?" | "Funktioniert das Backend?" |
|
||||||
|
| Validierungsfehler sichtbar? | Akzeptanzkriterien aus Issues |
|
||||||
|
| Tastatur-Navigation korrekt? | Kein UI-Rendering |
|
||||||
|
| API-Calls via `vi.mock()` | Echtes Backend via Docker |
|
||||||
|
|
||||||
|
Details: [`../docs/ui-testing-automation.md`](../docs/ui-testing-automation.md) Abschnitt 9.
|
||||||
|
|
||||||
|
## Zukünftig: Scanner / Tauri
|
||||||
|
|
||||||
|
Sollte Tauri-Testing (Appium o.ä.) aufgenommen werden, käme ein `scanner/`-Ordner
|
||||||
|
auf derselben Ebene wie `web-ui/` und `tui/` hinzu.
|
||||||
20
test-automation/web-ui/Dockerfile
Normal file
20
test-automation/web-ui/Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
FROM mcr.microsoft.com/playwright:v1.51.0-noble
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# pnpm installieren
|
||||||
|
RUN npm install -g pnpm@9
|
||||||
|
|
||||||
|
# Workspace-Root und e2e-Package-Manifest kopieren
|
||||||
|
COPY frontend/package.json frontend/pnpm-workspace.yaml ./
|
||||||
|
COPY test-automation/web-ui/package.json ./test-automation/web-ui/
|
||||||
|
|
||||||
|
# pnpm Install (nur Prod + Dev-Deps des e2e-Pakets)
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Test-Code und Konfiguration kopieren
|
||||||
|
COPY test-automation/web-ui/ ./test-automation/web-ui/
|
||||||
|
|
||||||
|
WORKDIR /app/test-automation/web-ui
|
||||||
|
|
||||||
|
ENTRYPOINT ["pnpm", "exec", "playwright", "test"]
|
||||||
34
test-automation/web-ui/fixtures/auth.fixture.ts
Normal file
34
test-automation/web-ui/fixtures/auth.fixture.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { test as base, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
type AuthFixtures = {
|
||||||
|
adminToken: string;
|
||||||
|
viewerToken: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const test = base.extend<AuthFixtures>({
|
||||||
|
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 };
|
||||||
29
test-automation/web-ui/fixtures/seed.fixture.ts
Normal file
29
test-automation/web-ui/fixtures/seed.fixture.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { test as base } from '@playwright/test';
|
||||||
|
import { ApiClient } from '../helpers/api-client.js';
|
||||||
|
|
||||||
|
type SeedFixtures = {
|
||||||
|
apiClient: ApiClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seed-Fixture: stellt einen authentifizierten ApiClient bereit.
|
||||||
|
*
|
||||||
|
* Strategie (zu klären in Phase 1):
|
||||||
|
* Option A – DB-Reset vor jeder Suite via Spring Boot test-Profile + Liquibase
|
||||||
|
* Option B – Test-Daten mit zufälligen Namen (UUID-Suffix) zur Isolation
|
||||||
|
*
|
||||||
|
* Aktuell: Option B als pragmatischer Einstieg.
|
||||||
|
*/
|
||||||
|
export const test = base.extend<SeedFixtures>({
|
||||||
|
apiClient: 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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { token } = await res.json();
|
||||||
|
const client = new ApiClient(request, token);
|
||||||
|
await use(client);
|
||||||
|
},
|
||||||
|
});
|
||||||
45
test-automation/web-ui/helpers/api-client.ts
Normal file
45
test-automation/web-ui/helpers/api-client.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import type { APIRequestContext, APIResponse } from '@playwright/test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Typisierter API-Wrapper für Playwright-Tests.
|
||||||
|
* Ergänzt den raw request-Context um Auth-Header und JSON-Defaults.
|
||||||
|
*/
|
||||||
|
export class ApiClient {
|
||||||
|
constructor(
|
||||||
|
private readonly request: APIRequestContext,
|
||||||
|
private readonly token: string,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private get authHeaders() {
|
||||||
|
return { Authorization: `Bearer ${this.token}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(path: string): Promise<APIResponse> {
|
||||||
|
return this.request.get(path, { headers: this.authHeaders });
|
||||||
|
}
|
||||||
|
|
||||||
|
async post(path: string, body: unknown): Promise<APIResponse> {
|
||||||
|
return this.request.post(path, {
|
||||||
|
data: body,
|
||||||
|
headers: this.authHeaders,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(path: string, body: unknown): Promise<APIResponse> {
|
||||||
|
return this.request.put(path, {
|
||||||
|
data: body,
|
||||||
|
headers: this.authHeaders,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async patch(path: string, body: unknown): Promise<APIResponse> {
|
||||||
|
return this.request.patch(path, {
|
||||||
|
data: body,
|
||||||
|
headers: this.authHeaders,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(path: string): Promise<APIResponse> {
|
||||||
|
return this.request.delete(path, { headers: this.authHeaders });
|
||||||
|
}
|
||||||
|
}
|
||||||
17
test-automation/web-ui/package.json
Normal file
17
test-automation/web-ui/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "@effigenix/e2e",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Playwright E2E-Tests für Effigenix ERP (Web UI + API)",
|
||||||
|
"scripts": {
|
||||||
|
"test": "playwright test",
|
||||||
|
"test:api": "playwright test --project=api",
|
||||||
|
"test:web": "playwright test --project=web-chromium",
|
||||||
|
"report": "playwright show-report playwright-report"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.51.0",
|
||||||
|
"@types/node": "^20.0.0",
|
||||||
|
"typescript": "^5.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
31
test-automation/web-ui/playwright.config.ts
Normal file
31
test-automation/web-ui/playwright.config.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
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' }],
|
||||||
|
],
|
||||||
|
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 nötig – reiner API-Test via request context
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'web-chromium',
|
||||||
|
testMatch: 'tests/web/**/*.spec.ts',
|
||||||
|
use: { browserName: 'chromium' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
30
test-automation/web-ui/tests/api/auth/authorization.spec.ts
Normal file
30
test-automation/web-ui/tests/api/auth/authorization.spec.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { test, expect } from '../../../fixtures/auth.fixture.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TC-AUTH – Autorisierung Masterdata
|
||||||
|
* Quelle: GitHub Issue #67
|
||||||
|
*/
|
||||||
|
test.describe('TC-AUTH: Autorisierung', () => {
|
||||||
|
test('TC-AUTH-01: Unauthentifizierter Zugriff wird abgelehnt', async ({ request }) => {
|
||||||
|
const res = await request.get('/api/suppliers');
|
||||||
|
expect([401, 403]).toContain(res.status());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-AUTH-02: Admin darf Lieferant erstellen', async ({ request, adminToken }) => {
|
||||||
|
const res = await request.post('/api/suppliers', {
|
||||||
|
data: { name: `Auth-Test-${Date.now()}`, phone: '+49 30 00000' },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
expect(res.status()).toBe(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-AUTH-03: Viewer darf keine Lieferanten erstellen', async ({ request, viewerToken }) => {
|
||||||
|
const res = await request.post('/api/suppliers', {
|
||||||
|
data: { name: `Viewer-Test-${Date.now()}`, phone: '+49 30 00001' },
|
||||||
|
headers: { Authorization: `Bearer ${viewerToken}` },
|
||||||
|
});
|
||||||
|
expect(res.status()).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Weitere ACs aus Issue #67 ergänzen
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { test, expect } from '../../../fixtures/auth.fixture.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TC-CAT – Produktkategorien
|
||||||
|
* Quelle: GitHub Issue #62
|
||||||
|
*/
|
||||||
|
test.describe('TC-CAT: Produktkategorien', () => {
|
||||||
|
test('TC-CAT-01: Kategorie erstellen – Happy Path', async ({ request, adminToken }) => {
|
||||||
|
const res = await request.post('/api/product-categories', {
|
||||||
|
data: { name: 'Obst & Gemüse', description: 'Frische Produkte' },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
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, adminToken }) => {
|
||||||
|
const res = await request.post('/api/product-categories', {
|
||||||
|
data: { name: `Milchprodukte-${Date.now()}` },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
expect(res.status()).toBe(201);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-CAT-04: Doppelter Name wird abgelehnt', async ({ request, adminToken }) => {
|
||||||
|
const name = `Duplikat-${Date.now()}`;
|
||||||
|
await request.post('/api/product-categories', {
|
||||||
|
data: { name },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
const res = await request.post('/api/product-categories', {
|
||||||
|
data: { name },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
expect([409, 422]).toContain(res.status());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-CAT-06: Leerer Name wird abgelehnt', async ({ request, adminToken }) => {
|
||||||
|
const res = await request.post('/api/product-categories', {
|
||||||
|
data: { name: '' },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
expect(res.status()).toBe(400);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { test, expect } from '../../../fixtures/auth.fixture.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TC-SUP – Lieferanten
|
||||||
|
* Quelle: GitHub Issue #63
|
||||||
|
*/
|
||||||
|
test.describe('TC-SUP: Lieferanten', () => {
|
||||||
|
test('TC-SUP-01: Lieferant erstellen – Pflichtfelder', async ({ request, adminToken }) => {
|
||||||
|
const res = await request.post('/api/suppliers', {
|
||||||
|
data: { name: `Frisch AG ${Date.now()}`, phone: '+49 30 12345' },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
expect(res.status()).toBe(201);
|
||||||
|
const body = await res.json();
|
||||||
|
expect(body.status).toBe('AKTIV');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-SUP-02: Lieferant erscheint in Liste nach Erstellung', async ({ request, adminToken }) => {
|
||||||
|
const name = `ListTest-${Date.now()}`;
|
||||||
|
await request.post('/api/suppliers', {
|
||||||
|
data: { name, phone: '+49 30 99999' },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
const res = await request.get('/api/suppliers', {
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
expect(res.status()).toBe(200);
|
||||||
|
const body = await res.json();
|
||||||
|
const found = (Array.isArray(body) ? body : body.content ?? []).some(
|
||||||
|
(s: { name: string }) => s.name === name,
|
||||||
|
);
|
||||||
|
expect(found).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TC-SUP-03: Lieferant ohne Name wird abgelehnt', async ({ request, adminToken }) => {
|
||||||
|
const res = await request.post('/api/suppliers', {
|
||||||
|
data: { phone: '+49 30 12345' },
|
||||||
|
headers: { Authorization: `Bearer ${adminToken}` },
|
||||||
|
});
|
||||||
|
expect(res.status()).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Weitere TCs aus Issue #63 nach Implementierung hinzufügen
|
||||||
|
// TC-SUP-04: Doppelter Name abgelehnt
|
||||||
|
// TC-SUP-05: Lieferant deaktivieren
|
||||||
|
// TC-SUP-06: Filter nach Name
|
||||||
|
});
|
||||||
0
test-automation/web-ui/tests/web/.gitkeep
Normal file
0
test-automation/web-ui/tests/web/.gitkeep
Normal file
12
test-automation/web-ui/tsconfig.json
Normal file
12
test-automation/web-ui/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["tests/**/*.ts", "fixtures/**/*.ts", "helpers/**/*.ts", "playwright.config.ts"]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue