/** * 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 123–136) * * 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('') }) })