feat(docs): 스크린샷 가이드 — 인증 후 세부 화면 캡처 추가

로그인 전 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0162BaZnrbiYgWc9JJMZ9Czb
This commit is contained in:
이정수 2026-06-30 14:22:20 +09:00
parent 9098325e7c
commit 5dd639fd7a
2 changed files with 143 additions and 49 deletions

View File

@ -12,54 +12,51 @@ pip3 install playwright
# 브라우저 다운로드 불필요 — 로컬 Chrome 사용 # 브라우저 다운로드 불필요 — 로컬 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 ```bash
python3 docs/development/screenshot-guide.py 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 무시) - 헤드리스 모드는 다크 테마 미적용 (시스템 prefers-color-scheme 무시)
- 로그인 필요 페이지는 세션 없이 리다이렉트됨
- `/tmp/bibimbap-screenshots/` 는 재부팅 시 삭제 — 영구 저장 필요하면 경로 변경
- Claude Design에 넘길 때 "다크 테마로 리디자인" 프롬프트에 명시 - Claude Design에 넘길 때 "다크 테마로 리디자인" 프롬프트에 명시
- 로그인 실패(잘못된 계정 정보) 시 인증 페이지는 건너뛰고 비인증 7장만 저장됨

View File

@ -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 os
import sys
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
OUT_DIR = "/tmp/bibimbap-screenshots" OUT_DIR = "/tmp/bibimbap-screenshots"
BASE_URL = "http://localhost:8080" BASE_URL = "http://localhost:8080"
pages = [ EMAIL = os.environ.get("SCREENSHOT_EMAIL", "")
("/", "01-home.png"), PASSWORD = os.environ.get("SCREENSHOT_PASSWORD", "")
# 비인증 페이지
ANON_PAGES = [
("/", "01-home-anon.png"),
("/login", "02-login.png"), ("/login", "02-login.png"),
("/signup", "03-signup.png"), ("/signup", "03-signup.png"),
("/posts", "04-posts-list.png"), ("/posts", "04-posts-list.png"),
("/recruit", "05-recruit-list.png"), ("/recruit", "05-recruit-list.png"),
("/game/3", "06-game-detail.png"), ("/game/3", "06-game-detail-anon.png"),
("/terms", "07-terms.png"), ("/terms", "07-terms.png"),
] ]
os.makedirs(OUT_DIR, exist_ok=True) 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: with sync_playwright() as p:
browser = p.chromium.launch( browser = p.chromium.launch(
executable_path=CHROME, executable_path=CHROME,
headless=True, headless=True,
args=["--no-sandbox"] args=["--no-sandbox"],
) )
page = browser.new_page(viewport={"width": 1400, "height": 900}) page = browser.new_page(viewport={"width": 1400, "height": 900})
for path, filename in pages: print("── 비인증 페이지 ──────────────────────────────")
page.goto(f"{BASE_URL}{path}", wait_until="networkidle") for path, filename in ANON_PAGES:
page.screenshot(path=os.path.join(OUT_DIR, filename), full_page=True) capture(page, path, filename)
print(f"{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() browser.close()