diff --git a/_global-parallel-config.json b/_global-parallel-config.json new file mode 100644 index 0000000..e6b3858 --- /dev/null +++ b/_global-parallel-config.json @@ -0,0 +1,317 @@ +{ + "$schema": "E2E Parallel Execution Configuration", + "version": "1.0.0", + "description": "테스트 시나리오 병렬 실행 및 최적화 설정", + "lastUpdated": "2026-01-31", + + "parallel": { + "enabled": true, + "maxWorkers": 4, + "defaultConcurrency": 2, + "isolatedBrowsers": true, + "sharedLogin": false + }, + + "workerConfig": { + "maxWorkers": { + "description": "최대 동시 실행 워커 수", + "default": 4, + "min": 1, + "max": 8, + "autoDetect": true, + "cpuMultiplier": 0.5 + }, + "workerTimeout": { + "description": "워커별 최대 실행 시간", + "default": 300000, + "perScenario": 60000 + }, + "workerRetry": { + "enabled": true, + "maxRetries": 1 + } + }, + + "executionModes": { + "sequential": { + "description": "순차 실행 (기본)", + "concurrency": 1, + "useCase": "디버깅, 의존성 있는 테스트" + }, + "parallel": { + "description": "병렬 실행", + "concurrency": 4, + "useCase": "독립적인 테스트, 빠른 실행" + }, + "grouped": { + "description": "그룹별 병렬 실행", + "groupConcurrency": 2, + "withinGroupSequential": true, + "useCase": "관련 테스트 그룹화" + }, + "prioritized": { + "description": "우선순위 기반 실행", + "highPriorityFirst": true, + "concurrency": 3, + "useCase": "중요 테스트 먼저" + } + }, + + "grouping": { + "byModule": { + "description": "모듈별 그룹화", + "groups": { + "auth": ["login", "logout", "session"], + "hr": ["attendance-*", "vacation-*", "employee-*"], + "accounting": ["vendor-*", "deposit-*", "card-*"], + "approval": ["approval-*", "draft-*", "reference-*"], + "boards": ["free-board", "notice-board", "popup-*"] + } + }, + "byPriority": { + "description": "우선순위별 그룹화", + "groups": { + "critical": ["login", "dashboard", "approval-box"], + "high": ["vendor-management", "attendance-*"], + "medium": ["*-board", "popup-*"], + "low": ["settings-*", "help-*"] + } + }, + "byDuration": { + "description": "실행 시간별 그룹화", + "groups": { + "fast": { "maxDuration": 30000, "concurrency": 4 }, + "medium": { "maxDuration": 60000, "concurrency": 3 }, + "slow": { "maxDuration": 120000, "concurrency": 2 } + } + } + }, + + "dependencies": { + "description": "테스트 간 의존성 관리", + "rules": [ + { + "scenario": "approval-box", + "dependsOn": ["login"], + "reason": "로그인 필요" + }, + { + "scenario": "crud-*-delete", + "dependsOn": ["crud-*-create"], + "reason": "삭제 전 생성 필요" + } + ], + "resolutionStrategy": "topological", + "blockOnFailure": true + }, + + "isolation": { + "browserContext": { + "description": "브라우저 컨텍스트 격리", + "newContextPerWorker": true, + "newContextPerScenario": false, + "sharedCookies": false, + "sharedStorage": false + }, + "testData": { + "description": "테스트 데이터 격리", + "prefixWithWorkerId": true, + "format": "E2E_TEST_W{workerId}_{entity}_{timestamp}" + }, + "ports": { + "description": "포트 격리 (로컬 서버)", + "basePort": 3000, + "portPerWorker": true + } + }, + + "loadBalancing": { + "strategy": "roundRobin", + "strategies": { + "roundRobin": { + "description": "순환 분배", + "algorithm": "각 워커에 순서대로 할당" + }, + "leastBusy": { + "description": "최소 부하 분배", + "algorithm": "가장 적은 작업 가진 워커에 할당" + }, + "durationBased": { + "description": "실행 시간 기반", + "algorithm": "예상 실행 시간으로 균등 분배" + }, + "adaptive": { + "description": "적응형", + "algorithm": "실시간 성능 기반 동적 조절" + } + } + }, + + "resourceManagement": { + "memory": { + "maxPerWorker": "512MB", + "warningThreshold": "400MB", + "killOnExceed": true + }, + "cpu": { + "maxPerWorker": 25, + "throttleOnHigh": true + }, + "network": { + "rateLimit": false, + "maxConcurrentRequests": 10 + } + }, + + "scheduling": { + "order": "dependency-aware", + "orders": { + "fifo": "선입선출", + "priority": "우선순위순", + "duration": "짧은 것 먼저", + "dependency-aware": "의존성 고려" + }, + "batchSize": 10, + "batchDelay": 1000 + }, + + "failureHandling": { + "onWorkerFailure": { + "action": "reassign", + "maxReassigns": 2, + "reassignDelay": 5000 + }, + "onScenarioFailure": { + "continueOthers": true, + "isolateFailedWorker": false + }, + "failFast": { + "enabled": false, + "threshold": 50, + "description": "50% 이상 실패 시 중단" + } + }, + + "scripts": { + "createWorkerPool": "(function(maxWorkers) { const pool = { workers: [], available: [], busy: [] }; for (let i = 0; i < maxWorkers; i++) { pool.workers.push({ id: i, status: 'idle', currentTask: null, completedTasks: 0 }); pool.available.push(i); } return pool; })", + + "assignTask": "(function(pool, task) { if (pool.available.length === 0) return null; const workerId = pool.available.shift(); const worker = pool.workers[workerId]; worker.status = 'busy'; worker.currentTask = task; pool.busy.push(workerId); return workerId; })", + + "releaseWorker": "(function(pool, workerId) { const worker = pool.workers[workerId]; worker.status = 'idle'; worker.currentTask = null; worker.completedTasks++; pool.busy = pool.busy.filter(id => id !== workerId); pool.available.push(workerId); })", + + "getPoolStatus": "(function(pool) { return { total: pool.workers.length, available: pool.available.length, busy: pool.busy.length, tasks: pool.workers.map(w => ({ id: w.id, status: w.status, completed: w.completedTasks })) }; })", + + "partitionScenarios": "(function(scenarios, workerCount) { const partitions = Array.from({ length: workerCount }, () => []); scenarios.forEach((scenario, index) => { partitions[index % workerCount].push(scenario); }); return partitions; })", + + "sortByDependency": "(function(scenarios, dependencies) { const graph = new Map(); const inDegree = new Map(); scenarios.forEach(s => { graph.set(s, []); inDegree.set(s, 0); }); dependencies.forEach(dep => { if (graph.has(dep.from) && graph.has(dep.to)) { graph.get(dep.from).push(dep.to); inDegree.set(dep.to, inDegree.get(dep.to) + 1); } }); const queue = scenarios.filter(s => inDegree.get(s) === 0); const result = []; while (queue.length > 0) { const current = queue.shift(); result.push(current); graph.get(current).forEach(next => { inDegree.set(next, inDegree.get(next) - 1); if (inDegree.get(next) === 0) queue.push(next); }); } return result; })" + }, + + "reporting": { + "aggregateResults": true, + "perWorkerReport": true, + "timeline": true, + "format": { + "summary": { + "totalScenarios": "총 시나리오", + "totalWorkers": "사용 워커", + "totalDuration": "총 실행 시간", + "parallelEfficiency": "병렬 효율" + }, + "perWorker": { + "workerId": "워커 ID", + "scenarios": "실행 시나리오", + "passed": "성공", + "failed": "실패", + "duration": "소요 시간" + } + }, + "template": { + "header": "## ⚡ 병렬 실행 결과", + "sections": { + "summary": "### 실행 요약", + "timeline": "### 타임라인", + "workers": "### 워커별 결과" + } + } + }, + + "optimization": { + "autoTuning": { + "enabled": true, + "adjustWorkersOnLoad": true, + "minWorkers": 1, + "maxWorkers": 8 + }, + "caching": { + "shareAuthTokens": false, + "shareStaticAssets": true, + "cacheTestData": false + }, + "warmup": { + "enabled": true, + "preloadBrowsers": true, + "preloadPages": ["login"] + } + }, + + "monitoring": { + "realtime": true, + "metrics": [ + "activeWorkers", + "queuedTasks", + "completedTasks", + "failedTasks", + "averageTaskDuration", + "cpuUsage", + "memoryUsage" + ], + "alertThresholds": { + "queueBacklog": 20, + "workerIdleTime": 30000, + "memoryUsage": 80 + } + }, + + "integration": { + "withRetry": { + "retryFailedOnDifferentWorker": true, + "maxWorkerRetries": 1 + }, + "withReporting": { + "consolidateReports": true, + "generateTimeline": true + }, + "withCI": { + "supportSharding": true, + "shardIndex": "auto", + "totalShards": "auto" + } + }, + + "presets": { + "quick": { + "description": "빠른 실행 (4 워커)", + "maxWorkers": 4, + "timeout": 180000, + "failFast": true + }, + "thorough": { + "description": "철저한 실행 (2 워커)", + "maxWorkers": 2, + "timeout": 600000, + "failFast": false + }, + "ci": { + "description": "CI 환경 (CPU 기반)", + "maxWorkers": "auto", + "timeout": 300000, + "headless": true + }, + "debug": { + "description": "디버그 (1 워커)", + "maxWorkers": 1, + "timeout": 600000, + "verbose": true + } + } +}