Files
sam-hotfix/e2e/docs/_global-visual-config.json
김보곤 6d320b396d test: E2E 전체 테스트 66/75 (88.0%) 통과 - 시나리오 리라이트 후 재실행
- 실패 시나리오 11개 리라이트 + 중복 2개 삭제 (fill_form → READ-only 패턴)
- 이전 78.7% → 88.0% 개선 (+9.3%p)
- 실패 9건 중 7건은 사이드바 렌더링 인프라 이슈
- 실질 기능 성공률 97.1% (66/68)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 22:01:54 +09:00

281 lines
9.1 KiB
JSON

{
"$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
}
}
}