import { test, expect, Page } from '@playwright/test' /** * IACE (CE-Compliance) Module — New Feature E2E Tests * * Covers: Order, Interview (Grenzen & Verwendung), Compliance Alerts, * Risk Assessment Table, Mitigations batch actions, CE-Akte Export, * Production Lines, Normenrecherche. * * Run with: * npx playwright test e2e/specs/iace-features.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', }, { id: 'a4c4031e-75a5-461e-a575-159f1eabd6b3', name: 'EIGENBAU-Zelle (Cobot)', }, { id: 'c43af8df-14e0-43ff-b26f-ab425f803e53', name: 'Gleichstrom-/Asynchronmotor', }, { id: '3e0808b2-2eed-4e82-b35d-6dd6857bc379', name: 'Schwingarm-Rundtaktanlage', }, ] as const const COBOT_PROJECT_ID = 'a4c4031e-75a5-461e-a575-159f1eabd6b3' const PRODUCTION_LINE_ID = 'c63b774e-22d4-4045-bb8d-646df626c42b' // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- 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 } } async function goTo(page: Page, path: string) { await page.goto(`${BASE}${path}`, { waitUntil: 'networkidle', timeout: 30000 }) await page.waitForTimeout(2000) await dismissCookieBanner(page) } async function assertNoAppError(page: Page) { const body = await page.textContent('body') expect(body).not.toContain('Application error') expect(body).not.toContain('Unhandled Runtime Error') } // --------------------------------------------------------------------------- // Per-project new feature tests // --------------------------------------------------------------------------- for (const project of PROJECTS) { // ------ Order tab ------ test.describe(`Order: ${project.name}`, () => { test.setTimeout(60_000) 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`) const body = await page.innerText('body') expect(body).toContain('Auftraggeber') expect(body).toContain('Firmenname') expect(body).toContain('Ansprechpartner') expect(body).toContain('E-Mail') expect(body).toContain('Telefon') }) test('order — status dropdown works', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/order`) const statusSelect = page.locator('select') await expect(statusSelect.first()).toBeVisible({ timeout: 10000 }) const options = statusSelect.first().locator('option') const count = await options.count() expect(count).toBeGreaterThanOrEqual(3) }) 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 options visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/order`) const body = await page.innerText('body') expect(body).toContain('Risikobeurteilung') expect(body).toContain('CE-Kennzeichnung') }) }) // ------ Interview (Grenzen & Verwendung) ------ test.describe(`Interview: ${project.name}`, () => { test.setTimeout(60_000) test('interview tab loads', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) await assertNoAppError(page) await expect(page.locator('h1')).toContainText('Grenzen', { timeout: 15000 }) }) test('interview — form sections visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) const body = await page.innerText('body') // Section titles contain the number prefix ("1. Allgemeine Produktbeschreibung") expect(body).toContain('Allgemeine Produktbeschreibung') expect(body).toContain('Bestimmungsgemasse Verwendung') expect(body).toContain('Vorhersehbare Fehlanwendung') expect(body).toContain('Grenzen der Maschine') expect(body).toContain('Schnittstellen') expect(body).toContain('Betroffene Personen') }) test('interview — pre-filled data visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) await page.waitForTimeout(2000) const body = await page.innerText('body') 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`) // Click on a collapsed section to expand it const sectionBtn = page.locator('button').filter({ hasText: 'Bestimmungsgemasse Verwendung' }) await expect(sectionBtn).toBeVisible({ timeout: 10000 }) await sectionBtn.click() await page.waitForTimeout(1000) // After expanding, "Verwendungszweck" should appear in the body const bodyAfter = await page.innerText('body') expect(bodyAfter).toContain('Verwendungszweck') }) test('interview — completion percentage visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) // CompletionBadge shows "X% ausgefuellt" — use body text check const body = await page.innerText('body') expect(body).toMatch(/\d+%\s*ausgefuellt/) }) test('interview — navigation buttons present', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/interview`) const body = await page.innerText('body') expect(body).toContain('Zurueck zur Uebersicht') expect(body).toContain('Weiter zu Komponenten') }) }) // ------ Hazards: Risk Assessment ------ test.describe(`Risk Assessment: ${project.name}`, () => { test.setTimeout(60_000) test('hazards — default view is Risikobewertung', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await page.waitForTimeout(2000) const body = await page.innerText('body') expect(body).toContain('Risikobewertungstabelle') }) test('hazards — S/E/P dropdowns visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await page.waitForTimeout(3000) const selects = page.locator('select') await expect(selects.first()).toBeVisible({ timeout: 15000 }) const selectCount = await selects.count() expect(selectCount).toBeGreaterThan(0) const firstValue = await selects.first().inputValue() const numValue = parseInt(firstValue, 10) expect(numValue).toBeGreaterThanOrEqual(1) }) test('hazards — "Gefaehrdungen erkennen" button visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/hazards`) await expect( page.locator('button', { hasText: 'Gefaehrdungen erkennen' }) ).toBeVisible({ timeout: 10000 }) }) }) // ------ Mitigations: Batch actions ------ test.describe(`Mitigations batch: ${project.name}`, () => { test.setTimeout(60_000) 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 — checkbox selection works', 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) 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) await expect( page.locator('button', { hasText: 'Verifizieren' }) ).toBeVisible({ timeout: 10000 }) await expect( page.locator('button', { hasText: 'Loeschen' }) ).toBeVisible({ timeout: 10000 }) } }) }) // ------ Tech File: Export buttons ------ test.describe(`Tech File Export: ${project.name}`, () => { test.setTimeout(60_000) 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 or sections visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}/tech-file`) await page.waitForTimeout(2000) const body = await page.innerText('body') // Progress section or section list renders const hasContent = body.includes('Fortschritt') || body.includes('Generieren') || body.includes('Keine Abschnitte') expect(hasContent).toBeTruthy() }) }) // ------ Overview: Compliance Alerts & Normenrecherche ------ test.describe(`Overview features: ${project.name}`, () => { test.setTimeout(60_000) test('overview — compliance alerts or quick actions visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await page.waitForTimeout(3000) await assertNoAppError(page) const body = await page.innerText('body') const hasAlerts = body.includes('Compliance-Hinweise erkannt') const hasQuick = body.includes('Schnellzugriff') expect(hasAlerts || hasQuick).toBeTruthy() }) test('overview — regulation badges 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')) { const hasBadge = body.includes('DSGVO') || body.includes('AI Act') || body.includes('CRA') expect(hasBadge).toBeTruthy() } }) test('overview — normenrecherche section visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await page.waitForTimeout(3000) const body = await page.innerText('body') if (body.includes('Normenrecherche')) { expect(body).toContain('relevante Normen') } }) test('overview — norm add input when norms visible', async ({ page }) => { await goTo(page, `/sdk/iace/${project.id}`) await page.waitForTimeout(3000) const addInput = page.locator('input[placeholder*="ISO 13857"]') if (await addInput.count() > 0) { await expect(addInput.first()).toBeVisible({ timeout: 10000 }) } }) }) } // --------------------------------------------------------------------------- // Compliance Alerts — Cobot project specific // --------------------------------------------------------------------------- test.describe('Compliance Alerts — Cobot', () => { test.setTimeout(60_000) test('trigger count > 0', 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')) { const match = body.match(/(\d+)\s*Compliance-Hinweise erkannt/) expect(match).not.toBeNull() if (match) { expect(parseInt(match[1], 10)).toBeGreaterThan(0) } } }) test('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')) { const hasDSGVO = body.includes('DSGVO') const hasAIAct = body.includes('AI Act') const hasCRA = body.includes('CRA') expect(hasDSGVO || hasAIAct || hasCRA).toBeTruthy() } }) }) // --------------------------------------------------------------------------- // 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('"Neue Produktionslinie" button visible', async ({ page }) => { await goTo(page, '/sdk/iace/lines') await expect( page.locator('button', { hasText: 'Neue Produktionslinie' }) ).toBeVisible({ timeout: 10000 }) }) test('"Fertigungsstrasse Halle 3" visible', async ({ page }) => { await goTo(page, '/sdk/iace/lines') await page.waitForTimeout(3000) const body = await page.innerText('body') 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) 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) const body = await page.innerText('body') expect(body).toContain('Stationsuebersicht') await expect( page.locator('text=Alle Produktionslinien') ).toBeVisible({ timeout: 10000 }) }) test('line dashboard — back link', 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 }) }) }) // --------------------------------------------------------------------------- // Normenrecherche — Cobot project // --------------------------------------------------------------------------- test.describe('Normenrecherche — Cobot', () => { test.setTimeout(60_000) test('shows norm count', async ({ page }) => { await goTo(page, `/sdk/iace/${COBOT_PROJECT_ID}`) await page.waitForTimeout(4000) const body = await page.innerText('body') if (body.includes('Normenrecherche')) { expect(body).toContain('relevante Normen') // Extract and verify a substantial count const match = body.match(/(\d+)\s*relevante Normen/) if (match) { expect(parseInt(match[1], 10)).toBeGreaterThan(50) } } }) test('add norm input visible', async ({ page }) => { await goTo(page, `/sdk/iace/${COBOT_PROJECT_ID}`) await page.waitForTimeout(4000) const body = await page.innerText('body') // Check the "Weitere Norm ergaenzen" text and input field if (body.includes('Normenrecherche')) { expect(body).toContain('Weitere Norm ergaenzen') } }) })