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()