Merge feat/zeroclaw-compliance-agent into main
Brings all compliance doc-check features: - 162 regex checks + 1874 Master Controls - LLM-agnostic agent with tool calling - Banner check (46 checks, 30 CMPs, stealth, Shadow DOM) - Impressum check (24 checks) - Deep consent verification (DataLayer, GCM, TCF) - CMP E2E tests (39 tests) - HTML email reports, FAQ, persistent history Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,45 @@
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
/**
|
||||
* Playwright config for testing against live Mac Mini instance.
|
||||
* No webServer — assumes https://macmini:3007 is already running.
|
||||
*
|
||||
* Usage: npx playwright test --config=e2e/playwright-live.config.ts
|
||||
*/
|
||||
|
||||
>>>>>>> feat/zeroclaw-compliance-agent
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './specs',
|
||||
<<<<<<< HEAD
|
||||
timeout: 30000,
|
||||
use: {
|
||||
baseURL: 'https://macmini:3007',
|
||||
ignoreHTTPSErrors: true,
|
||||
=======
|
||||
fullyParallel: true,
|
||||
retries: 0,
|
||||
workers: 3,
|
||||
reporter: [
|
||||
['html', { outputFolder: 'e2e/reports/html' }],
|
||||
['list'],
|
||||
],
|
||||
use: {
|
||||
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'https://macmini:3007',
|
||||
ignoreHTTPSErrors: true,
|
||||
screenshot: 'on',
|
||||
trace: 'on-first-retry',
|
||||
>>>>>>> feat/zeroclaw-compliance-agent
|
||||
},
|
||||
projects: [
|
||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||
],
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
outputDir: 'e2e/test-results',
|
||||
timeout: 20000,
|
||||
expect: { timeout: 5000 },
|
||||
// No webServer — we test against the live instance
|
||||
>>>>>>> feat/zeroclaw-compliance-agent
|
||||
})
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Document Generator E2E Test
|
||||
*
|
||||
* Prueft: Template-Library, Empfehlungen, Kategorie-Filter, Template-Auswahl
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
const BASE = process.env.PLAYWRIGHT_BASE_URL || 'https://macmini:3007'
|
||||
|
||||
test.describe('Document Generator', () => {
|
||||
test('Template Library loads with templates', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/document-generator`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Wait for templates to load
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// Check that template count is shown
|
||||
const body = await page.textContent('body')
|
||||
expect(body).toContain('Vorlagen gesamt')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/generator-library.png', fullPage: true })
|
||||
})
|
||||
|
||||
test('Category filter works', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/document-generator`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// Click on "Datenschutz" category if it exists
|
||||
const datenschutzButton = page.locator('button', { hasText: 'Datenschutz' })
|
||||
if (await datenschutzButton.isVisible()) {
|
||||
await datenschutzButton.click()
|
||||
await page.waitForTimeout(500)
|
||||
await page.screenshot({ path: 'e2e/test-results/generator-filter-datenschutz.png' })
|
||||
}
|
||||
|
||||
// Click "Alle" to reset
|
||||
const alleButton = page.locator('button', { hasText: 'Alle' })
|
||||
if (await alleButton.isVisible()) {
|
||||
await alleButton.click()
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
})
|
||||
|
||||
test('Recommendation section visible when scope is set', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/document-generator`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// Check for recommendation section (may or may not be visible depending on scope state)
|
||||
const recSection = page.locator('text=Empfohlene Dokumente')
|
||||
const isVisible = await recSection.isVisible().catch(() => false)
|
||||
|
||||
if (isVisible) {
|
||||
await page.screenshot({ path: 'e2e/test-results/generator-recommendations.png' })
|
||||
|
||||
// Check for Pflicht/Empfohlen sections
|
||||
const pflicht = page.locator('text=Pflicht')
|
||||
expect(await pflicht.isVisible()).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('New template categories are present', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/document-generator`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
const body = await page.textContent('body')
|
||||
|
||||
// Check that new categories exist
|
||||
const expectedCategories = ['TOM', 'Whistleblower', 'HR-Datenschutz', 'Drittlandtransfer']
|
||||
for (const cat of expectedCategories) {
|
||||
const button = page.locator('button', { hasText: cat })
|
||||
if (await button.isVisible()) {
|
||||
await button.click()
|
||||
await page.waitForTimeout(300)
|
||||
await page.screenshot({ path: `e2e/test-results/generator-category-${cat.toLowerCase().replace(/[^a-z]/g, '')}.png` })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* ISMS Asset Register E2E Test
|
||||
*
|
||||
* Prueft: Assets-Tab, CRUD, Filter, CSV-Export
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
const BASE = process.env.PLAYWRIGHT_BASE_URL || 'https://macmini:3007'
|
||||
|
||||
test.describe('ISMS — Asset Register', () => {
|
||||
test('ISMS page loads with Assets tab', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/isms`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Check that Assets tab exists
|
||||
const assetsTab = page.locator('button', { hasText: 'Assets' })
|
||||
expect(await assetsTab.isVisible()).toBeTruthy()
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/isms-overview.png' })
|
||||
})
|
||||
|
||||
test('Assets tab shows empty state', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/isms`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Click Assets tab
|
||||
const assetsTab = page.locator('button', { hasText: 'Assets' })
|
||||
await assetsTab.click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// Check for empty state or asset table
|
||||
const body = await page.textContent('body')
|
||||
const hasAssets = body?.includes('Gesamt') || false
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/isms-assets-tab.png' })
|
||||
expect(hasAssets).toBeTruthy()
|
||||
})
|
||||
|
||||
test('Add asset form is accessible', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/isms`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Click Assets tab
|
||||
const assetsTab = page.locator('button', { hasText: 'Assets' })
|
||||
await assetsTab.click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// Click "Asset hinzufuegen" button
|
||||
const addButton = page.locator('button', { hasText: 'Asset hinzufuegen' })
|
||||
if (await addButton.isVisible()) {
|
||||
await addButton.click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Check form fields are visible
|
||||
const body = await page.textContent('body')
|
||||
expect(body).toContain('Name')
|
||||
expect(body).toContain('Kategorie')
|
||||
expect(body).toContain('Schutzbedarf')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/isms-assets-form.png' })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Scope Profiling Test — Three Customer Profiles
|
||||
*
|
||||
* Legt drei fiktive Kundenprofile an die NICHT geloescht werden:
|
||||
* - TechStart GmbH (Startup, L1)
|
||||
* - MittelstandHandel AG (KMU, L2)
|
||||
* - FinanzKonzern SE (Enterprise, L4)
|
||||
*
|
||||
* WICHTIG: Testdaten werden NICHT aufgeraeumt (User will nachvollziehen).
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
const BASE = process.env.PLAYWRIGHT_BASE_URL || 'https://macmini:3007'
|
||||
|
||||
test.describe('Scope Profiling — Customer Profiles', () => {
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test('Szenario A: TechStart GmbH — Startup (L1)', async ({ page }) => {
|
||||
// Navigate to company profile
|
||||
await page.goto(`${BASE}/sdk/company-profile`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Take screenshot for documentation
|
||||
await page.screenshot({ path: 'e2e/test-results/profile-techstart-start.png' })
|
||||
|
||||
// Navigate to compliance scope
|
||||
await page.goto(`${BASE}/sdk/compliance-scope`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/scope-techstart.png' })
|
||||
|
||||
// Navigate to document generator to check recommendations
|
||||
await page.goto(`${BASE}/sdk/document-generator`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/generator-techstart.png' })
|
||||
|
||||
// Verify page loads without errors
|
||||
const body = await page.textContent('body')
|
||||
expect(body).not.toContain('Application error')
|
||||
})
|
||||
|
||||
test('Szenario B: MittelstandHandel AG — KMU (L2)', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/company-profile`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/profile-mittelstand-start.png' })
|
||||
|
||||
await page.goto(`${BASE}/sdk/compliance-scope`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/scope-mittelstand.png' })
|
||||
|
||||
// Check vendor transfers tab
|
||||
await page.goto(`${BASE}/sdk/vendor-compliance/transfers`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/transfers-mittelstand.png' })
|
||||
|
||||
// Check document generator
|
||||
await page.goto(`${BASE}/sdk/document-generator`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/generator-mittelstand.png' })
|
||||
|
||||
const body = await page.textContent('body')
|
||||
expect(body).not.toContain('Application error')
|
||||
})
|
||||
|
||||
test('Szenario C: FinanzKonzern SE — Enterprise (L4)', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/company-profile`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/profile-finanzkonzern-start.png' })
|
||||
|
||||
await page.goto(`${BASE}/sdk/compliance-scope`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/scope-finanzkonzern.png' })
|
||||
|
||||
// Check ISMS with assets
|
||||
await page.goto(`${BASE}/sdk/isms`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/isms-finanzkonzern.png' })
|
||||
|
||||
// Check whistleblower (Pflicht ab 50 MA)
|
||||
await page.goto(`${BASE}/sdk/whistleblower`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/whistleblower-finanzkonzern.png' })
|
||||
|
||||
// Check document generator
|
||||
await page.goto(`${BASE}/sdk/document-generator`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/generator-finanzkonzern.png' })
|
||||
|
||||
const body = await page.textContent('body')
|
||||
expect(body).not.toContain('Application error')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* SDK Module Reachability Test
|
||||
*
|
||||
* Prueft dass alle SDK-Module erreichbar sind (kein 404, kein Crash).
|
||||
* Schnellster Test — findet kaputte Seiten sofort.
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
const BASE = process.env.PLAYWRIGHT_BASE_URL || 'https://macmini:3007'
|
||||
|
||||
// All SDK module routes to test
|
||||
const SDK_ROUTES = [
|
||||
// Phase 1: Vorbereitung
|
||||
'/sdk/company-profile',
|
||||
'/sdk/compliance-scope',
|
||||
'/sdk/advisory-board',
|
||||
'/sdk/screening',
|
||||
'/sdk/source-policy',
|
||||
|
||||
// Phase 2: Analyse
|
||||
'/sdk/requirements',
|
||||
'/sdk/controls',
|
||||
'/sdk/evidence',
|
||||
'/sdk/risks',
|
||||
'/sdk/ai-act',
|
||||
'/sdk/audit-checklist',
|
||||
'/sdk/audit-report',
|
||||
|
||||
// Phase 3: Dokumentation
|
||||
'/sdk/obligations',
|
||||
'/sdk/dsfa',
|
||||
'/sdk/tom',
|
||||
'/sdk/loeschfristen',
|
||||
'/sdk/vvt',
|
||||
|
||||
// Phase 4: Rechtliche Texte
|
||||
'/sdk/einwilligungen',
|
||||
'/sdk/consent',
|
||||
'/sdk/cookie-banner',
|
||||
'/sdk/document-generator',
|
||||
'/sdk/workflow',
|
||||
|
||||
// Phase 5: Betrieb
|
||||
'/sdk/dsr',
|
||||
'/sdk/escalations',
|
||||
'/sdk/vendor-compliance',
|
||||
'/sdk/vendor-compliance/transfers',
|
||||
'/sdk/consent-management',
|
||||
'/sdk/email-templates',
|
||||
'/sdk/notfallplan',
|
||||
'/sdk/incidents',
|
||||
'/sdk/whistleblower',
|
||||
'/sdk/academy',
|
||||
'/sdk/training',
|
||||
'/sdk/control-library',
|
||||
|
||||
// Zusatzmodule
|
||||
'/sdk/isms',
|
||||
'/sdk/iace',
|
||||
'/sdk/agent',
|
||||
'/sdk/rag',
|
||||
'/sdk/quality',
|
||||
'/sdk/security-backlog',
|
||||
'/sdk/reporting',
|
||||
'/sdk/tom-generator',
|
||||
]
|
||||
|
||||
test.describe('SDK Module Reachability', () => {
|
||||
for (const route of SDK_ROUTES) {
|
||||
test(`${route} is reachable`, async ({ page }) => {
|
||||
const response = await page.goto(`${BASE}${route}`, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
timeout: 15000,
|
||||
})
|
||||
|
||||
// Page should load successfully (not 404 or 500)
|
||||
expect(response?.status()).toBeLessThan(400)
|
||||
|
||||
// Wait for client-side hydration
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// Check no visible 404 page or application error (not RSC payload)
|
||||
const has404Page = await page.locator('h1').filter({ hasText: '404' }).count()
|
||||
const hasAppError = await page.getByText('Application error').count()
|
||||
expect(has404Page).toBe(0)
|
||||
expect(hasAppError).toBe(0)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Vendor Compliance — Transfers Tab E2E Test
|
||||
*
|
||||
* Prueft: Drittlandtransfer-Tab, Erklaerungen, Adequacy-Liste
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
const BASE = process.env.PLAYWRIGHT_BASE_URL || 'https://macmini:3007'
|
||||
|
||||
test.describe('Vendor Compliance — Transfers', () => {
|
||||
test('Transfers tab is accessible', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/vendor-compliance/transfers`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Page should show transfer heading
|
||||
const body = await page.textContent('body')
|
||||
expect(body).toContain('Drittlandtransfers')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/transfers-tab.png', fullPage: true })
|
||||
})
|
||||
|
||||
test('Explanation section is visible', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/vendor-compliance/transfers`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Check for the three explanation cards
|
||||
const body = await page.textContent('body')
|
||||
expect(body).toContain('Was muss ich tun')
|
||||
expect(body).toContain('Angemessenheitsbeschluss')
|
||||
expect(body).toContain('DPF-Zertifizierung')
|
||||
expect(body).toContain('SCC + TIA')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/transfers-explanations.png' })
|
||||
})
|
||||
|
||||
test('Adequacy countries list is expandable', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/vendor-compliance/transfers`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// Click on the details summary to expand
|
||||
const summary = page.locator('summary', { hasText: 'Angemessenheitsbeschluss' })
|
||||
if (await summary.isVisible()) {
|
||||
await summary.click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Check for country names
|
||||
const body = await page.textContent('body')
|
||||
expect(body).toContain('Schweiz')
|
||||
expect(body).toContain('Japan')
|
||||
expect(body).toContain('Vereinigte Staaten')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/transfers-adequacy-list.png', fullPage: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('Schrems II info box is present', async ({ page }) => {
|
||||
await page.goto(`${BASE}/sdk/vendor-compliance/transfers`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
const body = await page.textContent('body')
|
||||
expect(body).toContain('Schrems II')
|
||||
|
||||
await page.screenshot({ path: 'e2e/test-results/transfers-schrems.png' })
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user