fix(A05 가이드 캡처): 위험 단추 검사가 선택자 글자만 봄(text= · :has-text) · class · id 이름 제외 · 시험 덧
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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") == []
|
||||
|
||||
Reference in New Issue
Block a user