/** * CAD-14 Beweis-Test: Der Workflow, den der Nutzer jeden Tag macht. * 1. Werkzeug waehlen (Sidebar), 2. zeichnen (2 Klicks), * 3. EXAKT EIN Element an der geklickten Position erwarten. * ROT-Zustand (jetzt): 2 Elemente (Legacy + V2 je eins) = Doppelung belegt. * GRUEN nach Schnitt: 1 Element, Position = Klickposition. */ import { test, expect } from '@playwright/test'; const BASE_URL = process.env.FRONTEND_URL || 'https://web-cad-neu.server.media-on.de'; const API_URL = process.env.API_URL || 'https://web-cad-neu.server.media-on.de'; async function api(method: string, endpoint: string, body?: unknown, token?: string) { const headers: Record = { 'Content-Type': 'application/json' }; if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch(`${API_URL}${endpoint}`, { method, headers, body: body ? JSON.stringify(body) : undefined }); const text = await res.text(); return { status: res.status, body: text ? JSON.parse(text) : null }; } interface VerifyWindow { __v2Debug?: { info: () => { cachedElements: number; ready: boolean } | null }; } test('zeichnen erzeugt GENAU EIN Element an der Klick-Position', async ({ page }) => { test.setTimeout(60000); await page.goto(BASE_URL); await page.fill('input[type=email]', 'admin@media-on.de'); await page.fill('input[type=password]', 'WebCAD2026!'); await page.click('button[type=submit]'); await page.waitForTimeout(3000); // CAD-14: Frisches Projekt + Zeichnung -> Basis 0, Zuordnung eindeutig const token = await page.evaluate(() => localStorage.getItem('auth_token')); expect(token).toBeTruthy(); const uniq = Date.now(); const projRes = await fetch(`${BASE_URL}/api/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ name: `CAD14-${uniq}`, description: 'proof' }), }); const proj = await projRes.json(); expect(proj?.id).toBeTruthy(); const drawingRes = await fetch(`${BASE_URL}/api/projects/${proj.id}/drawings`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ name: `CAD14-Proof-${uniq}` }), }); const drawing = await drawingRes.json(); expect(drawing?.id).toBeTruthy(); const drawingId: string = drawing.id; const beforeDbCount = 0; // Karte des frischen Projekts oeffnen (eindeutig per Name) — Dashboard // laedt die Liste beim Mount, daher Reload nach API-Anlage. await page.reload(); await page.waitForTimeout(2500); const card = page.locator(`.dashboard-project-card, [class*=project-card]`).filter({ hasText: `CAD14-${uniq}` }).first(); await card.click(); await page.waitForTimeout(2500); const verify = () => page.evaluate(() => { const w = window as unknown as VerifyWindow; return w.__v2Debug?.info?.() ?? null; }); const before = await verify(); const beforeCount = before?.cachedElements ?? -1; expect(beforeCount, 'Pixi muss ready sein').toBeGreaterThanOrEqual(0); // DB-Basis ist 0 (frische Zeichnung) // Linie-Werkzeug aus der SIDEBAR (der richtige Ort) await page.locator('[data-tool="line"]').first().click(); await page.waitForTimeout(300); // Auf die Zeichenflaeche klicken: zwei Klicks = eine Linie const canvas = page.locator('canvas').last(); const box = await canvas.boundingBox(); if (!box) throw new Error('Kein Canvas gefunden'); const cx = box.x + box.width / 2; const cy = box.y + box.height / 2; // CAD-14: line ist ein DRAG-Werkzeug (down -> move -> up committet). const startX = cx - 60; const startY = cy - 30; await page.mouse.move(startX, startY); await page.mouse.down(); await page.mouse.move(startX + 120, startY + 60, { steps: 8 }); await page.mouse.up(); await page.waitForTimeout(800); const after = await verify(); const afterCount = after?.cachedElements ?? -1; // ABNAHME 1: genau +1 Element im Renderer expect(afterCount - beforeCount, `Erwartet genau 1 neues Element, got ${afterCount - beforeCount}`).toBe(1); // ABNAHME 2: das Element MUSS in der DB persistiert sein (Autosave/Save-Kette) // drawingId aus Scope (frisch angelegt) // Autosave braucht bis zu 30s — poll bis zu 40s // CAD-14: ALLE Drawings des Proof-Projekts pruefen (die App koennte in // eine auto-angelegte Zeichnung schreiben statt in meine). let dbCount = -1; for (let i = 0; i < 8; i++) { await page.waitForTimeout(5000); const drs = await (await fetch(`${BASE_URL}/api/projects/${proj.id}/drawings`, { headers: { Authorization: `Bearer ${token}` } })).json(); let total = 0; for (const d of drs) { const els = await (await fetch(`${BASE_URL}/api/drawings/${d.id}/elements`, { headers: { Authorization: `Bearer ${token}` } })).json(); total += els.length; if (els.length > 0) console.log(`DRAWING ${d.id}: ${els.length} Elemente`); } dbCount = total; if (total >= 1) { console.log('GEFUNDEN in drawings:', drs.map((d: any) => d.id)); break; } } expect(dbCount, `Genau 1 Element muss in der DB landen (vor ${beforeDbCount}, nach ${dbCount})`).toBe(1); // ABNAHME 3: V2-Select-Tool klickt auf die Linienmitte — Auswahl muss // auf dem V2-Dokument landen (Karte: __v2GetSelection). await page.locator('[data-tool="select"]').first().click(); await page.waitForTimeout(300); const midX = startX + 60; const midY = startY + 30; await page.mouse.click(midX, midY); await page.waitForTimeout(500); const sel = await page.evaluate(() => { const w = window as unknown as { __v2GetSelection?: () => { ids: string[] } }; return w.__v2GetSelection?.() ?? null; }); expect(sel, 'Selection-Bridge muss erreichbar sein').toBeTruthy(); expect(sel!.ids.length, `Genau 1 Element muss ausgewaehlt sein, got ${sel!.ids.length}`).toBe(1); });