Files
breakpilot-compliance/admin-compliance/tests/playwright/founding-wizard/founding-wizard.spec.ts
T
Benjamin Admin 7a5f1e48dd feat(founding-wizard): Gründungs-Wizard für 2-Mann GmbH + 14 Notar-Templates
[migration-approved]

Templates (Migrations 123-136):
- 123 GO-GF (Geschäftsordnung Geschäftsführung)
- 124 SHA (Shareholders' Agreement, 56 Platzhalter)
- 125 Satzung (Articles of Association mit UG-Variante)
- 126 GF-Dienstvertrag (Trennungsprinzip Organ/Anstellung)
- 127 Arbeitsvertrag (AGG-neutral, NachwG, eAU)
- 128 Gesellschafterliste (§ 40 GmbHG)
- 129 GF-Bestellungsbeschluss (mit § 6 Abs. 2 Versicherung)
- 130 HRB-Anmeldung (§§ 7, 8, 39 GmbHG, § 12 HGB)
- 131 IP-Assignment Agreement (Gründer→GmbH)
- 132 Term Sheet (Pre-Seed/Seed VC-Standard)
- 133 Wandeldarlehensvertrag (Convertible Loan)
- 134 Beteiligungsvertrag (Subscription Agreement)
- 135 ESOP/VSOP-Plan (3 Varianten)
- 136 Cap Table

Kategorisierung (Migrations 137-138):
- ALTER TABLE compliance_legal_templates ADD lifecycle_stage TEXT[],
  functional_category TEXT (mit CHECK Constraints + GIN-Index)
- Backfill aller 105 Templates: lifecycle_stage (pre_founding|founding|
  startup|kmu|konzern) + functional_category (founding_legal|employment|
  investor_funding|...)

Backend Founding-Wizard Service:
- template_renderer.py: Handlebars-light ({{VAR}}, {{#IF FLAG}}...{{/IF}})
- wizard_to_context.py: Mapping Wizard-State → SCREAMING_SNAKE_CASE Vars
- markdown_to_docx.py: Markdown → DOCX via python-docx
- founding_wizard_routes.py: POST /v1/founding-wizard/generate
  → liefert base64-DOCX-Files für ausgewählte Templates

Frontend Founding-Wizard (/sdk/founding-wizard):
- 8-Step Wizard (Basics, Gesellschafter, GF, Kapital, Notar, SHA, GF-Verträge, Generate)
- useFoundingWizardForm Hook mit localStorage-Persistenz
- TypeScript Code-Registry (template-categories.ts) als Backup zur DB
- Word-Download via data:URLs (base64)

Tests:
- 20 Unit-Tests grün (Renderer, Context-Mapping, DOCX-Conversion)
- Playwright E2E-Test mit 2-Mann GmbH (Benjamin + Sharang) Test-Daten
2026-05-20 09:30:51 +02:00

197 lines
8.3 KiB
TypeScript
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.
/**
* Playwright E2E-Test: Founding-Wizard mit 2-Mann GmbH (Benjamin Bönisch + Sharang Parnerkar).
*
* Test-Flow:
* 1. Lokale Dev-URL aufrufen
* 2. Wizard durch alle 8 Steps befüllen
* 3. Dokumente generieren (8 Stück für Notartermin-Bundle)
* 4. Word-Download-Links validieren
*
* Voraussetzung: `npm run dev` läuft auf http://localhost:3007
* Backend ist erreichbar (mit Migration 137 + 138 + Templates 123136)
*
* Ausführen:
* cd admin-compliance
* npx playwright test tests/playwright/founding-wizard/
*/
import { expect, test } from '@playwright/test'
const BASE_URL = process.env.WIZARD_URL || 'http://localhost:3007/sdk/founding-wizard'
const TEST_DATA = {
basics: {
company_name: 'Breakpilot GmbH',
company_seat: 'Bietigheim-Bissingen',
company_address: 'Hauptstraße 1, 74321 Bietigheim-Bissingen',
industry: 'Software / KI / SaaS',
purpose: 'die Entwicklung, Bereitstellung und der Vertrieb von Softwarelösungen, Plattformen und IT-Dienstleistungen im Bereich der Künstlichen Intelligenz sowie compliance-bezogener Datenverarbeitungssysteme',
bullets: [
'a) Entwicklung, Programmierung und Betrieb von KI-gestützter Compliance-Software',
'b) Bereitstellung von datenschutzkonformen SaaS-Lösungen für Unternehmen',
'c) Beratungs- und Integrationsleistungen im Compliance-Umfeld',
],
},
notar: {
name: 'Dr. Müller',
place: 'Stuttgart',
address: 'Königstraße 1, 70173 Stuttgart',
date: '2026-06-15',
},
gesellschafter: [
{
name: 'Benjamin Bönisch',
birthdate: '1980-03-15',
address: 'Hauptstraße 1, 74321 Bietigheim-Bissingen',
email: 'benjamin@breakpilot.ai',
nennbetrag: 12500,
is_gf: true,
role: 'CEO',
},
{
name: 'Sharang Parnerkar',
birthdate: '1985-09-22',
address: 'Hauptstraße 2, 74321 Bietigheim-Bissingen',
email: 'sharang@breakpilot.ai',
nennbetrag: 12500,
is_gf: true,
role: 'CTO',
},
],
stammkapital: 25000,
}
test.describe('Founding Wizard — 2-Mann GmbH', () => {
test.beforeEach(async ({ page }) => {
// Clear localStorage to start fresh
await page.goto(BASE_URL)
await page.evaluate(() => localStorage.clear())
await page.reload()
})
test('füllt komplette 2-Mann GmbH aus und generiert Notartermin-Bundle', async ({ page }) => {
await page.goto(BASE_URL)
await expect(page.getByTestId('founding-wizard')).toBeVisible()
// STEP 1: Basics
await expect(page.getByTestId('step-content-1')).toBeVisible()
await page.getByTestId('company-name').fill(TEST_DATA.basics.company_name)
await page.getByTestId('legal-form').selectOption('GmbH')
await page.getByTestId('company-seat').fill(TEST_DATA.basics.company_seat)
await page.getByTestId('company-address').fill(TEST_DATA.basics.company_address)
await page.getByTestId('industry').fill(TEST_DATA.basics.industry)
await page.getByTestId('company-purpose').fill(TEST_DATA.basics.purpose)
await page.getByTestId('company-purpose-bullets').fill(TEST_DATA.basics.bullets.join('\n'))
await page.getByTestId('next-step').click()
// STEP 2: Gesellschafter
await expect(page.getByTestId('step-content-2')).toBeVisible()
for (const gs of TEST_DATA.gesellschafter) {
await page.getByTestId('gs-name').fill(gs.name)
await page.getByTestId('gs-birthdate').fill(gs.birthdate)
await page.getByTestId('gs-address').fill(gs.address)
await page.getByTestId('gs-email').fill(gs.email)
await page.getByTestId('gs-nennbetrag').fill(String(gs.nennbetrag))
await page.getByTestId('gs-role').fill(gs.role)
// is_gf bereits default true, nichts zu tun
await page.getByTestId('add-gesellschafter').click()
}
await expect(page.getByTestId('gs-row-1')).toContainText('Benjamin Bönisch')
await expect(page.getByTestId('gs-row-2')).toContainText('Sharang Parnerkar')
await expect(page.getByTestId('gs-total')).toContainText('25.000')
await page.getByTestId('next-step').click()
// STEP 3: GF-Assignment (beide bereits GF aus Step 2)
await expect(page.getByTestId('step-content-3')).toBeVisible()
await page.getByTestId('next-step').click()
// STEP 4: Kapital
await expect(page.getByTestId('step-content-4')).toBeVisible()
await expect(page.getByTestId('stammkapital')).toHaveValue('25000')
await page.getByTestId('einlage-method').selectOption('Geld')
await page.getByTestId('einlage-quote').fill('50')
await page.getByTestId('next-step').click()
// STEP 5: Notar
await expect(page.getByTestId('step-content-5')).toBeVisible()
await page.getByTestId('notary-name').fill(TEST_DATA.notar.name)
await page.getByTestId('notary-place').fill(TEST_DATA.notar.place)
await page.getByTestId('notary-address').fill(TEST_DATA.notar.address)
await page.getByTestId('notarial-date').fill(TEST_DATA.notar.date)
await page.getByTestId('next-step').click()
// STEP 6: SHA-Optionen
await expect(page.getByTestId('step-content-6')).toBeVisible()
await expect(page.getByTestId('has-sha')).toBeChecked()
await expect(page.getByTestId('vesting-months')).toHaveValue('48')
await expect(page.getByTestId('drag-along-pct')).toHaveValue('75')
await page.getByTestId('next-step').click()
// STEP 7: GF-Verträge (für beide Founders)
await expect(page.getByTestId('step-content-7')).toBeVisible()
// GF-Contracts werden mit Defaults erzeugt sobald GFs definiert sind -
// wir editieren die Gehälter
const contracts = page.locator('[data-testid^="contract-"]')
const count = await contracts.count()
expect(count).toBe(2)
await page.getByTestId('next-step').click()
// STEP 8: Generate
await expect(page.getByTestId('step-content-8')).toBeVisible()
await expect(page.getByTestId('generate-summary')).toContainText('Breakpilot GmbH')
await expect(page.getByTestId('generate-summary')).toContainText('Bietigheim-Bissingen')
await expect(page.getByTestId('generate-summary')).toContainText('25.000')
// Notartermin-Bundle auswählen
await page.getByTestId('select-notary-bundle').click()
// Check that bundle items are selected
await expect(page.getByTestId('doc-articles_of_association')).toBeChecked()
await expect(page.getByTestId('doc-sha')).toBeChecked()
await expect(page.getByTestId('doc-gesellschafterliste')).toBeChecked()
await expect(page.getByTestId('doc-managing_director_employment_contract')).toBeChecked()
// Generate
await page.getByTestId('generate-docs').click()
// Warten auf Generierung (max 30s)
await expect(page.getByTestId('generated-docs')).toBeVisible({ timeout: 30000 })
// Mindestens 8 Dokumente sollten erscheinen (für 2 Founders evtl. doppelt: GF-Vertrag, IP-Assignment)
const downloadLinks = page.locator('[data-testid^="download-"]')
const linkCount = await downloadLinks.count()
expect(linkCount).toBeGreaterThanOrEqual(8)
// Validiere dass download-URLs data: URLs sind (base64 DOCX)
for (let i = 0; i < Math.min(linkCount, 3); i++) {
const href = await downloadLinks.nth(i).getAttribute('href')
expect(href).toMatch(/^data:application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document;base64,/)
}
// Screenshot fürs Test-Artifact
await page.screenshot({ path: 'test-results/founding-wizard-final.png', fullPage: true })
})
test('zeigt Validierung wenn Pflichtfelder fehlen', async ({ page }) => {
await page.goto(BASE_URL)
// Next-Button sollte disabled sein wenn nichts ausgefüllt
await expect(page.getByTestId('next-step')).toBeDisabled()
await page.getByTestId('company-name').fill('Test')
// Immer noch disabled weil purpose fehlt
await expect(page.getByTestId('next-step')).toBeDisabled()
await page.getByTestId('company-seat').fill('Stuttgart')
await page.getByTestId('company-purpose').fill('Eine lange genug Beschreibung des Zwecks.')
// Jetzt sollte er enabled sein
await expect(page.getByTestId('next-step')).toBeEnabled()
})
test('Reset löscht alle Daten', async ({ page }) => {
await page.goto(BASE_URL)
await page.getByTestId('company-name').fill('Wird gelöscht GmbH')
page.on('dialog', d => d.accept())
await page.getByTestId('reset-wizard').click()
await expect(page.getByTestId('company-name')).toHaveValue('')
})
})