feat: IACE CE-Compliance Module — Normen, Risikobewertung, Production Lines
Major features: - 215 norms library with section references + Beuth URLs (A/B1/B2/C norms) - 173 hazard patterns with detail fields (scenario, trigger, harm, zone) - Deterministic pattern matching: Component × Lifecycle × Pattern cross-product - SIL/PL auto-calculation from S×E×P risk graph - Risk assessment table with editable S/E/P dropdowns - Production Line Dashboard with animated station flow (Running Dots) - IACE process flow + norms coverage on start page - Non-blocking cookie banner, ProcessFlow SSR fix - 104 Playwright E2E tests passing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './specs',
|
||||
timeout: 30000,
|
||||
use: {
|
||||
baseURL: 'https://macmini:3007',
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||
],
|
||||
})
|
||||
@@ -0,0 +1,328 @@
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
// ------ 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 })
|
||||
})
|
||||
|
||||
// ------ 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 — switch to risk assessment view', async ({ page }) => {
|
||||
await goTo(page, `/sdk/iace/${project.id}/hazards`)
|
||||
// Click the "Risikobewertung" toggle
|
||||
await page.locator('button', { hasText: 'Risikobewertung' }).click()
|
||||
// Wait for the RiskAssessmentTable to render (fetches mitigations)
|
||||
await page.waitForTimeout(3000)
|
||||
await assertNoAppError(page)
|
||||
// Risk assessment table renders <select> 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-column layout 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 — add buttons per column', async ({ page }) => {
|
||||
await goTo(page, `/sdk/iace/${project.id}/mitigations`)
|
||||
const addButtons = page.locator('button', { hasText: '+ Hinzufuegen' })
|
||||
const count = await addButtons.count()
|
||||
expect(count).toBe(3)
|
||||
})
|
||||
|
||||
test('mitigations — "Massnahme hinzufuegen" button', async ({ page }) => {
|
||||
await goTo(page, `/sdk/iace/${project.id}/mitigations`)
|
||||
await expect(
|
||||
page.locator('button', { hasText: 'Massnahme 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 })
|
||||
})
|
||||
|
||||
// ------ 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 })
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user