/** * 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({ // 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 { 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 { 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 = { '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 { // 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 { 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 { 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) }