{ "enabled": true, "id": "sales-management", "name": "매출관리 테스트", "screenshotPolicy": { "onErrorOnly": true, "captureOn": [ "error", "fail", "timeout", "404", "500", "blocked" ] }, "description": "회계관리 > 매출관리 메뉴의 매출등록, 계정과목 저장, 품목 동적 추가, 자동계산 로직 테스트", "baseUrl": "https://dev.codebridge-x.com", "menuNavigation": { "level1": "회계관리", "level2": "매출관리", "expectedUrl": "/ko/accounting/sales", "searchWithinParent": true, "closeOtherMenus": true }, "navigation": { "targetUrl": "/accounting/sales", "urlPattern": "/accounting/sales|/ko/accounting/sales", "menuHints": [ "매출관리", "매출", "회계관리" ] }, "menuNavigationEnhanced": { "strategy": "scroll-and-search", "sidebarSelector": ".sidebar-scroll, [class*='sidebar'], nav[class*='menu']", "scrollConfig": { "scrollStep": 200, "maxScrollAttempts": 10, "scrollDelay": 300 }, "level1": { "text": "회계관리", "selectors": [ "//span[contains(text(),'회계관리')]", "//div[contains(@class,'menu')]//span[text()='회계관리']", "[data-menu='accounting']" ] }, "level2": { "text": "매출관리", "selectors": [ "//a[contains(text(),'매출관리')]", "//span[contains(text(),'매출관리')]", "[href*='/accounting/sales']" ] }, "fallbackUrl": "/ko/accounting/sales" }, "auth": { "username": "TestUser5", "password": "password123!" }, "steps": [ { "id": 0, "name": "사이드바 메뉴 전체 펼치기", "description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비", "actions": [ { "type": "evaluate", "script": "document.querySelector('.sidebar-scroll')?.scrollTo({top:0,behavior:'instant'})" }, { "type": "wait", "duration": 300 }, { "type": "evaluate", "script": "Array.from(document.querySelectorAll('button')).find(b => b.innerText?.includes('모두 펼치기'))?.click()" }, { "type": "wait", "duration": 2000 } ], "expected": "사이드바 전체 메뉴가 펼쳐짐" }, { "id": 1, "name": "로그인", "action": "click_if_exists", "target": "form, [role=\"dialog\"], .modal", "expected": "로그인 성공 후 메인 페이지 이동" }, { "id": 2, "name": "2단계 메뉴 진입: 회계관리 > 매출관리", "description": "스크롤하며 회계관리 메뉴를 찾아 클릭 후 매출관리 진입", "navigationPattern": "scrollAndFind", "actions": [ { "type": "scrollAndFind", "description": "사이드바 스크롤하며 회계관리 메뉴 찾기", "scrollContainer": ".sidebar-scroll, [class*='sidebar']", "targetText": "회계관리", "scrollStep": 200, "maxAttempts": 10 }, { "type": "click_if_exists", "target": "회계관리", "selectors": [ "//span[contains(text(),'회계관리')]", "//div[contains(@class,'menu')]//span[text()='회계관리']" ] }, { "type": "wait", "duration": 500 }, { "type": "scrollAndFind", "description": "확장된 서브메뉴에서 매출관리 찾기", "scrollContainer": ".sidebar-scroll, [class*='sidebar']", "targetText": "매출관리", "scrollStep": 100, "maxAttempts": 5 }, { "type": "click_if_exists", "target": "매출관리", "selectors": [ "//a[contains(text(),'매출관리')]", "//span[contains(text(),'매출관리')]", "[href*='/accounting/sales']" ] }, { "type": "wait", "target": "페이지 로드 완료" } ], "fallback": { "type": "navigate", "url": "/ko/accounting/sales" }, "expected": { "url": "/ko/accounting/sales", "pageTitle": "매출관리", "elements": [ "매출 등록 버튼", "테이블" ] } }, { "id": 3, "name": "필수 검증 #5: 목업 페이지 감지", "action": "verify_not_mockup", "checks": [ "입력 필드 존재 (검색창, 날짜 선택)", "동작하는 버튼 존재 (매출 등록, 엑셀 다운로드)", "테이블 데이터 표시", "API 호출 확인" ], "expected": "정상 페이지 (목업 아님)" }, { "id": 4, "name": "목록 페이지 - 테이블 구조 확인", "action": "verify_table", "checks": [ "체크박스 컬럼", "No. 컬럼", "매출번호 컬럼", "거래처명 컬럼", "매출일 컬럼", "매출유형 컬럼", "공급가액 컬럼", "부가세 컬럼", "합계금액 컬럼", "계산서 컬럼", "명세서 컬럼" ], "expected": "테이블 컬럼 구조 정상 표시" }, { "id": 5, "name": "계정과목명 드롭박스 확인", "action": "verify_elements", "checks": [ "계정과목명 라벨 존재", "드롭박스(Select) 존재", "저장 버튼 존재" ], "expected": "계정과목 선택 UI 정상 표시" }, { "id": 6, "name": "계정과목명 드롭박스 옵션 확인", "action": "click_if_exists", "target": "accountSubject", "checks": [ "미설정 옵션", "제품매출 옵션", "상품매출 옵션", "기타매출 옵션" ], "expected": "계정과목 옵션 목록 표시" }, { "id": 7, "name": "체크박스 선택 (계정과목 저장용)", "action": "click_if_exists", "target": "first_row", "expected": "첫 번째 행 체크박스 선택됨" }, { "id": 8, "name": "계정과목 변경 - 제품매출 선택", "action": "click_if_exists", "target": "accountSubject", "value": "product", "expected": "계정과목이 '제품매출'로 변경됨" }, { "id": 9, "name": "필수 검증 #2: 계정과목 저장 버튼 클릭", "action": "click_if_exists", "target": "저장", "checks": [ "확인 다이얼로그 표시", "선택된 항목 수 표시", "계정과목명 표시" ], "expected": "저장 확인 다이얼로그 표시" }, { "id": 10, "name": "저장 확인 다이얼로그 - 확인 클릭", "action": "click_if_exists", "checks": [ "API 호출 확인 (PUT /api/v1/sales/batch-update-account)", "성공 토스트 메시지", "URL 유지 확인" ], "expected": "계정과목 저장 성공" }, { "id": "10-1", "name": "⚠️ 필수 검증: 계정과목명 변경 데이터 반영 확인", "action": "verify_data_update", "target": "first_row", "checks": [ "선택한 행의 매출유형 컬럼 값 확인", "변경 전 값과 비교 (예: 기타 매출 → 제품 매출)", "페이지 새로고침 후에도 변경된 값 유지 확인", "API 응답과 UI 표시값 일치 확인" ], "expected": "선택한 행의 매출유형이 '제품매출'로 실제 변경되어야 함", "note": "토스트 성공 메시지만으로 PASS 판정 불가. 실제 데이터 변경 확인 필수!" }, { "id": 11, "name": "매출 등록 버튼 클릭", "action": "click_if_exists", "target": "매출 등록", "expected": "매출 등록 페이지로 이동 (/ko/accounting/sales?mode=new)" }, { "id": 12, "name": "매출 등록 페이지 - URL 확인", "action": "verify_url", "target": "/ko/accounting/sales?mode=new", "expected": "매출 등록 페이지 URL 정상" }, { "id": 13, "name": "매출 등록 페이지 - 기본정보 섹션 확인", "action": "verify_elements", "checks": [ "매출번호 필드 (자동생성, readonly)", "매출일 필드 (DatePicker)", "거래처명 드롭박스", "매출유형 드롭박스" ], "expected": "기본정보 섹션 정상 표시" }, { "id": 14, "name": "매출번호 자동생성 확인", "action": "verify_field", "target": "salesNo", "checks": [ "값이 자동 생성됨 (예: S-2026-0001)", "readonly 상태" ], "expected": "매출번호 자동생성 확인" }, { "id": 15, "name": "거래처명 드롭박스 클릭", "action": "click_if_exists", "target": "vendorId", "expected": "거래처 목록 표시" }, { "id": 16, "name": "거래처명 선택", "action": "click_if_exists", "target": "vendorId", "value": "first_available", "expected": "거래처가 선택됨" }, { "id": 17, "name": "매출유형 드롭박스 확인", "action": "click_if_exists", "target": "salesType", "checks": [ "외상매출 옵션", "제품매출 옵션", "상품매출 옵션", "부품매출 옵션", "공사매출 옵션", "임대매출 옵션", "기타매출 옵션" ], "expected": "매출유형 옵션 목록 표시" }, { "id": 18, "name": "매출유형 선택 - 제품매출", "action": "click_if_exists", "target": "salesType", "value": "product", "expected": "매출유형이 '제품매출'로 선택됨" }, { "id": 19, "name": "품목정보 섹션 확인", "action": "verify_elements", "checks": [ "품목 정보 제목", "품목 추가 버튼 (+)", "품목 테이블 헤더 (품목명, 수량, 단가, 공급가액, 부가세, 적요)", "기본 품목 행 1개 존재" ], "expected": "품목정보 섹션 정상 표시" }, { "id": 20, "name": "품목 동적 추가 - 추가 버튼 클릭", "action": "click_if_exists", "target": "품목 추가", "expected": "새로운 품목 행 추가됨" }, { "id": 21, "name": "품목 행 개수 확인 (2개)", "action": "verify_row_count", "target": "items_table", "expected": "품목 행이 2개로 증가" }, { "id": 22, "name": "품목 동적 삭제 - 두 번째 행 삭제", "action": "click_if_exists", "target": "remove_item_row_2", "expected": "두 번째 품목 행 삭제됨" }, { "id": 23, "name": "품목 행 개수 확인 (1개)", "action": "verify_row_count", "target": "items_table", "expected": "품목 행이 1개로 감소" }, { "id": 24, "name": "품목명 입력", "action": "click_if_exists", "target": "items[0].itemName", "value": "테스트 품목", "expected": "품목명 입력됨" }, { "id": 25, "name": "수량 입력", "action": "click_if_exists", "target": "items[0].quantity", "value": "10", "expected": "수량 입력됨" }, { "id": 26, "name": "단가 입력", "action": "click_if_exists", "target": "items[0].unitPrice", "value": "50000", "expected": "단가 입력됨" }, { "id": 27, "name": "자동계산 검증 - 공급가액", "action": "verify_calculated_value", "target": "items[0].supplyAmount", "formula": "quantity * unitPrice", "expectedValue": "500000", "checks": [ "수량(10) × 단가(50000) = 공급가액(500,000)", "자동으로 계산되어 표시됨" ], "expected": "공급가액이 500,000원으로 자동 계산됨" }, { "id": 28, "name": "자동계산 검증 - 부가세", "action": "verify_calculated_value", "target": "items[0].vat", "formula": "supplyAmount * 0.1", "expectedValue": "50000", "checks": [ "공급가액(500,000) × 10% = 부가세(50,000)", "자동으로 계산되어 표시됨" ], "expected": "부가세가 50,000원으로 자동 계산됨" }, { "id": 29, "name": "적요 입력 (선택사항)", "action": "click_if_exists", "target": "items[0].note", "value": "테스트 적요", "expected": "적요 입력됨" }, { "id": 30, "name": "세금계산서 발행 Switch 확인", "action": "verify_elements", "target": "taxInvoice_section", "checks": [ "세금계산서 발행 라벨", "Switch 버튼 존재", "기본값 OFF 상태" ], "expected": "세금계산서 발행 Switch 정상 표시" }, { "id": 31, "name": "세금계산서 발행 Switch ON", "action": "click_if_exists", "target": "taxInvoiceSwitch", "value": "on", "expected": "세금계산서 발행 Switch가 ON으로 변경됨" }, { "id": 32, "name": "세금계산서 발행 Switch OFF", "action": "click_if_exists", "target": "taxInvoiceSwitch", "value": "off", "expected": "세금계산서 발행 Switch가 OFF로 변경됨" }, { "id": 33, "name": "거래명세서 발행 Switch 확인", "action": "verify_elements", "target": "transactionStatement_section", "checks": [ "거래명세서 발행 라벨", "Switch 버튼 존재", "기본값 OFF 상태" ], "expected": "거래명세서 발행 Switch 정상 표시" }, { "id": 34, "name": "거래명세서 발행 Switch ON", "action": "click_if_exists", "target": "transactionStatementSwitch", "value": "on", "expected": "거래명세서 발행 Switch가 ON으로 변경됨" }, { "id": 35, "name": "거래명세서 발행 Switch OFF", "action": "click_if_exists", "target": "transactionStatementSwitch", "value": "off", "expected": "거래명세서 발행 Switch가 OFF로 변경됨" }, { "id": 36, "name": "합계 금액 확인", "action": "verify_totals", "checks": [ "총 공급가액: 500,000원", "총 부가세: 50,000원", "총 합계금액: 550,000원" ], "expected": "합계 금액 정상 표시" }, { "id": 37, "name": "취소 버튼 동작 테스트", "action": "click_if_exists", "target": "취소", "expected": "취소 확인 다이얼로그 또는 목록 페이지로 이동" }, { "id": 38, "name": "취소 확인 - 목록 페이지 복귀", "action": "verify_url", "target": "/ko/accounting/sales", "expected": "매출 목록 페이지로 복귀" }, { "id": 39, "name": "다시 매출 등록 페이지 진입", "action": "click_if_exists", "target": "매출 등록", "expected": "매출 등록 페이지로 이동" }, { "id": 40, "name": "등록 테스트용 데이터 입력 - 거래처 선택", "action": "click_if_exists", "target": "vendorId", "value": "first_available", "expected": "거래처 선택됨" }, { "id": 41, "name": "등록 테스트용 데이터 입력 - 매출유형", "action": "click_if_exists", "target": "salesType", "value": "product", "expected": "매출유형 선택됨" }, { "id": 42, "name": "등록 테스트용 데이터 입력 - 품목명", "action": "click_if_exists", "target": "items[0].itemName", "value": "E2E 테스트 품목", "expected": "품목명 입력됨" }, { "id": 43, "name": "등록 테스트용 데이터 입력 - 수량", "action": "click_if_exists", "target": "items[0].quantity", "value": "5", "expected": "수량 입력됨" }, { "id": 44, "name": "등록 테스트용 데이터 입력 - 단가", "action": "click_if_exists", "target": "items[0].unitPrice", "value": "100000", "expected": "단가 입력됨" }, { "id": 45, "name": "필수 검증 #2: 등록 버튼 클릭", "action": "click_if_exists", "target": "등록", "checks": [ "버튼 클릭 전 URL 저장", "API 호출 확인 (POST /api/v1/sales)", "에러 페이지 감지", "성공 토스트 메시지 확인" ], "expected": "매출 등록 완료" }, { "id": 46, "name": "등록 성공 확인 - 토스트 메시지", "action": "verify_toast", "target": "매출이 등록되었습니다", "expected": "성공 토스트 메시지 표시" }, { "id": 47, "name": "등록 성공 확인 - 목록 페이지 이동", "action": "verify_url", "target": "/ko/accounting/sales", "expected": "매출 목록 페이지로 이동" }, { "id": 48, "name": "등록된 매출 목록 확인", "action": "verify_table_data", "checks": [ "신규 등록된 매출이 목록에 표시됨", "품목명: E2E 테스트 품목", "공급가액: 500,000원" ], "expected": "등록된 매출이 목록에 표시됨" }, { "id": 49, "name": "거래처 미선택 시 유효성 검증 테스트", "action": "navigate", "target": "/ko/accounting/sales?mode=new", "expected": "매출 등록 페이지 이동" }, { "id": 50, "name": "거래처 미선택 상태에서 등록 시도", "action": "click_if_exists", "target": "등록", "expected": "유효성 검증 실패 - 경고 메시지" }, { "id": 51, "name": "유효성 검증 메시지 확인", "action": "verify_toast", "target": "거래처를 선택해주세요", "type": "warning", "expected": "거래처 선택 요청 경고 메시지 표시" } ], "requiredVerifications": [ { "id": 1, "name": "파일 다운로드 (엑셀)", "steps": [], "criteria": "이 시나리오에서는 테스트하지 않음 (별도 시나리오)" }, { "id": 2, "name": "등록/저장 버튼", "steps": [ 9, 10, 45, 46, 47 ], "criteria": "계정과목 저장 + 매출 등록 시 API 호출 + 성공 토스트 + URL 유지/이동 확인" }, { "id": 3, "name": "검색/필터", "steps": [], "criteria": "이 시나리오에서는 테스트하지 않음 (등록 중심 테스트)" }, { "id": 4, "name": "모달 등록 완료", "steps": [ 9, 10 ], "criteria": "계정과목 저장 확인 다이얼로그 → 확인 클릭 → 저장 완료" }, { "id": 6, "name": "⚠️ 계정과목명 변경 데이터 반영 (필수)", "steps": [ "10-1" ], "criteria": "저장 후 실제 테이블 데이터가 변경되었는지 확인. 토스트만 확인하면 불충분!", "priority": "critical", "knownBug": { "bugId": "BUG-SALES-20260115-001", "status": "OPEN", "description": "성공 토스트 표시되나 실제 데이터 미변경" } }, { "id": 5, "name": "목업 페이지 감지", "steps": [ 3 ], "criteria": "입력 필드, 동작 버튼, API 호출 확인" } ], "testData": { "vendor": "첫 번째 사용 가능한 거래처", "salesType": "product", "item": { "itemName": "E2E 테스트 품목", "quantity": 5, "unitPrice": 100000 }, "calculatedValues": { "supplyAmount": 500000, "vat": 50000, "totalAmount": 550000 } }, "expectedAPIs": [ { "method": "GET", "endpoint": "/api/v1/sales", "description": "매출 목록 조회" }, { "method": "GET", "endpoint": "/api/v1/clients", "description": "거래처 목록 조회 (드롭박스용)" }, { "method": "POST", "endpoint": "/api/v1/sales", "description": "매출 등록" }, { "method": "PUT", "endpoint": "/api/v1/sales/batch-update-account", "description": "계정과목 일괄 변경" } ], "skipTests": [ { "feature": "삭제 기능", "reason": "사용자 요청에 따라 삭제 테스트 제외" } ] }