{ "$schema": "E2E Visual Regression Configuration", "version": "1.0.0", "description": "스크린샷 기반 시각적 회귀 테스트 설정", "lastUpdated": "2026-01-31", "screenshot": { "enabled": true, "baseDir": "e2e/results/screenshots", "baselineDir": "e2e/results/screenshots/baseline", "currentDir": "e2e/results/screenshots/current", "diffDir": "e2e/results/screenshots/diff", "format": "png", "quality": 90, "fullPage": false, "defaultViewport": { "width": 1920, "height": 1080 } }, "naming": { "format": "{scenario}_{page}_{viewport}_{timestamp}", "baselineFormat": "{scenario}_{page}_{viewport}_baseline", "examples": [ "dashboard_main_1920x1080_20260131_113000.png", "dashboard_main_1920x1080_baseline.png" ] }, "viewports": { "desktop": { "width": 1920, "height": 1080, "label": "Desktop HD" }, "laptop": { "width": 1366, "height": 768, "label": "Laptop" }, "tablet": { "width": 768, "height": 1024, "label": "Tablet Portrait" }, "tabletLandscape": { "width": 1024, "height": 768, "label": "Tablet Landscape" }, "mobile": { "width": 375, "height": 812, "label": "Mobile (iPhone X)" }, "mobileLarge": { "width": 414, "height": 896, "label": "Mobile Large" } }, "comparison": { "enabled": true, "threshold": 0.1, "thresholds": { "strict": 0.01, "normal": 0.1, "relaxed": 0.5 }, "ignoreAntialiasing": true, "ignoreColors": false, "ignoreDynamicContent": true, "algorithm": "pixelmatch" }, "dynamicContent": { "description": "동적으로 변하는 콘텐츠 처리", "masks": [ { "name": "timestamp", "selector": "[class*='time'], [class*='date'], time", "action": "mask" }, { "name": "avatar", "selector": "[class*='avatar'], .profile-image", "action": "mask" }, { "name": "notification-badge", "selector": "[class*='badge'], .notification-count", "action": "mask" }, { "name": "chart", "selector": "canvas, svg[class*='chart']", "action": "mask" }, { "name": "ads", "selector": "[class*='ad'], [class*='banner']", "action": "hide" } ], "hideSelectors": [ ".loading", ".spinner", "[class*='skeleton']", ".cursor-blink" ], "waitForSelectors": [ "main", "[class*='content']", "table tbody" ] }, "capturePoints": { "description": "스크린샷 캡처 시점", "triggers": [ { "name": "pageLoad", "description": "페이지 로드 완료 후", "delay": 1000 }, { "name": "afterAction", "description": "주요 액션 후", "delay": 500 }, { "name": "modalOpen", "description": "모달 열림 후", "delay": 300 }, { "name": "formFilled", "description": "폼 입력 완료 후", "delay": 200 }, { "name": "error", "description": "에러 발생 시", "delay": 0 } ] }, "scripts": { "prepareForScreenshot": "(async function() { await new Promise(r => setTimeout(r, 500)); const hideElements = document.querySelectorAll('.loading, .spinner, [class*=\"skeleton\"], .cursor-blink, [class*=\"toast\"]'); hideElements.forEach(el => el.style.visibility = 'hidden'); const waitForImages = Array.from(document.images).filter(img => !img.complete); await Promise.all(waitForImages.map(img => new Promise(r => { img.onload = r; img.onerror = r; setTimeout(r, 3000); }))); return { hidden: hideElements.length, imagesLoaded: document.images.length }; })()", "maskDynamicContent": "(function() { const masks = [ { selector: '[class*=\"time\"], [class*=\"date\"], time', color: '#888' }, { selector: '[class*=\"avatar\"], .profile-image', color: '#ccc' }, { selector: '[class*=\"badge\"]', color: '#666' } ]; let maskedCount = 0; masks.forEach(m => { document.querySelectorAll(m.selector).forEach(el => { el.style.backgroundColor = m.color; el.style.color = m.color; maskedCount++; }); }); return { maskedElements: maskedCount }; })()", "getPageState": "(function() { return { url: window.location.href, title: document.title, scrollY: window.scrollY, viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, bodyHeight: document.body.scrollHeight, hasModal: !!document.querySelector('[role=\"dialog\"], [class*=\"modal\"]:not([style*=\"display: none\"])'), loadingElements: document.querySelectorAll('.loading, .spinner, [class*=\"skeleton\"]').length }; })()", "waitForStableState": "(async function(timeout = 5000) { const start = Date.now(); let lastHeight = 0; let stableCount = 0; while (Date.now() - start < timeout) { const currentHeight = document.body.scrollHeight; const loadingCount = document.querySelectorAll('.loading, .spinner').length; if (currentHeight === lastHeight && loadingCount === 0) { stableCount++; if (stableCount >= 3) return { stable: true, duration: Date.now() - start }; } else { stableCount = 0; } lastHeight = currentHeight; await new Promise(r => setTimeout(r, 200)); } return { stable: false, duration: timeout }; })()", "highlightDifferences": "(function(diffAreas) { diffAreas.forEach(area => { const highlight = document.createElement('div'); highlight.style.cssText = `position: absolute; left: ${area.x}px; top: ${area.y}px; width: ${area.width}px; height: ${area.height}px; border: 3px solid red; background: rgba(255,0,0,0.2); pointer-events: none; z-index: 99999;`; document.body.appendChild(highlight); }); })", "captureElementScreenshot": "(async function(selector) { const element = document.querySelector(selector); if (!element) return { error: 'Element not found', selector }; const rect = element.getBoundingClientRect(); return { selector, x: rect.x, y: rect.y, width: rect.width, height: rect.height, visible: rect.width > 0 && rect.height > 0 }; })" }, "pages": { "description": "페이지별 스크린샷 설정", "dashboard": { "url": "/dashboard", "captures": ["full", "sidebar", "main-content"], "masks": ["chart", "notification-badge", "timestamp"], "threshold": 0.1 }, "login": { "url": "/login", "captures": ["full", "login-form"], "masks": [], "threshold": 0.05 }, "list": { "url": "/**/list", "captures": ["full", "table", "pagination"], "masks": ["timestamp", "avatar"], "threshold": 0.1 }, "form": { "url": "/**?mode=new", "captures": ["full", "form"], "masks": [], "threshold": 0.05 } }, "reporting": { "generateReport": true, "reportFormat": "markdown", "includeImages": true, "includeDiff": true, "template": { "header": "## 📸 Visual Regression 테스트 결과", "sections": { "summary": "### 요약", "passed": "### ✅ 일치", "failed": "### ❌ 차이 감지", "new": "### 🆕 신규 페이지" } } }, "baseline": { "autoUpdate": false, "updateOnPass": false, "reviewRequired": true, "storage": { "type": "local", "path": "e2e/results/screenshots/baseline" } }, "workflow": { "steps": [ { "order": 1, "name": "prepare", "description": "스크린샷 준비", "actions": ["waitForStableState", "prepareForScreenshot", "maskDynamicContent"] }, { "order": 2, "name": "capture", "description": "스크린샷 캡처", "actions": ["takeScreenshot", "saveToCurrentDir"] }, { "order": 3, "name": "compare", "description": "베이스라인과 비교", "actions": ["loadBaseline", "compareImages", "calculateDiff"] }, { "order": 4, "name": "report", "description": "결과 리포트", "actions": ["generateDiffImage", "updateReport"] } ] }, "thresholdLevels": { "critical": { "value": 0.01, "description": "로그인, 결제 등 핵심 페이지", "action": "fail" }, "high": { "value": 0.05, "description": "폼, 상세 페이지", "action": "warn" }, "medium": { "value": 0.1, "description": "목록, 대시보드", "action": "warn" }, "low": { "value": 0.2, "description": "동적 콘텐츠가 많은 페이지", "action": "info" } }, "errorHandling": { "onCaptureFailure": { "action": "retry", "maxRetries": 2, "continueTest": true }, "onCompareFailure": { "action": "createNew", "log": true, "continueTest": true }, "onBaselineNotFound": { "action": "createBaseline", "log": true, "continueTest": true } }, "integration": { "withCRUD": { "captureAfterCreate": true, "captureAfterUpdate": true, "captureBeforeDelete": true }, "withPerformance": { "captureOnSlowLoad": true, "slowLoadThreshold": 3000 }, "withAPI": { "captureOnError": true, "captureOn500": true } } }