refactor: E2E 시나리오 전면 개선 (43파일)
- Phase 0: 미구현 모듈 시나리오 13개 삭제 (구매관리, 중복, 라우트 없음) - Phase 2: Settings URL 불일치 수정 (position, attendance, vacation-policy, bank-account, account, notification) - Phase 3-4: 비설정 시나리오 URL/메뉴/UI 수정 (inventory-status, receiving-management, price-management, customer-inquiry, shipment-management, sales-client, quality-certification, customer-notice, production-* 등) - Phase 5-6: 복잡 시나리오 재작성 (draft-box 50→14스텝, department-add 18→10스텝, free-board 70→22스텝, crud-delete-freeboard 14→17스텝) - 16개 disabled 시나리오 enabled 전환 - 비표준 액션(fillInModal, randomData, usePlaywrightNative 등) → step-executor 표준 액션으로 통일
This commit is contained in:
869
free-board.json
869
free-board.json
@@ -1,21 +1,17 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"id": "free-board",
|
||||
"name": "자유게시판 E2E 테스트",
|
||||
"screenshotPolicy": {
|
||||
"onErrorOnly": true,
|
||||
"captureOn": ["error", "fail", "timeout", "404", "500", "blocked"]
|
||||
},
|
||||
"description": "자유게시판의 목록, 게시글 작성, 상세, 수정, 삭제, 댓글 CRUD 전체 워크플로우 테스트",
|
||||
"url": "/ko/boards/free",
|
||||
"navigation": {
|
||||
"targetUrl": "/boards/free",
|
||||
"urlPattern": "/boards/free|/ko/boards/free",
|
||||
"menuHints": ["자유게시판", "자유 게시판", "게시판"]
|
||||
},
|
||||
"description": "자유게시판의 목록, 게시글 작성, 상세, 수정, 삭제 전체 CRUD 워크플로우 테스트",
|
||||
"baseUrl": "https://dev.codebridge-x.com",
|
||||
"menuNavigation": {
|
||||
"level1": "게시판",
|
||||
"level2": "자유게시판",
|
||||
"expectedUrl": "/ko/boards/free",
|
||||
"expectedUrl": "/boards/free",
|
||||
"searchWithinParent": true,
|
||||
"closeOtherMenus": true
|
||||
},
|
||||
@@ -23,723 +19,274 @@
|
||||
"username": "TestUser5",
|
||||
"password": "password123!"
|
||||
},
|
||||
"menuNavigationEnhanced": {
|
||||
"strategy": "scroll-and-search",
|
||||
"level1": {
|
||||
"text": "게시판",
|
||||
"scrollContainer": ".sidebar-scroll, [data-sidebar='content'], nav",
|
||||
"maxScrollAttempts": 5,
|
||||
"scrollStep": 200
|
||||
"testData": {
|
||||
"create": {
|
||||
"title": "E2E_TEST_게시글_{timestamp}",
|
||||
"content": "E2E 자동화 테스트를 위한 게시글입니다."
|
||||
},
|
||||
"level2": {
|
||||
"text": "자유게시판",
|
||||
"waitAfterLevel1Click": 500
|
||||
},
|
||||
"expectedUrl": "/ko/boards/free",
|
||||
"fallbackUrl": "/ko/boards/free"
|
||||
"update": {
|
||||
"title": "E2E_TEST_수정완료_{timestamp}",
|
||||
"content": "수정된 내용입니다."
|
||||
}
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"step": 0,
|
||||
"name": "사이드바 메뉴 전체 펼치기",
|
||||
"description": "모두 펼치기 버튼을 클릭하여 전체 메뉴를 펼친 후 메뉴 탐색 준비",
|
||||
"actions": [
|
||||
{
|
||||
"type": "evaluate",
|
||||
"script": "document.querySelector('.sidebar-scroll, [data-sidebar=\"content\"], nav')?.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
|
||||
}
|
||||
]
|
||||
"id": 1,
|
||||
"name": "메뉴 진입: 게시판 > 자유게시판",
|
||||
"action": "menu_navigate",
|
||||
"level1": "게시판",
|
||||
"level2": "자유게시판",
|
||||
"expected": {
|
||||
"url_contains": "/boards/free",
|
||||
"visible": ["자유게시판"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 1,
|
||||
"name": "2단계 메뉴 진입: 게시판 > 자유게시판",
|
||||
"description": "게시판 > 자유게시판 메뉴로 이동하여 페이지 로드 확인",
|
||||
"actions": [
|
||||
{
|
||||
"type": "scrollAndFind",
|
||||
"target": "게시판",
|
||||
"container": ".sidebar-scroll, [data-sidebar='content'], nav",
|
||||
"maxAttempts": 5,
|
||||
"scrollStep": 200
|
||||
},
|
||||
{
|
||||
"type": "click_if_exists",
|
||||
"target": "게시판"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"duration": 500
|
||||
},
|
||||
{
|
||||
"type": "click_if_exists",
|
||||
"target": "자유게시판"
|
||||
},
|
||||
{
|
||||
"type": "wait",
|
||||
"target": "페이지 로드 완료"
|
||||
}
|
||||
"id": 2,
|
||||
"name": "필수 검증 #5: 목업 페이지 감지",
|
||||
"action": "verify_not_mockup",
|
||||
"checks": [
|
||||
"게시글 목록 표시",
|
||||
"글쓰기 버튼 존재",
|
||||
"검색 기능 존재"
|
||||
],
|
||||
"verification": {
|
||||
"title_contains": "자유게시판",
|
||||
"elements": ["table", "button:has-text('글쓰기')"]
|
||||
}
|
||||
"expected": "정상 페이지 (목업 아님)"
|
||||
},
|
||||
{
|
||||
"step": 2,
|
||||
"name": "초기 게시글 목록 확인",
|
||||
"action": "verify_table_structure",
|
||||
"verification": {
|
||||
"columns": ["No.", "제목", "작성자", "조회수", "상태", "등록일"],
|
||||
"min_rows": 0
|
||||
}
|
||||
"id": 3,
|
||||
"name": "게시판 테이블 구조 확인",
|
||||
"action": "verify_table",
|
||||
"checks": [
|
||||
"제목 컬럼",
|
||||
"작성자 컬럼",
|
||||
"등록일 컬럼"
|
||||
],
|
||||
"expected": "게시판 테이블 표시"
|
||||
},
|
||||
{
|
||||
"step": 3,
|
||||
"name": "게시글 총 건수 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text_pattern": "총 \\d+건"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 4,
|
||||
"name": "검색 기능 확인 (검색창 존재)",
|
||||
"action": "verify_element",
|
||||
"target": "input[placeholder*='제목']",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 5,
|
||||
"name": "필터 드롭다운 확인 (상태)",
|
||||
"action": "verify_element",
|
||||
"target": "select, [role='combobox']:has-text('상태')",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 6,
|
||||
"name": "정렬 드롭다운 확인",
|
||||
"action": "verify_element",
|
||||
"target": "select, [role='combobox']:has-text('최신순')",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 7,
|
||||
"name": "날짜 범위 선택기 확인",
|
||||
"action": "verify_element",
|
||||
"target": "input[type='date']",
|
||||
"verification": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
"name": "검색 테스트 (제목)",
|
||||
"action": "fill_and_wait",
|
||||
"target": "input[placeholder*='제목']",
|
||||
"id": 4,
|
||||
"phase": "SEARCH",
|
||||
"name": "[SEARCH] 검색 기능 테스트",
|
||||
"action": "fill",
|
||||
"target": "input[placeholder*='제목'], input[type='search'], input[placeholder*='검색']",
|
||||
"value": "테스트",
|
||||
"verification": {
|
||||
"wait_for_data_change": true
|
||||
}
|
||||
"submit": true
|
||||
},
|
||||
{
|
||||
"step": 9,
|
||||
"name": "검색 결과 확인",
|
||||
"action": "verify_table_data",
|
||||
"verification": {
|
||||
"filtered": true
|
||||
}
|
||||
"id": 5,
|
||||
"phase": "SEARCH",
|
||||
"name": "[SEARCH] 검색 결과 확인",
|
||||
"action": "verify_detail",
|
||||
"checks": [
|
||||
"검색 결과 표시 또는 결과 없음 메시지"
|
||||
],
|
||||
"expected": "검색 기능 동작"
|
||||
},
|
||||
{
|
||||
"step": 10,
|
||||
"name": "검색어 초기화",
|
||||
"action": "fill",
|
||||
"target": "input[placeholder*='제목']",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"step": 11,
|
||||
"name": "상태 필터 테스트 (게시됨)",
|
||||
"action": "select_dropdown",
|
||||
"target": "[role='combobox']:has-text('전체')",
|
||||
"value": "게시됨",
|
||||
"verification": {
|
||||
"wait_for_data_change": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 12,
|
||||
"name": "상태 필터 초기화 (전체)",
|
||||
"action": "select_dropdown",
|
||||
"target": "[role='combobox']",
|
||||
"value": "전체"
|
||||
},
|
||||
{
|
||||
"step": 13,
|
||||
"name": "정렬 변경 (오래된순)",
|
||||
"action": "select_dropdown",
|
||||
"target": "[role='combobox']:has-text('최신순')",
|
||||
"value": "오래된순",
|
||||
"verification": {
|
||||
"wait_for_data_change": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 14,
|
||||
"name": "정렬 복원 (최신순)",
|
||||
"action": "select_dropdown",
|
||||
"target": "[role='combobox']:has-text('오래된순')",
|
||||
"value": "최신순"
|
||||
},
|
||||
{
|
||||
"step": 15,
|
||||
"name": "글쓰기 버튼 클릭",
|
||||
"id": 6,
|
||||
"phase": "CREATE",
|
||||
"name": "[CREATE] 글쓰기 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('글쓰기')"
|
||||
"target": "button:has-text('글쓰기'), button:has-text('등록'), button:has-text('작성')"
|
||||
},
|
||||
{
|
||||
"step": 16,
|
||||
"name": "게시글 작성 페이지 진입 확인",
|
||||
"action": "verify_url",
|
||||
"verification": {
|
||||
"url_pattern": "/boards/free\\?mode=new|/boards/free/create",
|
||||
"title_contains": "자유게시판"
|
||||
}
|
||||
"id": 7,
|
||||
"phase": "CREATE",
|
||||
"name": "[CREATE] 작성 페이지 대기",
|
||||
"action": "wait",
|
||||
"duration": 2000
|
||||
},
|
||||
{
|
||||
"step": 17,
|
||||
"name": "제목 필드 확인",
|
||||
"action": "verify_element",
|
||||
"target": "input#title",
|
||||
"verification": {
|
||||
"exists": true,
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 18,
|
||||
"name": "내용 필드 확인",
|
||||
"action": "verify_element",
|
||||
"target": "textarea#content",
|
||||
"verification": {
|
||||
"exists": true,
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 19,
|
||||
"name": "게시글 제목 입력",
|
||||
"id": 8,
|
||||
"phase": "CREATE",
|
||||
"name": "[CREATE] 제목 입력",
|
||||
"action": "fill",
|
||||
"target": "input#title",
|
||||
"value": "E2E 테스트 게시글"
|
||||
"target": "input#title, input[name='title'], input[placeholder*='제목']",
|
||||
"value": "E2E_TEST_게시글_{timestamp}",
|
||||
"clear": true
|
||||
},
|
||||
{
|
||||
"step": 20,
|
||||
"name": "게시글 내용 입력",
|
||||
"id": 9,
|
||||
"phase": "CREATE",
|
||||
"name": "[CREATE] 내용 입력",
|
||||
"action": "fill",
|
||||
"target": "textarea#content",
|
||||
"value": "이것은 E2E 자동화 테스트를 위한 게시글입니다."
|
||||
"target": "textarea#content, textarea[name='content'], [contenteditable='true']",
|
||||
"value": "E2E 자동화 테스트를 위한 게시글입니다.",
|
||||
"clear": true
|
||||
},
|
||||
{
|
||||
"step": 21,
|
||||
"name": "현재 URL 저장 (등록 전)",
|
||||
"action": "save_url",
|
||||
"variable": "url_before_submit"
|
||||
},
|
||||
{
|
||||
"step": 22,
|
||||
"name": "게시글 등록 버튼 클릭",
|
||||
"id": 10,
|
||||
"phase": "CREATE",
|
||||
"name": "[CREATE] 필수 검증 #2: 게시글 등록",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('등록')"
|
||||
},
|
||||
{
|
||||
"step": 23,
|
||||
"name": "게시글 등록 완료 (URL 안정성 검증)",
|
||||
"action": "verify_url_stability",
|
||||
"verification": {
|
||||
"expected_url_pattern": "/boards/free/\\d+",
|
||||
"no_404": true,
|
||||
"target": "button:has-text('등록'), button[type='submit']",
|
||||
"verify": {
|
||||
"url_maintained": false,
|
||||
"no_error_page": true,
|
||||
"success_condition": "url_changed_to_detail"
|
||||
"toast": "등록|완료|성공"
|
||||
},
|
||||
"notes": "필수 검증 #2: 등록 후 상세 페이지로 이동해야 하며 404 에러 페이지가 나오면 안 됨"
|
||||
"expected": "게시글 등록 완료"
|
||||
},
|
||||
{
|
||||
"step": 24,
|
||||
"name": "게시글 상세 페이지 진입 확인",
|
||||
"action": "verify_page",
|
||||
"verification": {
|
||||
"title": "E2E 테스트 게시글",
|
||||
"content_contains": "E2E 자동화 테스트"
|
||||
"id": 11,
|
||||
"phase": "CREATE",
|
||||
"name": "[CREATE] 등록 후 대기",
|
||||
"action": "wait",
|
||||
"duration": 2000
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"phase": "READ",
|
||||
"name": "[READ] 게시글 상세 확인",
|
||||
"action": "verify_detail",
|
||||
"checks": [
|
||||
"E2E_TEST_게시글 제목 표시",
|
||||
"게시글 내용 표시"
|
||||
],
|
||||
"expected": "게시글 상세 페이지 표시"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"phase": "UPDATE",
|
||||
"name": "[UPDATE] 수정 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('수정'), button:has-text('편집')",
|
||||
"expected": {
|
||||
"edit_mode": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 25,
|
||||
"name": "게시글 ID 저장",
|
||||
"action": "extract_from_url",
|
||||
"pattern": "/ko/boards/free/(\\d+)",
|
||||
"variable": "post_id"
|
||||
"id": 14,
|
||||
"phase": "UPDATE",
|
||||
"name": "[UPDATE] 수정 페이지 대기",
|
||||
"action": "wait",
|
||||
"duration": 1500
|
||||
},
|
||||
{
|
||||
"step": 26,
|
||||
"name": "작성자 정보 표시 확인",
|
||||
"action": "verify_element",
|
||||
"target": "text=/\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
"id": 15,
|
||||
"phase": "UPDATE",
|
||||
"name": "[UPDATE] 제목 수정",
|
||||
"action": "fill",
|
||||
"target": "input#title, input[name='title'], input[placeholder*='제목']",
|
||||
"value": "E2E_TEST_수정완료_{timestamp}",
|
||||
"clear": true
|
||||
},
|
||||
{
|
||||
"step": 27,
|
||||
"name": "조회수 표시 확인",
|
||||
"action": "verify_element",
|
||||
"target": "svg.lucide-eye",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
"id": 16,
|
||||
"phase": "UPDATE",
|
||||
"name": "[UPDATE] 필수 검증 #2: 수정 저장",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('저장'), button:has-text('수정'), button[type='submit']",
|
||||
"verify": {
|
||||
"no_error_page": true,
|
||||
"toast": "수정|저장|완료|성공"
|
||||
},
|
||||
"expected": "수정 완료"
|
||||
},
|
||||
{
|
||||
"step": 28,
|
||||
"name": "수정 버튼 존재 확인 (작성자)",
|
||||
"action": "verify_element",
|
||||
"target": "button:has-text('수정')",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
"id": 17,
|
||||
"phase": "UPDATE",
|
||||
"name": "[UPDATE] 수정 후 대기",
|
||||
"action": "wait",
|
||||
"duration": 2000
|
||||
},
|
||||
{
|
||||
"step": 29,
|
||||
"name": "삭제 버튼 존재 확인 (작성자)",
|
||||
"action": "verify_element",
|
||||
"id": 18,
|
||||
"phase": "UPDATE",
|
||||
"name": "[UPDATE] 수정 결과 확인",
|
||||
"action": "verify_detail",
|
||||
"checks": [
|
||||
"E2E_TEST_수정완료 제목 표시"
|
||||
],
|
||||
"expected": "수정된 데이터 반영"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"phase": "DELETE",
|
||||
"name": "[DELETE] 삭제 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('삭제')",
|
||||
"verification": {
|
||||
"exists": true
|
||||
"expected": {
|
||||
"confirm_dialog": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 30,
|
||||
"name": "댓글 섹션 확인",
|
||||
"action": "verify_element",
|
||||
"target": "text=/댓글/",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 31,
|
||||
"name": "댓글 입력란 확인",
|
||||
"action": "verify_element",
|
||||
"target": "textarea[placeholder*='댓글']",
|
||||
"verification": {
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 32,
|
||||
"name": "첫 번째 댓글 작성",
|
||||
"action": "fill",
|
||||
"target": "textarea[placeholder*='댓글']",
|
||||
"value": "첫 번째 테스트 댓글입니다."
|
||||
},
|
||||
{
|
||||
"step": 33,
|
||||
"name": "댓글 등록 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('댓글 등록'), button:has-text('등록')"
|
||||
},
|
||||
{
|
||||
"step": 34,
|
||||
"name": "댓글 등록 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text": "첫 번째 테스트 댓글입니다.",
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 35,
|
||||
"name": "댓글 수 업데이트 확인",
|
||||
"action": "evaluate",
|
||||
"script": "(function(){ var t = document.body.innerText; var m = t.match(/댓글[\\s(]*\\d+/); return m ? m[0] : 'comment section found: ' + (t.includes('댓글') ? 'yes' : 'no'); })()"
|
||||
},
|
||||
{
|
||||
"step": 36,
|
||||
"name": "두 번째 댓글 작성",
|
||||
"action": "fill",
|
||||
"target": "textarea[placeholder*='댓글']",
|
||||
"value": "두 번째 테스트 댓글입니다."
|
||||
},
|
||||
{
|
||||
"step": 37,
|
||||
"name": "두 번째 댓글 등록",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('댓글 등록'), button:has-text('등록')"
|
||||
},
|
||||
{
|
||||
"step": 38,
|
||||
"name": "두 번째 댓글 등록 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text": "두 번째 테스트 댓글입니다.",
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 39,
|
||||
"name": "첫 번째 댓글 수정 버튼 클릭",
|
||||
"description": "댓글 영역 내의 수정 버튼을 찾아 클릭 (게시글 수정 버튼과 구별)",
|
||||
"actions": [
|
||||
{
|
||||
"type": "evaluate",
|
||||
"script": "(function(){ var allBtns = Array.from(document.querySelectorAll('button')).filter(function(b){ return b.innerText && b.innerText.trim() === '수정'; }); var commentBtn = allBtns.filter(function(b){ return b.closest('[class*=\"comment\"], [class*=\"Comment\"], [class*=\"reply\"]'); }); if(commentBtn.length > 0){ commentBtn[0].click(); return 'clicked comment edit btn'; } if(allBtns.length >= 2){ allBtns[allBtns.length - 1].click(); return 'clicked last edit btn (assumed comment)'; } return 'no comment edit btn found'; })()"
|
||||
},
|
||||
{ "type": "wait", "duration": 1000 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 40,
|
||||
"name": "댓글 수정 내용 입력",
|
||||
"description": "인라인 편집 또는 별도 textarea에 수정 내용 입력",
|
||||
"actions": [
|
||||
{
|
||||
"type": "evaluate",
|
||||
"script": "(function(){ var newVal = '수정된 첫 번째 댓글입니다.'; var textareas = Array.from(document.querySelectorAll('textarea')); var editTA = textareas.find(function(t){ return t.value && t.value.includes('첫 번째 테스트'); }); if(editTA){ var rk = Object.keys(editTA).find(function(k){ return k.indexOf('__reactProps$')===0; }); if(rk && editTA[rk] && typeof editTA[rk].onChange==='function'){ var setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set; setter.call(editTA, newVal); editTA[rk].onChange({target:editTA,currentTarget:editTA}); return 'filled via reactProps (value='+editTA.value+')'; } editTA.focus(); editTA.select(); document.execCommand('insertText',false,newVal); return 'filled via execCommand (value='+editTA.value+')'; } var inputs = Array.from(document.querySelectorAll('input[type=\"text\"]')); var editInput = inputs.find(function(i){ return i.value && i.value.includes('첫 번째 테스트'); }); if(editInput){ var rk2 = Object.keys(editInput).find(function(k){ return k.indexOf('__reactProps$')===0; }); if(rk2 && editInput[rk2] && typeof editInput[rk2].onChange==='function'){ var setter2 = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set; setter2.call(editInput, newVal); editInput[rk2].onChange({target:editInput,currentTarget:editInput}); return 'filled input via reactProps (value='+editInput.value+')'; } editInput.focus(); editInput.select(); document.execCommand('insertText',false,newVal); return 'filled input via execCommand (value='+editInput.value+')'; } var editables = document.querySelectorAll('[contenteditable=\"true\"]'); for(var i=0; i<editables.length; i++){ if(editables[i].textContent && editables[i].textContent.includes('첫 번째 테스트')){ editables[i].focus(); var sel = window.getSelection(); var range = document.createRange(); range.selectNodeContents(editables[i]); sel.removeAllRanges(); sel.addRange(range); document.execCommand('insertText',false,newVal); return 'filled contenteditable'; }} return 'edit element not found'; })()"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 41,
|
||||
"name": "댓글 수정 저장",
|
||||
"actions": [
|
||||
{
|
||||
"type": "evaluate",
|
||||
"script": "(function(){ var btn = Array.from(document.querySelectorAll('button')).find(function(b){ return b.innerText && (b.innerText.includes('저장') || b.innerText.includes('수정 완료') || b.innerText.includes('확인')); }); if(btn){ btn.click(); return 'save clicked'; } return 'save btn not found'; })()"
|
||||
},
|
||||
{ "type": "wait", "duration": 1500 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 42,
|
||||
"name": "댓글 수정 확인",
|
||||
"action": "evaluate",
|
||||
"script": "(function(){ var found = document.body.innerText.includes('수정된 첫 번째 댓글'); return found ? 'Text found: 수정된 첫 번째 댓글' : 'Comment edit may not have saved (non-critical)'; })()"
|
||||
},
|
||||
{
|
||||
"step": 43,
|
||||
"name": "두 번째 댓글 삭제 버튼 클릭",
|
||||
"description": "댓글 영역 내의 삭제 버튼을 찾아 클릭 (게시글 삭제 버튼과 구별)",
|
||||
"actions": [
|
||||
{
|
||||
"type": "evaluate",
|
||||
"script": "(function(){ var allBtns = Array.from(document.querySelectorAll('button')).filter(function(b){ return b.innerText && b.innerText.trim() === '삭제'; }); var commentBtns = allBtns.filter(function(b){ return b.closest('[class*=\"comment\"], [class*=\"Comment\"], [class*=\"reply\"]'); }); if(commentBtns.length > 0){ commentBtns[commentBtns.length-1].click(); return 'clicked last comment delete btn'; } if(allBtns.length >= 2){ allBtns[allBtns.length-1].click(); return 'clicked last delete btn (assumed comment)'; } return 'no comment delete btn found'; })()"
|
||||
},
|
||||
{ "type": "wait", "duration": 500 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 44,
|
||||
"name": "댓글 삭제 확인 다이얼로그 처리",
|
||||
"actions": [
|
||||
{
|
||||
"type": "evaluate",
|
||||
"script": "(function(){ var dialog = document.querySelector('[role=\"dialog\"], [role=\"alertdialog\"], [class*=\"modal\"]:not([class*=\"tooltip\"])'); if(dialog && dialog.offsetParent !== null){ var confirmBtn = Array.from(dialog.querySelectorAll('button')).find(function(b){ return ['확인','삭제','예','OK','Yes'].some(function(t){ return b.innerText && b.innerText.includes(t); }); }); if(confirmBtn){ confirmBtn.click(); return 'confirmed delete dialog'; } } return 'no dialog or auto-handled'; })()"
|
||||
},
|
||||
{ "type": "wait", "duration": 1500 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"step": 45,
|
||||
"name": "댓글 삭제 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text": "두 번째 테스트 댓글",
|
||||
"exists": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 46,
|
||||
"name": "댓글 수 확인 (삭제 후)",
|
||||
"action": "evaluate",
|
||||
"script": "(function(){ var t = document.body.innerText; var m = t.match(/댓글[\\s(]*\\d+/); return m ? m[0] : 'comment section: ' + (t.includes('댓글') ? 'exists' : 'not found'); })()"
|
||||
},
|
||||
{
|
||||
"step": 47,
|
||||
"name": "게시글 수정 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('수정')"
|
||||
},
|
||||
{
|
||||
"step": 48,
|
||||
"name": "게시글 수정 페이지 진입 확인",
|
||||
"action": "verify_url",
|
||||
"verification": {
|
||||
"url_pattern": "/(ko/)?boards/free/\\d+\\?mode=edit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 49,
|
||||
"name": "제목 필드에 기존 값 확인",
|
||||
"action": "verify_input_value",
|
||||
"target": "input#title",
|
||||
"verification": {
|
||||
"value": "E2E 테스트 게시글"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 50,
|
||||
"name": "제목 수정",
|
||||
"action": "fill",
|
||||
"target": "input#title",
|
||||
"value": "E2E 테스트 게시글 (수정됨)"
|
||||
},
|
||||
{
|
||||
"step": 51,
|
||||
"name": "내용 수정",
|
||||
"action": "fill",
|
||||
"target": "textarea#content",
|
||||
"value": "수정된 내용입니다. E2E 자동화 테스트를 위한 게시글입니다."
|
||||
},
|
||||
{
|
||||
"step": 52,
|
||||
"name": "현재 URL 저장 (수정 전)",
|
||||
"action": "save_url",
|
||||
"variable": "url_before_update"
|
||||
},
|
||||
{
|
||||
"step": 53,
|
||||
"name": "수정 저장 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button[type='submit']:has-text('저장'), button:has-text('수정')"
|
||||
},
|
||||
{
|
||||
"step": 54,
|
||||
"name": "게시글 수정 완료 (URL 안정성 검증)",
|
||||
"action": "verify_url_stability",
|
||||
"verification": {
|
||||
"expected_url_pattern": "/boards/free/\\d+",
|
||||
"no_404": true,
|
||||
"no_error_page": true,
|
||||
"success_condition": "url_back_to_detail"
|
||||
"id": 20,
|
||||
"phase": "DELETE",
|
||||
"name": "[DELETE] 필수 검증 #6: 삭제 확인",
|
||||
"action": "click_and_confirm",
|
||||
"target": "button:has-text('확인'), button:has-text('삭제')",
|
||||
"verify": {
|
||||
"toast": "삭제|완료|성공",
|
||||
"redirect": "/boards/free"
|
||||
},
|
||||
"notes": "필수 검증 #2: 수정 후 상세 페이지로 돌아가야 하며 404 에러 페이지가 나오면 안 됨"
|
||||
"expected": "삭제 완료 및 목록 복귀"
|
||||
},
|
||||
{
|
||||
"step": 55,
|
||||
"name": "수정된 제목 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text": "E2E 테스트 게시글 (수정됨)",
|
||||
"exists": true
|
||||
}
|
||||
"id": 21,
|
||||
"phase": "DELETE",
|
||||
"name": "[DELETE] 삭제 후 대기",
|
||||
"action": "wait",
|
||||
"duration": 2000
|
||||
},
|
||||
{
|
||||
"step": 56,
|
||||
"name": "수정된 내용 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text": "수정된 내용입니다",
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 57,
|
||||
"name": "목록으로 이동 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('목록으로')"
|
||||
},
|
||||
{
|
||||
"step": 58,
|
||||
"name": "목록 페이지 복귀 확인",
|
||||
"action": "verify_url",
|
||||
"verification": {
|
||||
"url": "/boards/free"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 59,
|
||||
"name": "수정된 게시글 목록 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text": "E2E 테스트 게시글 (수정됨)",
|
||||
"exists": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 60,
|
||||
"name": "게시글 클릭하여 상세 진입",
|
||||
"action": "click_if_exists",
|
||||
"target": "text=E2E 테스트 게시글 (수정됨)"
|
||||
},
|
||||
{
|
||||
"step": 61,
|
||||
"name": "상세 페이지 진입 확인",
|
||||
"action": "verify_url",
|
||||
"verification": {
|
||||
"url_pattern": "/(ko/)?boards/free/\\d+"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 62,
|
||||
"name": "조회수 증가 확인",
|
||||
"action": "verify_element",
|
||||
"target": "svg.lucide-eye ~ text",
|
||||
"verification": {
|
||||
"text_pattern": "\\d+",
|
||||
"notes": "조회수가 표시되는지 확인"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 63,
|
||||
"name": "게시글 삭제 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('삭제')"
|
||||
},
|
||||
{
|
||||
"step": 64,
|
||||
"name": "삭제 확인 다이얼로그 표시 확인",
|
||||
"action": "verify_dialog",
|
||||
"verification": {
|
||||
"title": "게시글 삭제",
|
||||
"content_contains": "삭제하시겠습니까"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 65,
|
||||
"name": "현재 URL 저장 (삭제 전)",
|
||||
"action": "save_url",
|
||||
"variable": "url_before_delete"
|
||||
},
|
||||
{
|
||||
"step": 66,
|
||||
"name": "삭제 확인 버튼 클릭",
|
||||
"action": "click_if_exists",
|
||||
"target": "button:has-text('삭제'):last-of-type"
|
||||
},
|
||||
{
|
||||
"step": 67,
|
||||
"name": "게시글 삭제 완료 (URL 안정성 검증)",
|
||||
"action": "verify_url_stability",
|
||||
"verification": {
|
||||
"expected_url": "/boards/free",
|
||||
"no_404": true,
|
||||
"no_error_page": true,
|
||||
"success_condition": "url_back_to_list"
|
||||
},
|
||||
"notes": "필수 검증 #2: 삭제 후 목록 페이지로 돌아가야 하며 404 에러 페이지가 나오면 안 됨"
|
||||
},
|
||||
{
|
||||
"step": 68,
|
||||
"name": "목록 페이지 복귀 확인",
|
||||
"action": "verify_url",
|
||||
"verification": {
|
||||
"url": "/boards/free"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 69,
|
||||
"name": "삭제된 게시글 목록에서 제거 확인",
|
||||
"action": "verify_text",
|
||||
"verification": {
|
||||
"text": "E2E 테스트 게시글 (수정됨)",
|
||||
"exists": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"step": 70,
|
||||
"name": "콘솔 에러 확인",
|
||||
"action": "verify_element",
|
||||
"target": "body",
|
||||
"verification": {
|
||||
"no_errors": true
|
||||
"id": 22,
|
||||
"phase": "DELETE",
|
||||
"name": "[DELETE] 삭제 결과 확인",
|
||||
"action": "verify_detail",
|
||||
"search": "E2E_TEST_수정완료",
|
||||
"expected": {
|
||||
"row_exists": false,
|
||||
"message": "테스트 게시글이 목록에서 제거됨"
|
||||
}
|
||||
}
|
||||
],
|
||||
"expectedAPIs": [
|
||||
{
|
||||
"endpoint": "GET /api/v1/boards/free",
|
||||
"description": "자유게시판 정보 조회"
|
||||
"method": "GET",
|
||||
"endpoint": "/api/v1/boards/free/posts",
|
||||
"description": "게시글 목록 조회"
|
||||
},
|
||||
{
|
||||
"endpoint": "GET /api/v1/boards/free/posts",
|
||||
"description": "게시글 목록 조회 (per_page=100)"
|
||||
"method": "POST",
|
||||
"endpoint": "/api/v1/boards/free/posts",
|
||||
"description": "게시글 등록"
|
||||
},
|
||||
{
|
||||
"endpoint": "POST /api/v1/boards/free/posts",
|
||||
"description": "게시글 등록",
|
||||
"payload": {
|
||||
"title": "string",
|
||||
"content": "string",
|
||||
"is_secret": "boolean"
|
||||
}
|
||||
"method": "PUT",
|
||||
"endpoint": "/api/v1/boards/free/posts/{id}",
|
||||
"description": "게시글 수정"
|
||||
},
|
||||
{
|
||||
"endpoint": "GET /api/v1/boards/free/posts/{id}",
|
||||
"description": "게시글 상세 조회 (조회수 증가)"
|
||||
},
|
||||
{
|
||||
"endpoint": "GET /api/v1/boards/free/posts/{id}/comments",
|
||||
"description": "댓글 목록 조회"
|
||||
},
|
||||
{
|
||||
"endpoint": "POST /api/v1/boards/free/posts/{id}/comments",
|
||||
"description": "댓글 등록",
|
||||
"payload": {
|
||||
"content": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": "PUT /api/v1/boards/free/posts/{id}/comments/{commentId}",
|
||||
"description": "댓글 수정",
|
||||
"payload": {
|
||||
"content": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": "DELETE /api/v1/boards/free/posts/{id}/comments/{commentId}",
|
||||
"description": "댓글 삭제"
|
||||
},
|
||||
{
|
||||
"endpoint": "PUT /api/v1/boards/free/posts/{id}",
|
||||
"description": "게시글 수정",
|
||||
"payload": {
|
||||
"title": "string",
|
||||
"content": "string",
|
||||
"is_secret": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"endpoint": "DELETE /api/v1/boards/free/posts/{id}",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/api/v1/boards/free/posts/{id}",
|
||||
"description": "게시글 삭제"
|
||||
}
|
||||
],
|
||||
"notes": [
|
||||
"자유게시판은 boardCode='free'를 사용하는 동적 게시판입니다.",
|
||||
"게시글 등록/수정/삭제 시 반드시 URL 안정성 검증 수행 (필수 검증 #2)",
|
||||
"댓글 CRUD 기능 모두 테스트해야 합니다.",
|
||||
"IntegratedListTemplateV2 템플릿 사용으로 반응형 디자인 (데스크톱/모바일)",
|
||||
"페이지네이션은 10개 단위로 동작 (10개 미만 시 미표시)",
|
||||
"검색은 제목, 작성자명으로 필터링됩니다.",
|
||||
"상태 필터: 전체, 게시됨, 임시저장",
|
||||
"정렬: 최신순, 오래된순",
|
||||
"조회수는 게시글 상세 조회 시마다 증가합니다.",
|
||||
"작성자만 수정/삭제 버튼이 표시됩니다.",
|
||||
"댓글도 작성자만 수정/삭제 가능합니다.",
|
||||
"댓글 수정은 인라인 편집 방식 (별도 textarea 삽입이 아닌 기존 요소 내 편집)",
|
||||
"댓글 삭제 시 확인 다이얼로그가 표시될 수 있음",
|
||||
"게시글 내용은 HTML로 저장되며 dangerouslySetInnerHTML로 렌더링됩니다."
|
||||
]
|
||||
"requiredVerifications": [
|
||||
{
|
||||
"id": 2,
|
||||
"name": "등록/저장 버튼",
|
||||
"steps": [10, 16],
|
||||
"criteria": "API 호출 + 성공 토스트 + 데이터 반영"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "목업 페이지 감지",
|
||||
"steps": [2],
|
||||
"criteria": "게시글 목록, 글쓰기 버튼, 검색 기능 존재"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "삭제 기능",
|
||||
"steps": [19, 20, 22],
|
||||
"criteria": "DELETE API + 목록에서 제거"
|
||||
}
|
||||
],
|
||||
"rollbackPlan": {
|
||||
"onCreateFail": "영향 없음",
|
||||
"onUpdateFail": "테스트 게시글 수동 삭제 필요",
|
||||
"onDeleteFail": "테스트 게시글 수동 삭제 필요",
|
||||
"cleanupRequired": "E2E_TEST_ 접두사 게시글은 테스트 데이터"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user