import { test, expect, Page } from '@playwright/test' /** * IACE (CE-Compliance) Module — Comprehensive E2E Tests * * Tests all 4 seeded projects across every tab: * Overview, Components, Hazards, Mitigations, Verification, Evidence, Tech-File, Monitoring, * Order, Interview (Grenzen & Verwendung), Compliance Alerts, Risk Assessment Table, * CE-Akte Export, Production Lines, Normenrecherche. * * Run with: * npx playwright test e2e/specs/iace-module.spec.ts --config e2e/playwright-live.config.ts --reporter=list */ const BASE = 'https://macmini:3007' const PROJECTS = [ { id: 'bb7d5b88-469d-401f-a0e3-ae5b867e4a1c', name: 'Kniehebelpresse HP-500', expectedComps: 14, expectedHazards: 8, expectedMeasures: 20, }, { id: 'a4c4031e-75a5-461e-a575-159f1eabd6b3', name: 'EIGENBAU-Zelle (Cobot)', expectedComps: 7, expectedHazards: 8, expectedMeasures: 26, }, { id: 'c43af8df-14e0-43ff-b26f-ab425f803e53', name: 'Gleichstrom-/Asynchronmotor', expectedComps: 6, expectedHazards: 6, expectedMeasures: 16, }, { id: '3e0808b2-2eed-4e82-b35d-6dd6857bc379', name: 'Schwingarm-Rundtaktanlage', expectedComps: 7, expectedHazards: 10, expectedMeasures: 38, }, ] as const /** The Cobot project ID — used for compliance alerts checks. */ const COBOT_PROJECT_ID = 'a4c4031e-75a5-461e-a575-159f1eabd6b3' /** Seeded production line ID. */ const PRODUCTION_LINE_ID = 'c63b774e-22d4-4045-bb8d-646df626c42b' // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Dismiss the cookie consent banner if present (blocks all clicks). */ async function dismissCookieBanner(page: Page) { try { const acceptBtn = page.locator('button', { hasText: 'Nur notwendige Cookies' }) if (await acceptBtn.isVisible({ timeout: 3000 })) { await acceptBtn.click({ force: true }) await page.waitForTimeout(500) } } catch { // Banner not present or already dismissed } } /** Navigate, wait for async data, and dismiss cookie overlay. */ async function goTo(page: Page, path: string) { await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle', timeout: 30000 }) await page.waitForTimeout(2000) await dismissCookieBanner(page) } /** Assert that no Next.js / React error overlay is present. */ async function assertNoAppError(page: Page) { const body = await page.textContent('body') expect(body).not.toContain('Application error') expect(body).not.toContain('Unhandled Runtime Error') } // --------------------------------------------------------------------------- // 1. IACE Start Page (/sdk/iace) // --------------------------------------------------------------------------- test.describe('IACE Start Page', () => { test.setTimeout(60_000) test('page loads without error', async ({ page }) => { await goTo(page, '/sdk/iace') await assertNoAppError(page) // The page title (h1) should contain CE-Compliance const body = await page.innerText('body') expect(body).toContain('CE-Compliance (IACE)') }) test('page description text visible', async ({ page }) => { await goTo(page, '/sdk/iace') const body = await page.innerText('body') expect(body).toContain('Industrial AI Compliance Engine') }) test('create project button visible', async ({ page }) => { await goTo(page, '/sdk/iace') await expect( page.locator('button', { hasText: 'Neues Projekt erstellen' }) ).toBeVisible({ timeout: 10000 }) }) test('sidebar navigation has IACE link', async ({ page }) => { await goTo(page, '/sdk/iace') // The SDK sidebar should have "CE-Compliance (IACE)" as a link const body = await page.innerText('body') expect(body).toContain('CE-Compliance (IACE)') }) }) // --------------------------------------------------------------------------- // 2–9. Per-project tests (existing tabs + new features) // --------------------------------------------------------------------------- for (const project of PROJECTS) { test.describe(`Project: ${project.name}`, () => { test.setTimeout(60_000) // ------ Overview ------ test('overview page loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText(project.name, { timeout: 15000 }) }) test('overview — status workflow visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) // Status workflow or risk summary should be visible await expect( page.locator('text=Projektstatus').or(page.locator('text=Risikozusammenfassung')) ).toBeVisible({ timeout: 10000 }) }) test('overview — risk summary section', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await expect( page.locator('text=Risikozusammenfassung') ).toBeVisible({ timeout: 10000 }) }) test('overview — component/hazard/measure counters', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) // The risk summary card has three counters const body = await page.innerText('body') expect(body).toContain('Komponenten') expect(body).toContain('Gefaehrdungen') }) test('overview — completeness gates section', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await expect( page.locator('text=Completeness Gates') ).toBeVisible({ timeout: 10000 }) }) test('overview — quick actions present', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await expect(page.locator('text=Schnellzugriff')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Komponenten verwalten')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Hazard Log oeffnen')).toBeVisible({ timeout: 10000 }) }) test('overview — IACE sidebar navigation visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) // Sidebar with nav items (may be in aside or nav element) await expect( page.locator('text=Uebersicht').or(page.locator('text=Hazard Log')).first() ).toBeVisible({ timeout: 10000 }) }) test('overview — no crash from norms API', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await assertNoAppError(page) }) // ------ Overview: Compliance Alerts ------ test('overview — compliance alerts section visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) // ComplianceAlerts renders only when triggers > 0. // Wait for content to settle, then check for either the alerts header or // the absence of an error (some projects may have 0 triggers). await page.waitForTimeout(3000) await assertNoAppError(page) const body = await page.innerText('body') // At least one of these should be present on the overview page: // The alerts section OR the norms section OR the quick actions section const hasAlerts = body.includes('Compliance-Hinweise erkannt') const hasNorms = body.includes('Normenrecherche') const hasQuick = body.includes('Schnellzugriff') expect(hasAlerts || hasNorms || hasQuick).toBeTruthy() }) test('overview — regulation badges visible when alerts present', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await page.waitForTimeout(3000) const body = await page.innerText('body') if (body.includes('Compliance-Hinweise erkannt')) { // Regulation badges should be rendered (DSGVO, AI Act, CRA, NIS2, Data Act) const hasBadge = body.includes('DSGVO') || body.includes('AI Act') || body.includes('CRA') expect(hasBadge).toBeTruthy() } }) // ------ Overview: Normenrecherche ------ test('overview — normenrecherche section visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await page.waitForTimeout(3000) const body = await page.innerText('body') // SuggestedNorms renders with the total count — verify it shows "relevante Normen" if (body.includes('Normenrecherche')) { expect(body).toContain('relevante Normen') } }) test('overview — norm add field visible when norms section open', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await page.waitForTimeout(3000) // The SuggestedNorms section has a custom norm input with placeholder "z.B. ISO 13857:2019" const addInput = page.locator('input[placeholder*="ISO 13857"]') if (await addInput.count() > 0) { await expect(addInput.first()).toBeVisible({ timeout: 10000 }) } }) // ------ Components ------ test('components tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/components`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Komponenten', { timeout: 10000 }) }) test('components — "Aus Bibliothek waehlen" button', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/components`) await expect( page.locator('button', { hasText: 'Aus Bibliothek waehlen' }) ).toBeVisible({ timeout: 10000 }) }) test('components — "Komponente hinzufuegen" button', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/components`) await expect( page.locator('button', { hasText: 'Komponente hinzufuegen' }) ).toBeVisible({ timeout: 10000 }) }) // ------ Order ------ test('order tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/order`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Auftrag', { timeout: 15000 }) }) test('order — form fields visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/order`) // The Auftraggeber section with Firmenname and Ansprechpartner await expect(page.locator('text=Auftraggeber')).toBeVisible({ timeout: 10000 }) await expect(page.locator('label:has-text("Firmenname")')).toBeVisible({ timeout: 10000 }) await expect(page.locator('label:has-text("Ansprechpartner")')).toBeVisible({ timeout: 10000 }) await expect(page.locator('label:has-text("E-Mail")')).toBeVisible({ timeout: 10000 }) await expect(page.locator('label:has-text("Telefon")')).toBeVisible({ timeout: 10000 }) }) test('order — status dropdown works', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/order`) // The Angebot section has a status select with options const statusSelect = page.locator('select') await expect(statusSelect.first()).toBeVisible({ timeout: 10000 }) // Verify dropdown has the expected options const options = statusSelect.first().locator('option') const count = await options.count() expect(count).toBeGreaterThanOrEqual(3) // offen, angenommen, abgelehnt, storniert }) test('order — Auftrag and Angebot sections visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/order`) await expect(page.locator('text=Auftrag').first()).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Angebot').first()).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Notizen')).toBeVisible({ timeout: 10000 }) }) test('order — scope checkboxes visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/order`) // SCOPE_OPTIONS: Risikobeurteilung, Normenrecherche, Betriebsanleitung, CE-Kennzeichnung, Schulung await expect(page.locator('text=Risikobeurteilung').first()).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=CE-Kennzeichnung')).toBeVisible({ timeout: 10000 }) }) // ------ Interview (Grenzen & Verwendung) ------ test('interview tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Grenzen & Verwendung', { timeout: 15000 }) }) test('interview — form sections visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) // The 6 collapsible section headers await expect(page.locator('text=Allgemeine Produktbeschreibung')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Bestimmungsgemasse Verwendung')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Vorhersehbare Fehlanwendung')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Grenzen der Maschine')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Schnittstellen')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Betroffene Personen')).toBeVisible({ timeout: 10000 }) }) test('interview — pre-filled data visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) // First section is open by default and shows pre-filled machine name await page.waitForTimeout(2000) const body = await page.innerText('body') // The machine name from the project should appear somewhere in the form // (either in the input or in a "Vorausgefuellt" help text) const hasProjectName = body.includes(project.name) || body.includes('Vorausgefuellt') expect(hasProjectName).toBeTruthy() }) test('interview — section collapse/expand works', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) // "Bestimmungsgemasse Verwendung" section is collapsed by default // Click to expand it const sectionBtn = page.locator('button', { hasText: 'Bestimmungsgemasse Verwendung' }) await expect(sectionBtn).toBeVisible({ timeout: 10000 }) await sectionBtn.click() await page.waitForTimeout(500) // After expanding, we should see "Verwendungszweck" label inside await expect(page.locator('text=Verwendungszweck')).toBeVisible({ timeout: 10000 }) // Click again to collapse await sectionBtn.click() await page.waitForTimeout(500) // Content should no longer be visible await expect(page.locator('label:has-text("Verwendungszweck")')).not.toBeVisible({ timeout: 3000 }) }) test('interview — completion badge visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) // CompletionBadge shows "X% ausgefuellt" await expect(page.locator('text=ausgefuellt')).toBeVisible({ timeout: 10000 }) }) test('interview — navigation buttons present', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) await expect(page.locator('text=Zurueck zur Uebersicht')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Weiter zu Komponenten')).toBeVisible({ timeout: 10000 }) }) // ------ Hazards ------ test('hazards tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Hazard Log', { timeout: 10000 }) }) test('hazards — view toggle exists', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await expect( page.locator('button', { hasText: 'Hazard-Liste' }) ).toBeVisible({ timeout: 10000 }) await expect( page.locator('button', { hasText: 'Risikobewertung' }) ).toBeVisible({ timeout: 10000 }) }) test('hazards — default view is Risikobewertung', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) // The "Risikobewertung" button should be the active one (has bg-purple-600) const riskBtn = page.locator('button', { hasText: 'Risikobewertung' }) await expect(riskBtn).toBeVisible({ timeout: 10000 }) // Default state is 'risk', so the RiskAssessmentTable should be rendered // Wait for it to load await page.waitForTimeout(2000) // Check that the risk assessment table header is visible const body = await page.innerText('body') expect(body).toContain('Risikobewertungstabelle') }) test('hazards — S/E/P dropdowns visible with values > 1', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) // Default view is risk assessment — wait for table await page.waitForTimeout(3000) // RiskAssessmentTable renders elements (S/E/P/A dropdowns) const selects = page.locator('select') await expect(selects.first()).toBeVisible({ timeout: 15000 }) const selectCount = await selects.count() expect(selectCount).toBeGreaterThan(0) }) test('hazards — Auto-Erkennung button visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await expect( page.locator('button', { hasText: 'Auto-Erkennung' }) ).toBeVisible({ timeout: 10000 }) }) test('hazards — Auto-Erkennung click no crash', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await page.locator('button', { hasText: 'Auto-Erkennung' }).click() await page.waitForTimeout(5000) await assertNoAppError(page) }) test('hazards — risk level stats visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await expect( page.locator('text=Gesamt').first() ).toBeVisible({ timeout: 10000 }) }) // ------ Mitigations ------ test('mitigations tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/mitigations`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Massnahmen', { timeout: 10000 }) }) test('mitigations — 3 accordion sections visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/mitigations`) await expect(page.locator('text=Stufe 1: Design')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Stufe 2: Schutz')).toBeVisible({ timeout: 10000 }) await expect(page.locator('text=Stufe 3: Information')).toBeVisible({ timeout: 10000 }) }) test('mitigations — column descriptions visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/mitigations`) // Check for any of the 3-step hierarchy descriptions await expect( page.locator('text=Inhaerent sichere Konstruktion').or(page.locator('text=Stufe 1')) ).toBeVisible({ timeout: 10000 }) await expect( page.locator('text=Technische Schutzmassnahmen').or(page.locator('text=Stufe 2')) ).toBeVisible({ timeout: 10000 }) await expect( page.locator('text=Hinweise und Schulungen') ).toBeVisible({ timeout: 10000 }) }) test('mitigations — checkbox selection works', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/mitigations`) await page.waitForTimeout(2000) // Find the first checkbox in the mitigations table rows const checkboxes = page.locator('input[type="checkbox"]') const count = await checkboxes.count() if (count > 0) { // Click the first non-header checkbox to select an item await checkboxes.first().click() await page.waitForTimeout(500) // After selecting, batch action buttons should appear: "ausgewaehlt" text const body = await page.innerText('body') expect(body).toContain('ausgewaehlt') } }) test('mitigations — batch buttons appear when items selected', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/mitigations`) await page.waitForTimeout(2000) const checkboxes = page.locator('input[type="checkbox"]') const count = await checkboxes.count() if (count > 0) { await checkboxes.first().click() await page.waitForTimeout(500) // Batch action buttons: Verifizieren and Loeschen await expect( page.locator('button', { hasText: 'Verifizieren' }) ).toBeVisible({ timeout: 10000 }) await expect( page.locator('button', { hasText: 'Loeschen' }) ).toBeVisible({ timeout: 10000 }) } }) test('mitigations — add buttons visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/mitigations`) await expect( page.locator('button', { hasText: '+ Hinzufuegen' }) ).toBeVisible({ timeout: 10000 }) }) test('mitigations — "Bibliothek" button', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/mitigations`) await expect( page.locator('button', { hasText: 'Bibliothek' }).first() ).toBeVisible({ timeout: 10000 }) }) // ------ Verification ------ test('verification tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/verification`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Verifikationsplan', { timeout: 10000 }) }) // ------ Evidence ------ test('evidence tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/evidence`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Nachweise', { timeout: 10000 }) }) // ------ Tech File ------ test('tech-file tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/tech-file`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('CE-Akte', { timeout: 10000 }) }) test('tech-file — "PDF exportieren" button visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/tech-file`) await expect( page.locator('button', { hasText: 'PDF exportieren' }) ).toBeVisible({ timeout: 10000 }) }) test('tech-file — "Excel exportieren" button visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/tech-file`) await expect( page.locator('button', { hasText: 'Excel exportieren' }) ).toBeVisible({ timeout: 10000 }) }) test('tech-file — progress bar and section list visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/tech-file`) await page.waitForTimeout(2000) const body = await page.innerText('body') // Progress section always renders expect(body).toContain('Fortschritt') // Either sections are listed or the empty state shows const hasSections = body.includes('Generieren') || body.includes('Keine Abschnitte vorhanden') expect(hasSections).toBeTruthy() }) // ------ Monitoring ------ test('monitoring tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/monitoring`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Monitoring', { timeout: 10000 }) }) }) } // --------------------------------------------------------------------------- // 10. Compliance Alerts — Cobot project specific // --------------------------------------------------------------------------- test.describe('Compliance Alerts — Cobot Project', () => { test.setTimeout(60_000) test('compliance alerts show trigger count > 0', async ({ page }) => { await goTo(page, `/sdk/iace/${COBOT_PROJECT_ID}`) await page.waitForTimeout(4000) // ComplianceAlerts shows "X Compliance-Hinweise erkannt" const alertsHeader = page.locator('text=Compliance-Hinweise erkannt') if (await alertsHeader.isVisible({ timeout: 10000 })) { const headerText = await alertsHeader.innerText() // Extract the count from "X Compliance-Hinweise erkannt" const match = headerText.match(/(\d+)/) expect(match).not.toBeNull() if (match) { expect(parseInt(match[1], 10)).toBeGreaterThan(0) } } }) test('compliance alerts — regulation badges visible', async ({ page }) => { await goTo(page, `/sdk/iace/${COBOT_PROJECT_ID}`) await page.waitForTimeout(4000) const body = await page.innerText('body') if (body.includes('Compliance-Hinweise erkannt')) { // At least some of: DSGVO, AI Act, CRA, NIS2, Data Act const hasDSGVO = body.includes('DSGVO') const hasAIAct = body.includes('AI Act') const hasCRA = body.includes('CRA') expect(hasDSGVO || hasAIAct || hasCRA).toBeTruthy() } }) }) // --------------------------------------------------------------------------- // 11. Production Lines // --------------------------------------------------------------------------- test.describe('Production Lines', () => { test.setTimeout(60_000) test('lines list page loads', async ({ page }) => { await goTo(page, '/sdk/iace/lines') await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Produktionslinien', { timeout: 15000 }) }) test('lines — "Neue Produktionslinie" button visible', async ({ page }) => { await goTo(page, '/sdk/iace/lines') await expect( page.locator('button', { hasText: 'Neue Produktionslinie' }) ).toBeVisible({ timeout: 10000 }) }) test('lines — "Fertigungsstrasse Halle 3" visible', async ({ page }) => { await goTo(page, '/sdk/iace/lines') await page.waitForTimeout(3000) const body = await page.innerText('body') // The seeded line should appear in the list expect(body).toContain('Fertigungsstrasse Halle 3') }) test('line dashboard loads with stations', async ({ page }) => { await goTo(page, `/sdk/iace/lines/${PRODUCTION_LINE_ID}`) await assertNoAppError(page) await page.waitForTimeout(3000) // The dashboard should show "Stationsuebersicht" heading await expect( page.locator('text=Stationsuebersicht') ).toBeVisible({ timeout: 15000 }) }) test('line dashboard — station cards rendered', async ({ page }) => { await goTo(page, `/sdk/iace/lines/${PRODUCTION_LINE_ID}`) await page.waitForTimeout(3000) // The dashboard renders StationCard components with project machine names // At least one station should be present const body = await page.innerText('body') expect(body).toContain('Stationsuebersicht') // Check that we have "Alle Produktionslinien" back link await expect( page.locator('text=Alle Produktionslinien') ).toBeVisible({ timeout: 10000 }) }) test('line dashboard — back link to lines list', async ({ page }) => { await goTo(page, `/sdk/iace/lines/${PRODUCTION_LINE_ID}`) const backLink = page.locator('a', { hasText: 'Alle Produktionslinien' }) await expect(backLink).toBeVisible({ timeout: 10000 }) }) }) // --------------------------------------------------------------------------- // 12. Normenrecherche — large norm count // --------------------------------------------------------------------------- test.describe('Normenrecherche — Cobot Project', () => { test.setTimeout(60_000) test('normenrecherche shows large norm count', async ({ page }) => { await goTo(page, `/sdk/iace/${COBOT_PROJECT_ID}`) await page.waitForTimeout(4000) const body = await page.innerText('body') // SuggestedNorms shows "Normenrecherche — X relevante Normen" if (body.includes('Normenrecherche')) { const match = body.match(/Normenrecherche\s*[—-]\s*(\d+)\s*relevante Normen/) if (match) { const normCount = parseInt(match[1], 10) // Should be a substantial number (not just 215 from the old fallback) expect(normCount).toBeGreaterThan(100) } } }) test('normenrecherche — add norm input visible', async ({ page }) => { await goTo(page, `/sdk/iace/${COBOT_PROJECT_ID}`) await page.waitForTimeout(4000) // The "Weitere Norm ergaenzen" section has an input with ISO placeholder const addInput = page.locator('input[placeholder*="ISO 13857"]') if (await addInput.count() > 0) { await expect(addInput.first()).toBeVisible({ timeout: 10000 }) } }) })