1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 17:04:49 +01:00

init e2e ui tests base skeleton

This commit is contained in:
Janosch 2026-03-27 09:41:35 +01:00
parent 83c7321c8f
commit c84629cc4e
16 changed files with 1219 additions and 0 deletions

View 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-P13P17 | epic:production-order | `api/production/orders.spec.ts` | Mittel |
| #33#36 US-P09P12 | epic:batch | `api/production/batches.spec.ts` | Mittel |
| #26#32 US-P02P08 | epic:recipe | `api/production/recipes.spec.ts` | Mittel |
| #43#44 US-P18P19 | epic:traceability | `api/production/traceability.spec.ts` | Mittel |
| #4#20 Story 2.x6.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-P14P17 | je 24 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-P13P19 (#38#44)
- [ ] Inventory BC Specs aus Story 2.x6.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 |