From ac624f2e9b13af211bb2e23c9e2e7127c5932318 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 7 May 2026 16:13:07 +0200 Subject: [PATCH] feat: Umfassende Playwright-Tests fuer alle IACE Features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Order, Grenzen, Compliance Alerts, Risk Assessment, Mitigations, CE-Akte Export, Production Lines, Normenrecherche — alle getestet. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../e2e/specs/iace-module.spec.ts | 390 +++++++++++++++++- 1 file changed, 381 insertions(+), 9 deletions(-) diff --git a/admin-compliance/e2e/specs/iace-module.spec.ts b/admin-compliance/e2e/specs/iace-module.spec.ts index 9b9000a..6e01899 100644 --- a/admin-compliance/e2e/specs/iace-module.spec.ts +++ b/admin-compliance/e2e/specs/iace-module.spec.ts @@ -4,7 +4,9 @@ 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. + * 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 @@ -43,6 +45,12 @@ const PROJECTS = [ }, ] 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 // --------------------------------------------------------------------------- @@ -111,7 +119,7 @@ test.describe('IACE Start Page', () => { }) // --------------------------------------------------------------------------- -// 2–9. Per-project tests +// 2–9. Per-project tests (existing tabs + new features) // --------------------------------------------------------------------------- for (const project of PROJECTS) { @@ -175,6 +183,55 @@ for (const project of PROJECTS) { 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`) @@ -196,6 +253,106 @@ for (const project of PROJECTS) { ).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`) @@ -213,6 +370,41 @@ for (const project of PROJECTS) { ).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