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

21 KiB
Raw Blame History

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):

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

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

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

# 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

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

# 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

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

// 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)

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

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

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

// __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