From d6137b362dd987b958310fee0f9544e53e3e22a8 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 26 Oct 2026 09:39:57 +0900 Subject: [PATCH] =?UTF-8?q?fix(A05=20=EA=B0=80=EC=9D=B4=EB=93=9C=20?= =?UTF-8?q?=EC=BA=A1=EC=B2=98):=20=EC=9C=84=ED=97=98=20=EB=8B=A8=EC=B6=94?= =?UTF-8?q?=20=EA=B2=80=EC=82=AC=EA=B0=80=20=EC=84=A0=ED=83=9D=EC=9E=90=20?= =?UTF-8?q?=EA=B8=80=EC=9E=90=EB=A7=8C=20=EB=B4=84(text=3D=20=C2=B7=20:has?= =?UTF-8?q?-text)=20=C2=B7=20class=20=C2=B7=20id=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8=20=C2=B7=20=EC=8B=9C=ED=97=98=20=EB=8D=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- A05_EduDetail/A05_EduDetail_Guide_Capture.py | 20 ++++++++++++++++---- resources/tester/test_539_guide_capture.py | 13 ++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/A05_EduDetail/A05_EduDetail_Guide_Capture.py b/A05_EduDetail/A05_EduDetail_Guide_Capture.py index 786d10319..1277d7cf3 100644 --- a/A05_EduDetail/A05_EduDetail_Guide_Capture.py +++ b/A05_EduDetail/A05_EduDetail_Guide_Capture.py @@ -49,8 +49,9 @@ frame 은 callouts 항목에도 씀 지킴 - - [저장] · [확정] · [초기화] · [적용] · [삭제] · 메일 보내기 등 위험 단추 누름은 거절 — 선택자 · eval 글(목록 - 읽을 때) · 실제 요소 글자(누르기 직전) 둘 다 검사 · [취소] · [닫기] 는 통과 + - [저장] · [확정] · [초기화] · [적용] · [삭제] · 메일 보내기 등 위험 단추 누름은 거절 — 선택자 속 글자 + (`text=` · `:has-text("…")`만 · class · id 이름은 안 봄) · eval 글 · 실제 요소 글자(누르기 직전) 검사 · + [취소] · [닫기] 는 통과 - 확인창을 찍을 때만 `"confirm": true` 로 그 단추를 누름 — 그 장면은 after 에 누름 단계가 있어야 함([취소]) - 페이지의 /api 쓰기 요청(PUT · PATCH · DELETE · allow_post 밖 POST)은 모두 끊음 · 끊은 요청은 표에 적음 - 브라우저 확인창(window.confirm)은 늘 취소 · 새 컨텍스트라 사용자 브라우저 · 저장소와 안 섞임 @@ -132,6 +133,17 @@ def is_danger(text: str) -> bool: return bool(text) and bool(DANGER_RE.search(text)) and not SAFE_RE.search(text) +SEL_TEXT_RE = re.compile( + r"""text\s*=\s*(?:"[^"]*"|'[^']*'|[^>]+)|:(?:has-)?text(?:-is|-matches)?\(\s*(?:"[^"]*"|'[^']*')""", + re.I, +) + + +def sel_text(sel: str) -> str: + """선택자 속 글자 부분만(`text=` · `:has-text("…")`) — class · id 이름은 뺌.""" + return " ".join(SEL_TEXT_RE.findall(sel or "")) + + def _check_steps(steps: Any, where: str) -> tuple[list[str], bool]: """단계 목록 검사 → (잘못, confirm 단계 있음).""" if steps is None: @@ -166,11 +178,11 @@ def _check_steps(steps: Any, where: str) -> tuple[list[str], bool]: if kind != "click": errs.append(f"{at} confirm 은 click 에만") confirm = True - elif kind in ("click", "eval") and is_danger(val): + elif is_danger(val if kind == "eval" else sel_text(val)) and kind in ("click", "eval"): errs.append( f"{at} 위험 단추 거절({kind}: {val[:40]}) — 확인창만이면 confirm + after [취소]" ) - if kind == "press" and is_danger(st.get("sel", "")): + if kind == "press" and is_danger(sel_text(st.get("sel", ""))): errs.append(f"{at} 위험 요소에서 키 거절") return errs, confirm diff --git a/resources/tester/test_539_guide_capture.py b/resources/tester/test_539_guide_capture.py index d94fee832..3d32f2297 100644 --- a/resources/tester/test_539_guide_capture.py +++ b/resources/tester/test_539_guide_capture.py @@ -80,11 +80,9 @@ def test_bad_fields(bad): [ "button:has-text('저장')", "text=확정", - ".reset-btn", "button:has-text('초기화')", "button:has-text('적용')", "button:has-text('메일 보내기')", - "#save", "button:has-text('삭제')", ], ) @@ -101,7 +99,16 @@ def test_danger_eval_rejected(): @pytest.mark.parametrize( - "sel", ["button:has-text('취소')", "text=닫기", ".cancel", "button:has-text('내보내기')"] + "sel", + [ + "button:has-text('취소')", + "text=닫기", + ".cancel", + "button:has-text('내보내기')", + ".d04-confirm__list", + ".ui-confirm__cancel", + "#save-btn button:has-text('닫기')", + ], ) def test_safe_click_passes(sel): assert G.check_scene(_scene(before=[{"click": sel}]), "B05") == []