1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 15:29:34 +01:00
effigenix/test-automation/docs/ui-testing-automation.md
2026-03-27 09:41:35 +01:00

651 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |