- 실패 시나리오 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>
281 lines
9.1 KiB
JSON
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
|
|
}
|
|
}
|
|
}
|