From 5dd639fd7a41e5d7ad6a986c9ae52bc27bcc6d57 Mon Sep 17 00:00:00 2001 From: art Date: Tue, 30 Jun 2026 14:22:20 +0900 Subject: [PATCH] =?UTF-8?q?feat(docs):=20=EC=8A=A4=ED=81=AC=EB=A6=B0?= =?UTF-8?q?=EC=83=B7=20=EA=B0=80=EC=9D=B4=EB=93=9C=20=E2=80=94=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=20=ED=9B=84=20=EC=84=B8=EB=B6=80=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EC=BA=A1=EC=B2=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 로그인 전 7장만 캡처하던 스크립트를 확장: - SCREENSHOT_EMAIL/PASSWORD env var로 인증 세션 획득 - 로그인 후 홈·프로필·게시글폼·모집폼 캡처 - game-detail 리뷰·댓글 composer hidden 제거 후 캡처 - AJAX 로그인 흐름(fetch + window.location) wait_for_url 대응 Co-Authored-By: Claude Sonnet 4.6 (1M context) Claude-Session: https://claude.ai/code/session_0162BaZnrbiYgWc9JJMZ9Czb --- docs/development/screenshot-guide.md | 79 +++++++++---------- docs/development/screenshot-guide.py | 113 +++++++++++++++++++++++++-- 2 files changed, 143 insertions(+), 49 deletions(-) diff --git a/docs/development/screenshot-guide.md b/docs/development/screenshot-guide.md index 1110d49..e0fa93c 100644 --- a/docs/development/screenshot-guide.md +++ b/docs/development/screenshot-guide.md @@ -12,54 +12,51 @@ pip3 install playwright # 브라우저 다운로드 불필요 — 로컬 Chrome 사용 ``` -## 스크립트 - -```python -import os -from playwright.sync_api import sync_playwright - -CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" -OUT_DIR = "/tmp/bibimbap-screenshots" -BASE_URL = "http://localhost:8080" - -pages = [ - ("/", "01-home.png"), - ("/login", "02-login.png"), - ("/signup", "03-signup.png"), - ("/posts", "04-posts-list.png"), - ("/recruit", "05-recruit-list.png"), - ("/game/3", "06-game-detail.png"), - ("/terms", "07-terms.png"), -] - -os.makedirs(OUT_DIR, exist_ok=True) - -with sync_playwright() as p: - browser = p.chromium.launch( - executable_path=CHROME, - headless=True, - args=["--no-sandbox"] - ) - page = browser.new_page(viewport={"width": 1400, "height": 900}) - - for path, filename in pages: - page.goto(f"{BASE_URL}{path}", wait_until="networkidle") - page.screenshot(path=os.path.join(OUT_DIR, filename), full_page=True) - print(f"✓ {filename}") - - browser.close() -``` - ## 실행 +### 비인증 페이지만 (로그인 불필요) + ```bash python3 docs/development/screenshot-guide.py -# 결과: /tmp/bibimbap-screenshots/*.png ``` +7장 캡처: 홈(비로그인), 로그인, 회원가입, 게시글 목록, 모집 목록, 게임 상세(비로그인), 이용약관 + +### 전체 캡처 (로그인 포함 세부 화면) + +```bash +SCREENSHOT_EMAIL=user@example.com SCREENSHOT_PASSWORD=yourpw \ + python3 docs/development/screenshot-guide.py +``` + +추가 7장 캡처: + +| 파일 | 화면 | +|---|---| +| `08-home-loggedin.png` | 홈 (로그인 후 — 내비게이션 변경) | +| `09-profile.png` | 프로필 페이지 | +| `10-posts-form.png` | 게시글 작성 폼 | +| `11-recruit-form.png` | 모집 작성 폼 | +| `12-game-detail-loggedin.png` | 게임 상세 (로그인 후 전체 뷰) | +| `13-game-review-form.png` | 리뷰 작성 폼 (노출 상태) | +| `14-game-comment-form.png` | 댓글 작성 폼 (노출 상태, 섹션 포커스) | + +## 캡처 전략 + +**비인증 → 인증 비교**: 01·06번(비인증)과 08·12번(인증) 쌍으로 로그인 전후 차이를 비교할 수 있다. + +**폼 상태 강제 노출**: 리뷰/댓글 composer는 서버 API 응답(로그인 상태, 기존 리뷰 여부) 후 JS가 제어한다. 스크립트는 3초 내 자연 노출을 기다리고, 실패하면 `removeAttribute('hidden')`으로 강제 노출해 캡처한다. 실제 동작과 동일한 HTML 구조를 캡처한다. + +## 출력 위치 + +``` +/tmp/bibimbap-screenshots/*.png +``` + +재부팅 시 삭제됨. 영구 저장 필요 시 `OUT_DIR` 변수 변경. + ## 주의 - 헤드리스 모드는 다크 테마 미적용 (시스템 prefers-color-scheme 무시) -- 로그인 필요 페이지는 세션 없이 리다이렉트됨 -- `/tmp/bibimbap-screenshots/` 는 재부팅 시 삭제 — 영구 저장 필요하면 경로 변경 - Claude Design에 넘길 때 "다크 테마로 리디자인" 프롬프트에 명시 +- 로그인 실패(잘못된 계정 정보) 시 인증 페이지는 건너뛰고 비인증 7장만 저장됨 diff --git a/docs/development/screenshot-guide.py b/docs/development/screenshot-guide.py index 46f10ed..4c828ff 100644 --- a/docs/development/screenshot-guide.py +++ b/docs/development/screenshot-guide.py @@ -1,34 +1,131 @@ +""" +bibimbap 스크린샷 캡처 스크립트 + +비인증 페이지 + 인증 후 세부 화면 상태 모두 캡처. + +사용법: + # 비인증 페이지만 + python3 docs/development/screenshot-guide.py + + # 인증 포함 전체 (로그인 계정 필요) + SCREENSHOT_EMAIL=user@example.com SCREENSHOT_PASSWORD=pw \ + python3 docs/development/screenshot-guide.py +""" import os +import sys from playwright.sync_api import sync_playwright CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" OUT_DIR = "/tmp/bibimbap-screenshots" BASE_URL = "http://localhost:8080" -pages = [ - ("/", "01-home.png"), +EMAIL = os.environ.get("SCREENSHOT_EMAIL", "") +PASSWORD = os.environ.get("SCREENSHOT_PASSWORD", "") + +# 비인증 페이지 +ANON_PAGES = [ + ("/", "01-home-anon.png"), ("/login", "02-login.png"), ("/signup", "03-signup.png"), ("/posts", "04-posts-list.png"), ("/recruit", "05-recruit-list.png"), - ("/game/3", "06-game-detail.png"), + ("/game/3", "06-game-detail-anon.png"), ("/terms", "07-terms.png"), ] os.makedirs(OUT_DIR, exist_ok=True) + +def capture(page, path, filename, *, full_page=True): + page.goto(f"{BASE_URL}{path}", wait_until="networkidle") + page.screenshot(path=os.path.join(OUT_DIR, filename), full_page=full_page) + print(f" ✓ {filename}") + + +def do_login(page): + """로그인 폼 제출 후 세션 획득. CSRF 토큰은 폼에서 추출.""" + page.goto(f"{BASE_URL}/login", wait_until="networkidle") + page.fill("input[name='email']", EMAIL) + page.fill("input[name='password']", PASSWORD) + page.click("button[type='submit']") + page.wait_for_url(lambda url: "/login" not in url, timeout=8000) + + +def capture_review_form(page): + """게임 상세 — 리뷰 작성 폼 노출 상태.""" + page.goto(f"{BASE_URL}/game/3", wait_until="networkidle") + # JS가 로그인 상태 확인 후 composer를 보여줄 때까지 대기 (최대 3초) + try: + page.wait_for_function( + "document.getElementById('game-review-composer') && " + "!document.getElementById('game-review-composer').hasAttribute('hidden')", + timeout=5000, + ) + except Exception: + # JS가 숨긴 채로 두면 강제 노출 (스크린샷 전용) + page.evaluate( + "document.getElementById('game-review-composer')?.removeAttribute('hidden')" + ) + page.screenshot( + path=os.path.join(OUT_DIR, "13-game-review-form.png"), full_page=False + ) + print(" ✓ 13-game-review-form.png") + + +def capture_comment_form(page): + """게임 상세 — 댓글 작성 폼 노출 상태.""" + page.goto(f"{BASE_URL}/game/3", wait_until="networkidle") + try: + page.wait_for_function( + "document.getElementById('game-comment-composer') && " + "!document.getElementById('game-comment-composer').hasAttribute('hidden')", + timeout=5000, + ) + except Exception: + page.evaluate( + "document.getElementById('game-comment-composer')?.removeAttribute('hidden')" + ) + # 댓글 섹션으로 스크롤 + page.evaluate( + "document.getElementById('game-comment-composer')?.scrollIntoView({block:'center'})" + ) + page.screenshot( + path=os.path.join(OUT_DIR, "14-game-comment-form.png"), full_page=False + ) + print(" ✓ 14-game-comment-form.png") + + with sync_playwright() as p: browser = p.chromium.launch( executable_path=CHROME, headless=True, - args=["--no-sandbox"] + args=["--no-sandbox"], ) page = browser.new_page(viewport={"width": 1400, "height": 900}) - for path, filename in pages: - page.goto(f"{BASE_URL}{path}", wait_until="networkidle") - page.screenshot(path=os.path.join(OUT_DIR, filename), full_page=True) - print(f"✓ {filename}") + print("── 비인증 페이지 ──────────────────────────────") + for path, filename in ANON_PAGES: + capture(page, path, filename) + + if EMAIL and PASSWORD: + print("\n── 로그인 후 세부 화면 ─────────────────────────") + try: + do_login(page) + print(" ✓ 로그인 성공") + + capture(page, "/", "08-home-loggedin.png") + capture(page, "/profile", "09-profile.png") + capture(page, "/posts/new", "10-posts-form.png") + capture(page, "/recruit/new", "11-recruit-form.png") + capture(page, "/game/3", "12-game-detail-loggedin.png") + capture_review_form(page) + capture_comment_form(page) + + except Exception as e: + print(f" ✗ 로그인 실패 또는 인증 페이지 캡처 오류: {e}", file=sys.stderr) + else: + print("\n※ SCREENSHOT_EMAIL / SCREENSHOT_PASSWORD 미설정 — 비인증 페이지만 캡처됨") + print(" 전체 캡처: SCREENSHOT_EMAIL=... SCREENSHOT_PASSWORD=... python3 docs/development/screenshot-guide.py") browser.close()