{ "$schema": "E2E Test Data Management Configuration", "version": "1.0.0", "description": "테스트 데이터 생성, 관리, 정리를 위한 전역 설정", "lastUpdated": "2026-01-31", "namingConvention": { "prefix": "E2E_TEST_", "format": "{prefix}{entity}_{timestamp}", "timestampFormat": "YYYYMMDD_HHmmss", "examples": [ "E2E_TEST_게시글_20260131_110000", "E2E_TEST_거래처_20260131_110000", "E2E_TEST_사원_20260131_110000" ] }, "dataTemplates": { "freeBoard": { "entity": "게시글", "fields": { "title": "E2E_TEST_게시글_{timestamp}", "content": "E2E 자동화 테스트용 게시글입니다.\n생성시간: {datetime}\n테스트 목적: CRUD 흐름 검증" }, "updateFields": { "title": "E2E_TEST_게시글_수정됨_{timestamp}" }, "identifierField": "title", "searchableBy": ["title"] }, "vendor": { "entity": "거래처", "fields": { "name": "E2E_TEST_거래처_{timestamp}", "businessNumber": "123-45-{random6}", "representative": "테스트담당자", "phone": "02-1234-5678", "address": "서울시 테스트구 자동화로 123" }, "updateFields": { "representative": "수정된담당자", "phone": "02-9999-8888" }, "identifierField": "name", "searchableBy": ["name", "businessNumber"] }, "employee": { "entity": "사원", "fields": { "name": "E2E_TEST_사원_{timestamp}", "employeeNumber": "E2E{random4}", "department": "테스트부서", "position": "테스터", "email": "e2e_test_{timestamp}@test.com" }, "updateFields": { "department": "수정부서", "position": "시니어테스터" }, "identifierField": "name", "searchableBy": ["name", "employeeNumber"] }, "deposit": { "entity": "입금", "fields": { "description": "E2E_TEST_입금_{timestamp}", "amount": 100000, "depositor": "테스트입금자", "accountNumber": "110-123-456789" }, "updateFields": { "amount": 200000, "description": "E2E_TEST_입금_수정됨_{timestamp}" }, "identifierField": "description", "searchableBy": ["description", "depositor"] }, "card": { "entity": "카드", "fields": { "cardName": "E2E_TEST_카드_{timestamp}", "cardNumber": "1234-5678-{random4}-{random4}", "cardCompany": "테스트카드사", "holder": "테스트소지자" }, "updateFields": { "holder": "수정된소지자" }, "identifierField": "cardName", "searchableBy": ["cardName", "cardNumber"] } }, "scripts": { "generateTimestamp": "(() => { const now = new Date(); const pad = n => n.toString().padStart(2, '0'); return `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`; })()", "generateRandom": "(length) => Math.random().toString().substring(2, 2 + length)", "generateTestData": "(template, timestamp) => { const data = {}; Object.entries(template.fields).forEach(([key, value]) => { if (typeof value === 'string') { data[key] = value.replace('{timestamp}', timestamp).replace('{datetime}', new Date().toLocaleString('ko-KR')).replace('{random4}', Math.random().toString().substring(2, 6)).replace('{random6}', Math.random().toString().substring(2, 8)); } else { data[key] = value; } }); return data; }", "findTestData": "(prefix = 'E2E_TEST_') => { const rows = document.querySelectorAll('table tbody tr, [class*=\"table\"] [class*=\"row\"]'); return Array.from(rows).filter(row => row.innerText?.includes(prefix)).map(row => ({ text: row.innerText?.substring(0, 100), element: row })); }", "cleanupTestData": "(async (prefix = 'E2E_TEST_') => { const testRows = []; const rows = document.querySelectorAll('table tbody tr'); rows.forEach(row => { if (row.innerText?.includes(prefix)) { testRows.push(row); } }); return { found: testRows.length, message: `Found ${testRows.length} test data rows to cleanup` }; })", "verifyDataCreated": "(identifierValue) => { const pageText = document.body.innerText; return pageText.includes(identifierValue); }", "verifyDataDeleted": "(identifierValue) => { const pageText = document.body.innerText; return !pageText.includes(identifierValue); }" }, "lifecycle": { "beforeTest": { "description": "테스트 시작 전 실행", "actions": [ "generateTimestamp", "prepareTestData" ] }, "afterCreate": { "description": "데이터 생성 후 실행", "actions": [ "verifyDataCreated", "storeCreatedId" ] }, "afterUpdate": { "description": "데이터 수정 후 실행", "actions": [ "verifyDataUpdated" ] }, "afterDelete": { "description": "데이터 삭제 후 실행", "actions": [ "verifyDataDeleted" ] }, "afterTest": { "description": "테스트 종료 후 실행", "actions": [ "cleanupOrphanedData", "generateReport" ] }, "onError": { "description": "에러 발생 시 실행", "actions": [ "attemptCleanup", "logError" ] } }, "cleanup": { "strategy": "immediate", "strategies": { "immediate": "테스트 완료 즉시 삭제 (권장)", "batch": "테스트 세션 종료 시 일괄 삭제", "scheduled": "정기적으로 삭제 (위험)" }, "orphanedDataHandling": { "description": "이전 테스트에서 정리되지 않은 데이터 처리", "maxAge": "24h", "autoCleanup": true, "pattern": "E2E_TEST_*" }, "protectedData": { "description": "삭제하면 안되는 데이터 패턴", "patterns": [ "가우스*", "실제데이터*", "운영*" ] } }, "validation": { "rules": { "uniqueIdentifier": { "description": "테스트 데이터는 고유 식별자를 가져야 함", "check": "timestamp 포함 여부" }, "noProductionData": { "description": "운영 데이터와 구분 가능해야 함", "check": "E2E_TEST_ 접두사 필수" }, "cleanupable": { "description": "쉽게 식별하고 삭제 가능해야 함", "check": "검색 가능한 필드 존재" } } }, "isolation": { "description": "테스트 데이터 격리 전략", "levels": { "session": { "description": "세션별 고유 타임스탬프 사용", "implementation": "timestamp를 세션 시작 시 한번만 생성" }, "scenario": { "description": "시나리오별 고유 데이터", "implementation": "시나리오 ID + timestamp 조합" }, "step": { "description": "스텝별 고유 데이터", "implementation": "스텝 번호 + timestamp 조합" } }, "currentLevel": "session" }, "dependencies": { "description": "테스트 데이터 간 의존성 관리", "examples": { "approval": { "requires": ["employee", "document"], "description": "결재 테스트는 사원과 문서 데이터 필요" }, "payment": { "requires": ["vendor", "invoice"], "description": "지급 테스트는 거래처와 청구서 필요" } }, "resolutionOrder": "topological", "autoCreate": true }, "reporting": { "trackCreated": true, "trackDeleted": true, "trackOrphaned": true, "includeInTestReport": true, "format": { "summary": { "created": "생성된 테스트 데이터 수", "deleted": "삭제된 테스트 데이터 수", "orphaned": "정리되지 않은 데이터 수" }, "detail": { "entity": "엔티티 유형", "identifier": "식별자", "createdAt": "생성 시간", "deletedAt": "삭제 시간", "status": "상태 (created/deleted/orphaned)" } } }, "errorRecovery": { "onCreateFailure": { "action": "skip", "log": true, "continueTest": false }, "onDeleteFailure": { "action": "retry", "maxRetries": 2, "log": true, "continueTest": true }, "onOrphanedData": { "action": "warn", "autoCleanup": false, "log": true } } }