Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
242 lines
6.9 KiB
TypeScript
242 lines
6.9 KiB
TypeScript
/**
|
|
* Playwright Test Fixtures for AI Compliance SDK
|
|
*
|
|
* These fixtures provide reusable setup for SDK E2E tests.
|
|
* Demo data is seeded via the API (same storage path as real data).
|
|
*/
|
|
|
|
import { test as base, expect, Page } from '@playwright/test'
|
|
|
|
// Selectors for SDK components
|
|
export const selectors = {
|
|
// Dashboard
|
|
dashboard: '[data-testid="sdk-dashboard"]',
|
|
loadDemoDataButton: '[data-testid="load-demo-data"]',
|
|
progressBar: '[data-testid="progress-bar"]',
|
|
|
|
// Sidebar
|
|
sidebar: '[data-testid="sdk-sidebar"]',
|
|
sidebarPhase1: '[data-testid="sidebar-phase-1"]',
|
|
sidebarPhase2: '[data-testid="sidebar-phase-2"]',
|
|
sidebarStep: (stepId: string) => `[data-testid="sidebar-step-${stepId}"]`,
|
|
|
|
// Command Bar
|
|
commandBar: '[data-testid="command-bar"]',
|
|
commandInput: '[data-testid="command-input"]',
|
|
commandSuggestion: (index: number) => `[data-testid="command-suggestion-${index}"]`,
|
|
|
|
// Navigation
|
|
prevButton: '[data-testid="prev-step-button"]',
|
|
nextButton: '[data-testid="next-step-button"]',
|
|
stepHeader: '[data-testid="step-header"]',
|
|
|
|
// Use Case Workshop
|
|
useCaseCard: (index: number) => `[data-testid="use-case-card-${index}"]`,
|
|
addUseCaseButton: '[data-testid="add-use-case-button"]',
|
|
useCaseForm: '[data-testid="use-case-form"]',
|
|
|
|
// Risk Matrix
|
|
riskCard: (riskId: string) => `[data-testid="risk-card-${riskId}"]`,
|
|
riskMatrix: '[data-testid="risk-matrix"]',
|
|
addRiskButton: '[data-testid="add-risk-button"]',
|
|
|
|
// Controls
|
|
controlCard: (controlId: string) => `[data-testid="control-card-${controlId}"]`,
|
|
controlsGrid: '[data-testid="controls-grid"]',
|
|
|
|
// Export
|
|
exportButton: '[data-testid="export-button"]',
|
|
exportPdfButton: '[data-testid="export-pdf-button"]',
|
|
exportZipButton: '[data-testid="export-zip-button"]',
|
|
}
|
|
|
|
// Type for SDK test fixtures
|
|
type SDKFixtures = {
|
|
sdkPage: Page
|
|
withDemoData: Page
|
|
commandBar: Page
|
|
}
|
|
|
|
/**
|
|
* Extended test with SDK-specific fixtures
|
|
*/
|
|
export const test = base.extend<SDKFixtures>({
|
|
// Basic SDK page (navigates to dashboard)
|
|
sdkPage: async ({ page }, use) => {
|
|
await page.goto('/sdk')
|
|
// Wait for page to be ready
|
|
await page.waitForLoadState('networkidle')
|
|
await use(page)
|
|
},
|
|
|
|
// SDK page with demo data loaded
|
|
withDemoData: async ({ page }, use) => {
|
|
// Navigate to SDK
|
|
await page.goto('/sdk')
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
// Seed demo data via API (same storage path as real data)
|
|
await page.evaluate(async () => {
|
|
const response = await fetch('/api/sdk/v1/demo/seed', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ tenantId: 'e2e-test-tenant' }),
|
|
})
|
|
if (!response.ok) {
|
|
// If endpoint doesn't exist, try seeding via localStorage
|
|
// This is a fallback for development
|
|
console.warn('Demo seed endpoint not available, using localStorage fallback')
|
|
}
|
|
})
|
|
|
|
// Reload page to pick up demo data
|
|
await page.reload()
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
await use(page)
|
|
|
|
// Cleanup: Clear demo data after test
|
|
await page.evaluate(async () => {
|
|
try {
|
|
await fetch('/api/sdk/v1/demo/clear', {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ tenantId: 'e2e-test-tenant' }),
|
|
})
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
})
|
|
},
|
|
|
|
// SDK page with command bar opened
|
|
commandBar: async ({ page }, use) => {
|
|
await page.goto('/sdk')
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
// Open command bar with keyboard shortcut
|
|
await page.keyboard.press('Meta+k')
|
|
|
|
// Wait for command bar to be visible
|
|
await page.waitForSelector(selectors.commandBar, { state: 'visible' })
|
|
|
|
await use(page)
|
|
},
|
|
})
|
|
|
|
// Re-export expect for convenience
|
|
export { expect }
|
|
|
|
/**
|
|
* Helper: Wait for SDK to initialize
|
|
*/
|
|
export async function waitForSDKInit(page: Page): Promise<void> {
|
|
await page.waitForLoadState('networkidle')
|
|
// Wait for React hydration
|
|
await page.waitForFunction(() => {
|
|
return document.querySelector('[data-testid="sdk-dashboard"]') !== null ||
|
|
document.querySelector('[data-testid="step-header"]') !== null
|
|
}, { timeout: 10000 })
|
|
}
|
|
|
|
/**
|
|
* Helper: Navigate to a specific SDK step
|
|
*/
|
|
export async function navigateToStep(page: Page, stepId: string): Promise<void> {
|
|
const stepUrl = getStepUrl(stepId)
|
|
await page.goto(stepUrl)
|
|
await waitForSDKInit(page)
|
|
}
|
|
|
|
/**
|
|
* Helper: Get URL for a step
|
|
*/
|
|
export function getStepUrl(stepId: string): string {
|
|
const stepUrls: Record<string, string> = {
|
|
'use-case-workshop': '/sdk/advisory-board',
|
|
'screening': '/sdk/screening',
|
|
'modules': '/sdk/modules',
|
|
'requirements': '/sdk/requirements',
|
|
'controls': '/sdk/controls',
|
|
'evidence': '/sdk/evidence',
|
|
'audit-checklist': '/sdk/audit-checklist',
|
|
'risks': '/sdk/risks',
|
|
'ai-act': '/sdk/ai-act',
|
|
'obligations': '/sdk/obligations',
|
|
'dsfa': '/sdk/dsfa',
|
|
'tom': '/sdk/tom',
|
|
'loeschfristen': '/sdk/loeschfristen',
|
|
'vvt': '/sdk/vvt',
|
|
'consent': '/sdk/consent',
|
|
'cookie-banner': '/sdk/cookie-banner',
|
|
'einwilligungen': '/sdk/einwilligungen',
|
|
'dsr': '/sdk/dsr',
|
|
'escalations': '/sdk/escalations',
|
|
}
|
|
return stepUrls[stepId] || '/sdk'
|
|
}
|
|
|
|
/**
|
|
* Helper: Use command bar to navigate
|
|
*/
|
|
export async function useCommandBarToNavigate(page: Page, searchTerm: string): Promise<void> {
|
|
// Open command bar
|
|
await page.keyboard.press('Meta+k')
|
|
await page.waitForSelector(selectors.commandBar, { state: 'visible' })
|
|
|
|
// Type search term
|
|
await page.fill(selectors.commandInput, searchTerm)
|
|
|
|
// Wait for suggestions
|
|
await page.waitForSelector(selectors.commandSuggestion(0), { timeout: 5000 })
|
|
|
|
// Click first suggestion
|
|
await page.click(selectors.commandSuggestion(0))
|
|
|
|
// Wait for navigation
|
|
await page.waitForLoadState('networkidle')
|
|
}
|
|
|
|
/**
|
|
* Helper: Seed demo data programmatically
|
|
*/
|
|
export async function seedDemoData(page: Page, tenantId: string = 'e2e-test'): Promise<boolean> {
|
|
const result = await page.evaluate(async (tid) => {
|
|
try {
|
|
const response = await fetch('/api/sdk/v1/demo/seed', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ tenantId: tid }),
|
|
})
|
|
return response.ok
|
|
} catch {
|
|
return false
|
|
}
|
|
}, tenantId)
|
|
|
|
if (result) {
|
|
await page.reload()
|
|
await waitForSDKInit(page)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Helper: Clear demo data
|
|
*/
|
|
export async function clearDemoData(page: Page, tenantId: string = 'e2e-test'): Promise<boolean> {
|
|
return await page.evaluate(async (tid) => {
|
|
try {
|
|
const response = await fetch('/api/sdk/v1/demo/clear', {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ tenantId: tid }),
|
|
})
|
|
return response.ok
|
|
} catch {
|
|
return false
|
|
}
|
|
}, tenantId)
|
|
}
|