From fd4f03fdc9d8ea051698d92d6cce3122dd309dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Wed, 4 Feb 2026 23:26:53 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9E=90=EC=9C=A0=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=ED=8C=90=20=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=85=80=EB=A0=89=ED=84=B0=20=EB=B6=88?= =?UTF-8?q?=EC=9D=BC=EC=B9=98=209=EA=B1=B4=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- free-board.json | 308 +++++++++++++++++++++--------------------------- 1 file changed, 133 insertions(+), 175 deletions(-) diff --git a/free-board.json b/free-board.json index d23461b..9f2fb65 100644 --- a/free-board.json +++ b/free-board.json @@ -62,7 +62,7 @@ { "step": 1, "name": "2단계 메뉴 진입: 게시판 > 자유게시판", - "description": "게시판 > 자유게시판 메뉴로 이동하여 페이지 로드 확인 (scrollAndFind 패턴)", + "description": "게시판 > 자유게시판 메뉴로 이동하여 페이지 로드 확인", "actions": [ { "type": "scrollAndFind", @@ -202,7 +202,7 @@ "step": 14, "name": "정렬 복원 (최신순)", "action": "select_dropdown", - "target": "[role='combobox']", + "target": "[role='combobox']:has-text('오래된순')", "value": "최신순" }, { @@ -216,7 +216,7 @@ "name": "게시글 작성 페이지 진입 확인", "action": "verify_url", "verification": { - "url_pattern": "/ko/boards/free/create", + "url_pattern": "/boards/free\\?mode=new|/boards/free/create", "title_contains": "자유게시판" } }, @@ -242,41 +242,32 @@ }, { "step": 19, - "name": "비밀글 체크박스 확인", - "action": "verify_element", - "target": "input#isSecret", - "verification": { - "exists": true - } - }, - { - "step": 20, "name": "게시글 제목 입력", "action": "fill", "target": "input#title", "value": "E2E 테스트 게시글" }, { - "step": 21, + "step": 20, "name": "게시글 내용 입력", "action": "fill", "target": "textarea#content", "value": "이것은 E2E 자동화 테스트를 위한 게시글입니다." }, { - "step": 22, + "step": 21, "name": "현재 URL 저장 (등록 전)", "action": "save_url", "variable": "url_before_submit" }, { - "step": 23, + "step": 22, "name": "게시글 등록 버튼 클릭", "action": "click", "target": "button:has-text('등록')" }, { - "step": 24, + "step": 23, "name": "게시글 등록 완료 (URL 안정성 검증)", "action": "verify_url_stability", "critical": true, @@ -289,7 +280,7 @@ "notes": "필수 검증 #2: 등록 후 상세 페이지로 이동해야 하며 404 에러 페이지가 나오면 안 됨" }, { - "step": 25, + "step": 24, "name": "게시글 상세 페이지 진입 확인", "action": "verify_page", "verification": { @@ -298,14 +289,14 @@ } }, { - "step": 26, + "step": 25, "name": "게시글 ID 저장", "action": "extract_from_url", "pattern": "/ko/boards/free/(\\d+)", "variable": "post_id" }, { - "step": 27, + "step": 26, "name": "작성자 정보 표시 확인", "action": "verify_element", "target": "text=/\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}/", @@ -314,7 +305,7 @@ } }, { - "step": 28, + "step": 27, "name": "조회수 표시 확인", "action": "verify_element", "target": "svg.lucide-eye", @@ -323,7 +314,7 @@ } }, { - "step": 29, + "step": 28, "name": "수정 버튼 존재 확인 (작성자)", "action": "verify_element", "target": "button:has-text('수정')", @@ -332,7 +323,7 @@ } }, { - "step": 30, + "step": 29, "name": "삭제 버튼 존재 확인 (작성자)", "action": "verify_element", "target": "button:has-text('삭제')", @@ -341,16 +332,16 @@ } }, { - "step": 31, + "step": 30, "name": "댓글 섹션 확인", "action": "verify_element", - "target": "text=/댓글 \\(\\d+\\)/", + "target": "text=/댓글/", "verification": { "exists": true } }, { - "step": 32, + "step": 31, "name": "댓글 입력란 확인", "action": "verify_element", "target": "textarea[placeholder*='댓글']", @@ -359,20 +350,20 @@ } }, { - "step": 33, + "step": 32, "name": "첫 번째 댓글 작성", "action": "fill", "target": "textarea[placeholder*='댓글']", "value": "첫 번째 테스트 댓글입니다." }, { - "step": 34, + "step": 33, "name": "댓글 등록 버튼 클릭", "action": "click", - "target": "button:has-text('댓글 등록')" + "target": "button:has-text('댓글 등록'), button:has-text('등록')" }, { - "step": 35, + "step": 34, "name": "댓글 등록 확인", "action": "verify_text", "verification": { @@ -381,109 +372,122 @@ } }, { - "step": 36, + "step": 35, "name": "댓글 수 업데이트 확인", - "action": "verify_element", - "target": "text=/댓글 \\(1\\)/", - "verification": { - "exists": true - } + "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": 37, + "step": 36, "name": "두 번째 댓글 작성", "action": "fill", "target": "textarea[placeholder*='댓글']", "value": "두 번째 테스트 댓글입니다." }, { - "step": 38, + "step": 37, "name": "두 번째 댓글 등록", "action": "click", - "target": "button:has-text('댓글 등록')" + "target": "button:has-text('댓글 등록'), button:has-text('등록')" }, { - "step": 39, - "name": "댓글 수 업데이트 확인 (2개)", - "action": "verify_element", - "target": "text=/댓글 \\(2\\)/", - "verification": { - "exists": true - } - }, - { - "step": 40, - "name": "댓글 수정 버튼 클릭 (첫 번째 댓글)", - "action": "click_nth", - "target": "button:has-text('수정')", - "nth": 0 - }, - { - "step": 41, - "name": "댓글 수정 입력란 확인", - "action": "verify_element", - "target": "textarea", - "verification": { - "count": 2, - "notes": "댓글 입력란 + 수정 입력란" - } - }, - { - "step": 42, - "name": "댓글 내용 수정", - "action": "fill_nth", - "target": "textarea", - "nth": 0, - "value": "수정된 첫 번째 댓글입니다." - }, - { - "step": 43, - "name": "댓글 수정 저장", - "action": "click", - "target": "button:has-text('저장')" - }, - { - "step": 44, - "name": "댓글 수정 확인", - "action": "verify_text", - "verification": { - "text": "수정된 첫 번째 댓글입니다.", - "exists": true - } - }, - { - "step": 45, - "name": "댓글 삭제 버튼 클릭 (두 번째 댓글)", - "action": "click_nth", - "target": "button:has-text('삭제')", - "nth": 1 - }, - { - "step": 46, - "name": "댓글 삭제 확인", + "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 textareas = Array.from(document.querySelectorAll('textarea')); var editTA = textareas.find(function(t){ return t.value && t.value.includes('첫 번째 테스트'); }); if(editTA){ var setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,'value').set; setter.call(editTA, '수정된 첫 번째 댓글입니다.'); editTA.dispatchEvent(new Event('input',{bubbles:true})); editTA.dispatchEvent(new Event('change',{bubbles:true})); return 'filled edit textarea'; } var inputs = Array.from(document.querySelectorAll('input[type=\"text\"]')); var editInput = inputs.find(function(i){ return i.value && i.value.includes('첫 번째 테스트'); }); if(editInput){ var inputSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set; inputSetter.call(editInput, '수정된 첫 번째 댓글입니다.'); editInput.dispatchEvent(new Event('input',{bubbles:true})); editInput.dispatchEvent(new Event('change',{bubbles:true})); return 'filled edit input'; } var editables = document.querySelectorAll('[contenteditable=\"true\"]'); for(var i=0; i 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": 47, - "name": "댓글 수 업데이트 확인 (1개)", - "action": "verify_element", - "target": "text=/댓글 \\(1\\)/", - "verification": { - "exists": true - } + "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": 48, + "step": 47, "name": "게시글 수정 버튼 클릭", "action": "click", "target": "button:has-text('수정')" }, { - "step": 49, + "step": 48, "name": "게시글 수정 페이지 진입 확인", "action": "verify_url", "verification": { @@ -491,7 +495,7 @@ } }, { - "step": 50, + "step": 49, "name": "제목 필드에 기존 값 확인", "action": "verify_input_value", "target": "input#title", @@ -500,39 +504,33 @@ } }, { - "step": 51, + "step": 50, "name": "제목 수정", "action": "fill", "target": "input#title", "value": "E2E 테스트 게시글 (수정됨)" }, { - "step": 52, + "step": 51, "name": "내용 수정", "action": "fill", "target": "textarea#content", "value": "수정된 내용입니다. E2E 자동화 테스트를 위한 게시글입니다." }, { - "step": 53, - "name": "비밀글 체크", - "action": "check", - "target": "input#isSecret" - }, - { - "step": 54, + "step": 52, "name": "현재 URL 저장 (수정 전)", "action": "save_url", "variable": "url_before_update" }, { - "step": 55, + "step": 53, "name": "수정 저장 버튼 클릭", "action": "click", "target": "button[type='submit']:has-text('저장'), button:has-text('수정')" }, { - "step": 56, + "step": 54, "name": "게시글 수정 완료 (URL 안정성 검증)", "action": "verify_url_stability", "critical": true, @@ -545,7 +543,7 @@ "notes": "필수 검증 #2: 수정 후 상세 페이지로 돌아가야 하며 404 에러 페이지가 나오면 안 됨" }, { - "step": 57, + "step": 55, "name": "수정된 제목 확인", "action": "verify_text", "verification": { @@ -554,7 +552,7 @@ } }, { - "step": 58, + "step": 56, "name": "수정된 내용 확인", "action": "verify_text", "verification": { @@ -563,13 +561,13 @@ } }, { - "step": 59, + "step": 57, "name": "목록으로 이동 버튼 클릭", "action": "click", "target": "button:has-text('목록으로')" }, { - "step": 60, + "step": 58, "name": "목록 페이지 복귀 확인", "action": "verify_url", "verification": { @@ -577,7 +575,7 @@ } }, { - "step": 61, + "step": 59, "name": "수정된 게시글 목록 확인", "action": "verify_text", "verification": { @@ -586,13 +584,13 @@ } }, { - "step": 62, + "step": 60, "name": "게시글 클릭하여 상세 진입", "action": "click", "target": "text=E2E 테스트 게시글 (수정됨)" }, { - "step": 63, + "step": 61, "name": "상세 페이지 진입 확인", "action": "verify_url", "verification": { @@ -600,7 +598,7 @@ } }, { - "step": 64, + "step": 62, "name": "조회수 증가 확인", "action": "verify_element", "target": "svg.lucide-eye ~ text", @@ -610,13 +608,13 @@ } }, { - "step": 65, + "step": 63, "name": "게시글 삭제 버튼 클릭", "action": "click", "target": "button:has-text('삭제')" }, { - "step": 66, + "step": 64, "name": "삭제 확인 다이얼로그 표시 확인", "action": "verify_dialog", "verification": { @@ -625,19 +623,19 @@ } }, { - "step": 67, + "step": 65, "name": "현재 URL 저장 (삭제 전)", "action": "save_url", "variable": "url_before_delete" }, { - "step": 68, + "step": 66, "name": "삭제 확인 버튼 클릭", "action": "click", "target": "button:has-text('삭제'):last-of-type" }, { - "step": 69, + "step": 67, "name": "게시글 삭제 완료 (URL 안정성 검증)", "action": "verify_url_stability", "critical": true, @@ -650,7 +648,7 @@ "notes": "필수 검증 #2: 삭제 후 목록 페이지로 돌아가야 하며 404 에러 페이지가 나오면 안 됨" }, { - "step": 70, + "step": 68, "name": "목록 페이지 복귀 확인", "action": "verify_url", "verification": { @@ -658,7 +656,7 @@ } }, { - "step": 71, + "step": 69, "name": "삭제된 게시글 목록에서 제거 확인", "action": "verify_text", "verification": { @@ -667,48 +665,7 @@ } }, { - "step": 72, - "name": "페이지네이션 존재 확인 (조건부)", - "action": "verify_element", - "target": "nav[aria-label='pagination']", - "verification": { - "exists": "if_data_gt_10", - "notes": "10개 이상일 때만 페이지네이션 표시" - } - }, - { - "step": 73, - "name": "체크박스 선택 테스트 (첫 번째 항목)", - "action": "check_nth", - "target": "input[type='checkbox']", - "nth": 1, - "verification": { - "notes": "0번은 전체 선택, 1번은 첫 번째 데이터" - } - }, - { - "step": 74, - "name": "체크박스 선택 해제", - "action": "uncheck_nth", - "target": "input[type='checkbox']", - "nth": 1 - }, - { - "step": 75, - "name": "전체 선택 체크박스 클릭", - "action": "check_nth", - "target": "input[type='checkbox']", - "nth": 0 - }, - { - "step": 76, - "name": "전체 선택 해제", - "action": "uncheck_nth", - "target": "input[type='checkbox']", - "nth": 0 - }, - { - "step": 77, + "step": 70, "name": "콘솔 에러 확인", "action": "verify_console", "verification": { @@ -786,7 +743,8 @@ "조회수는 게시글 상세 조회 시마다 증가합니다.", "작성자만 수정/삭제 버튼이 표시됩니다.", "댓글도 작성자만 수정/삭제 가능합니다.", - "비밀글 체크 시 is_secret=true로 전송됩니다.", + "댓글 수정은 인라인 편집 방식 (별도 textarea 삽입이 아닌 기존 요소 내 편집)", + "댓글 삭제 시 확인 다이얼로그가 표시될 수 있음", "게시글 내용은 HTML로 저장되며 dangerouslySetInnerHTML로 렌더링됩니다." ] }