QA 브라우저 시나리오 — staging.yetdapay.com 전표면 스윕
읽기 안내
공개·판매자·관리자 화면의 브라우저 검사 시나리오와 Fixture를 정리합니다.
시나리오가 문서에 있다는 사실과 실제 실행·통과했다는 증거를 구분하세요.
작성: INDEPENDENT_QA · 2026-11-01 · 대상: https://staging.yetdapay.com (staging API, mock 결제) 증거: Playwright chromium 스윕 스크립트(
fe/e2e/qa-sweep/), D1 직접 조회(yetda-d1-staging), API curl 픽스처: sellerseller_e30305fda8e2(slugs-e30305fda8e2), productprod_661d517f1e50/prod_ae0f413d13c6, projectproj_2f071a8d5455, 계정 qa2-buyer/qa2-seller/[email protected]
A. PUBLIC (비로그인)
A1. 랜딩
- Given 익명 방문자가
/에 진입한다 - When 내비(주요 기능/이용 방법/FAQ), 로그인, 무료로 시작하기, 기능 살펴보기, 법적 링크를 클릭한다
- Then 각각
#features/#how/#faq앵커,/login,/signup,/legal/privacy,/legal/terms로 이동한다 ✅ (실측 통과) - Then 페이지
<title>과 FAQ 본문의 서비스명이 "YetdaPay"로 일관된다 ❌ (D3: "Nougat DevPay" 잔존)
A2. 회원가입
- Given
/signup에서 이메일/비밀번호/표시 이름과 약관 동의를 입력한다 - When 비밀번호가 8자 미만이거나 약관 미동의 시 "비밀번호는 8자 이상이어야 합니다"/"필수 약관에 동의해 주세요" 오류를 표시한다 ✅
- When 유효 입력 후 제출하면
/onboarding으로 이동한다 ✅ - Then 온보딩에서 유형 선택→계속→이름/브랜드 입력→가입 완료로 판매자가 생성된다 ❌ (D24: UiCard #footer 슬롯 누락으로 "계속" 버튼이 렌더되지 않아 진행 불가 — UI 경로 전면 차단)
A3. 로그인
- Given
/login에서 유효 자격증명을 제출한다 - Then
/dashboard로 이동하며 수 초 내 판매 데이터가 표시된다 ✅ (초기 "서버에 연결 중…"은 pullAll 대기 구간) - When 잘못된 비밀번호면 "invalid credentials"를 표시하고 로그인 상태로 유지한다 ✅
- Given
/login?redirect=/dashboard/접근 후 로그인하면 원래 목적지로 복귀한다 ✅
A4. 공개 스토어/상품
- Given 공개된 스토어
/store/[slug]— 상품 목록·가격·SANDBOX 배지가 표시된다 ✅ - When 존재하지 않는 slug면 "스토어를 찾을 수 없습니다"와 홈 링크를 표시한다 ✅ (API 404 올바름)
- When 상품 카드를 클릭하면
/store/[slug]/p/[productId]상세로 이동한다 ✅ - Then "구매하기/구독 시작하기" →
/checkout/[id]에서 결제 폼이 표시된다 ❌ (D1: 익명·buyer 모두 "상품을 찾을 수 없습니다" — 로컬 동기화 데이터 의존) - Then 스토어 푸터 문구가 YetdaPay 브랜딩이다 ❌ (D3: "Nougat DevPay 프론트엔드 PoC")
A5. 결제/주문 완료
- Given 결제 완료 링크
/checkout/complete/[orderId] - Then 구매자는 주문번호·라이선스 키·구독 정보를 확인한다 ❌ (D27: 구매자 본인에게도 "주문을 찾을 수 없습니다" — seller-scoped 데이터 의존)
- Given
/checkout/item/[itemId]잘못된 아이템 — "상품을 찾을 수 없습니다" ✅
A6. 후원
- Given
/donate/[slug]— 슬러그의 스토어 정보와 금액 프리셋이 표시된다 ❌ (D8: slug 무시, 로컬 seller 상태 사용; 익명·buyer는 버튼 무반응) - Then 금액 칩이 자연스러운 한국어다 ❌ (D4: "10천원/30천원/50천원")
A7. 라이선스 검증 (공개)
- Given
/license/verify는 공개 경로다 (middleware publicPrefixes) - Then 익명·구매자도 검증 폼을 사용할 수 있다 ❌ (D2: dashboard 레이아웃 블록 화면 — 익명 영구 "서버에 연결 중", buyer는 10×403 후 "서버와 연결할 수 없습니다")
- Then 키+기기ID+상품ID를 입력하면 서버 검증·기기 자동등록·상품 일치 검사가 동작한다 ❌ (D16: 배포 워커는 key만 읽음 — product_id/device_id 무시, PRODUCT_MISMATCH/자동등록 미구현; 참고: API 자체는 공개·키 정규화 정상)
- 판매자로 접근 시 "데모 키 채우기"로 발급된 키를 채운다 ❌ (D17: GET /licenses가 key 미반환 → undefined → TypeError)
A8. 호스티드 통합 페이지 /h/[publicId]
- Then 유효한 publicId로 매니페스트·후원·피드백 폼이 표시된다 ❌ (D9: 배포 워커에
/api/v1/public/integrations/*라우트 자체가 없고 D1에 테이블 없음 — 모든 /h/* "연동을 찾을 수 없습니다")
A9. 데모 스토어 /demo/[projectId]
- Then 프로젝트·아이템·플레이어 재화가 표시된다 ✅
- Then 아이템 구매가 Mock 결제→지급까지 동작한다 ⚠️ (grant_unit 빈 아이템은 409 INVALID_UNIT으로 구매 불가 — D26; 유효 아이템은 API 실측 성공)
A10. 법적 페이지
/legal/privacy,/legal/terms— "검토 대상" 고지와 함께 렌더된다 ✅ (내용 자체가 검토 대상 표기 — 제품 의도 확인 필요)
B. SELLER 콘솔 (판매자 JWT)
B1. 접근 제어
- Given 판매자 역할 — 모든 seller 라우트가 열린다 ✅
- Given buyer 역할 —
/dashboard//products등은 403 "판매자 권한이 필요합니다" ❌→올바른 차단이나 CTA가 403으로 되돌아감 (D6) - Given 익명 —
/dashboard등은/login?redirect=…로 이동한다 ✅; 단/license/verify는 D2로 미차단
B2. 대시보드
- KPI(결제금액/프로젝트/예상정산/라이선스/구독)·최근 주문·CTA 링크가 실데이터로 표시된다 ✅
- Then 주문 시각이 정확하다 ❌ (D19: UTC naive datetime이 로컬로 파싱돼 9시간 오차 — "9시간 전")
B3. 프로젝트/아이템
- 목록·등록·상세(개요/아이템/지급 내역/AI 연동 탭) ✅
- Then PATCH /items/:id가 부분 갱신이다 ❌ (D25: 생략 필드 초기화 — price 1000→0 실측)
B4. 상품
- 목록·등록·상세(유형 전환, 기능/Entitlement/라이선스 정책 편집) ✅ (UI 저장 시 product 생성 확인 — 토스트/피드백은 미확인)
- Then 상품 상세 직접 진입이 즉시 표시된다 ❌ (D13: 자체 pullAll과 플러그인 sync가 경합해 generation 무효화 → 5~10초 가짜 "서버와 연결할 수 없습니다")
B5. 주문/환불
- 목록·필터 탭·상세(주문 정보, 멱등키, 연결 라이선스/구독) ✅
- When Mock 환불 → 확인 → Then 주문이 refunded, 라이선스 회수, 구독 취소 ✅(DB 확인) — 단 화면은 60초까지 "결제 완료"로 남고 확인 토스트 없음 ❌ (D14)
B6. 라이선스/구독
- 목록·상세(키 복사, 일시정지/회수, 만료일 변경, 기기 등록) ✅
- Then 수동 발급은 검증된 판매자에게만 허용된다 ✅ (SELLER_NOT_VERIFIED → admin 승인 후 허용)
- Then 상품의 기기 한도가 라이선스에 반영된다 ❌ (D18: product maxDevices=2 → 발급 max_devices=3; 스토어엔 "최대 2대" 표시 — 구매자 약속과 실제 불일치)
- 구독 시뮬레이션 메뉴(갱신/실패/만료/취소) ✅
B7. 정산
- 미정산 매출·수수료·예상 정산액 계산 표시 ✅
- When 미승인 판매자가 정산 실행 → 403 거부 ✅ — 단 사용자에게 오류 미표시 ❌ (D21)
- When 승인 후 실행 → 정산 생성(₩5,000→₩4,590, 환불 주문 제외) ✅
B8. 피드백/위젯/후원 버튼/AI 연동
- 피드백 목록·필터·빈 상태 ✅; 위젯 목록·설정·연동 코드 ✅
- Then 새 위젯 생성이 동작한다 ❌ (D15: FE boolean ↔ BE int 불일치로 400 "invalid json")
- Then 생성된 임베드/AI 코드가 내 스토어를 가리킨다 ❌ (D7: sellerSlug 기본값 'nougat' → 남의 스토어로 링크 생성; /widgets/embed/* 는 placeholder 404 — D28)
- Then 생성물 브랜딩이 YetdaPay다 ❌ (D3: "Nougat DevPay" 주석/프롬프트 잔존)
B9. 설정/정산 등록
- 판매자 정보·스토어·API Key·Webhook·라이선스 정책·Provider·테마 탭 ✅
- Then 정산 등록(심사 신청)을 UI로 제출한다 ❌ (D24: UiCard #footer 누락으로 "심사 신청" 버튼 부재)
- "시연 데이터 초기화"(localStorage 초기화)는 apiMode에서 실질 무동작 — 의도 확인 필요 ⚠️
B10. 명령 팔레트/공통
- ⌘K 검색·이동·액션 ✅; 사이드바 접기·내 스토어 보기·로그아웃 ✅
- Then 비관리자에게 운영 콘솔 항목이 노출되지 않는다 ❌ (D20: seller 팔레트에 "운영자 콘솔" → 403)
C. ADMIN (플랫폼 운영자 JWT)
C1. 접근 제어
- buyer/seller→/admin = 403 "관리자 권한이 필요합니다" ✅
- 운영자 전용 데이터(사용자·판매자·심사·KPI·감사) 표시 ✅
C2. 심사/계정 관리
- When 판매자 심사 승인 → verification.status=APPROVED, 정산·수동발급 해금 ✅
- When 사용자 비활성 → 기존 JWT 즉시·영구 무효(401), 재활성 후에도 재로그인 필요 ✅ (보안 기대 동작)
C3. 판매자 컨텍스트 (act-as)
- Given 운영자가 컨텍스트 스위처에서 판매자를 선택한다
- Then "판매자 콘솔" 버튼이 활성화되고 해당 판매자 스코프로 콘솔이 열린다 ❌ (D10: 배포 env에 ALLOW_ACT_AS_SELLER 없음 → 헤더 무시 → admin 무스코프 목록 + 404 폭주 → 영구 블록)
- Then 스위처에서 모든 판매자를 선택할 수 있다 ❌ (D11: 스크롤/검색 없는 드롭다운 — 뷰포트 밖 항목 클릭 불가)
C4. 크로스 테넌트
- admin이 seller 라우트에서 전 테넌트 데이터를 읽는다 — 현재는 act-as 미적용 시의 부수 효과로 관측; act-as 의도(특정 판매자로서)와 불일치 → D10과 함께 보고 ⚠️
MOBILE-ADMIN (모바일 뷰포트 스윕 — ADMIN 표면 전용)
실행: 2026-11-02, staging.yetdapay.com, Playwright chromium — 375×812 / 390×844 (+768×1024 태블릿 스팟체크), admin JWT(
usr_00dadc7fc1b0), act-as 대상seller_beta(Beta Studio). 스크립트fe/e2e/qa-sweep/mobile/admin-mobile.mjs, 증거/tmp/qa-mobile/shots/+/tmp/qa-mobile/mobile-admin-findings.json. 이전 sweep 이후 D10(act-as)·D11(드롭다운 검색)이 스테이징에 반영되어 있음을 확인.
MA1. /admin 레이아웃 @375/390
- Given admin JWT로 모바일(375px)에서 /admin을 연다
- Then 페이지가 가로 스크롤 없이 렌더된다 ❌ (D29: document.scrollWidth=619 > 375 — 헤더 고정폭 플렉스 행에 모바일 축약 없음 → +244px 오버플로우; 390px에서도 +229px)
- Then 헤더의 모든 컨트롤(판매자 컨텍스트·판매자 콘솔·테마·로그아웃)이 화면 안에 있다 ❌ (D29: 판매자 콘솔 x=467, 로그아웃 x=521 — 화면 밖; 가로 스크롤로는 도달 가능하나 기본 상태에서 비노출. admin 레이아웃에 햄버거/축약 내비 없음)
- Then KPI 카드가 스태킹된다 ✅ (375에서 2×2, 768에서 4열)
- Then 사용자·거래 테이블이 페이지를 밀지 않고 카드 안에서 가로 스크롤된다 ✅ (UiTable overflow-x-auto 정상 격리)
- Then 토스트가 화면 안에 표시된다 ❌ (D32: 페이지 오버플로우 시 fixed 토스트가 619px 레이아웃 기준 right-5에 배치 → right=599, 화면 밖. D29의 파생 증상)
- Then 탭 대상이 24px 이상이다 ❌ (D33: 다크모드 토글 실측 17×36 — 화면 밖 클립의 부산물이나 24px 미만)
MA2. 판매자 컨텍스트 드롭다운 @375
- When 컨텍스트 트리거를 탭한다 → 검색 입력 + 37개 항목 스크롤 목록(h≈372, 뷰포트 내) 표시 ✅ (D11 수정 확인)
- When 'beta' 검색 → 1건 필터, 탭하여
ndp_act_as_seller=seller_beta저장 ✅ - Then 드롭다운이 뷰포트 안에 있다 ❌ (D31: 우측 1px 오버플로 — right=376 vs 375, cosmetic)
- Then "판매자 콘솔" 진입 버튼이 보인다 ❌ (D29의 직접 영향 — x=467 화면 밖, act-as 진입이 사실상 가로 스크롤 선행 필요)
MA3. act-as → /dashboard @375 (판매자 콘솔 under impersonation)
- Given
ndp_act_as_seller=seller_beta로 /dashboard를 연다 - Then impersonation 배너가 모바일에서 보인다 ✅ (sticky top-0, h≈35, "운영자 콘솔로 돌아가기"(x≤277)·"컨텍스트 해제"(right=359) 링크 모두 화면 내, 스크롤해도 유지)
- Then 페이지가 가로 스크롤 없이 렌더된다 ❌ (D30: scrollWidth=661 > 375 (+286px) —
pages/dashboard.vue의grid lg:grid-cols-2가 모바일에서 암시적 auto 단일 트랙 → UiTable min-width 640에 의해 카드가 642px로 팽창; "최근 주문"·"최근 라이선스" 카드 화면 밖) - Then 모바일 드로어가 동작한다 ✅ (햄버거 → 270px 패널 오픈, 내비·내 스토어 보기·백드롭 닫기 정상)
- Then act-as API 호출이 정상이다 ✅ (HTTP≥400 없음, pageerror 없음 — D10 수정 확인)
- 비고: D30은 seller 표면 공통 패턴(
grid lg:grid-cols-*+ min-width 테이블) — 다른 seller 라우트 전파 가능성 있음, seller-레인 스윕과 교차 확인 필요
MOBILE-PUBLIC (모바일 뷰포트 스윕 — 공개/구매자 표면 전용)
실행: 2026-11-02 (KST), staging.yetdapay.com, Playwright chromium — 375×812(iPhone UA, touch) / 390×844 (+768×1024 태블릿 스팟체크 6라우트). 익명 세션. 스크립트
fe/e2e/qa-sweep/mobile/{mobile-lib,sweep,interact}.mjs, 증거/tmp/qa-mobile/shots/+/tmp/qa-mobile/mobile-sweep.json+/tmp/qa-mobile/mobile-interact.json. 커버 라우트: / /login /signup /legal/privacy /legal/terms /store/[slug] /store/[slug]/p/[productId] /store/404-slug /donate/[slug] /checkout/[productId] /checkout/complete/[orderId] /checkout/item/[itemId] /license/verify /demo/[projectId] /h/[publicId].
MP1. 가로 오버플로우 / 뷰포트 적합성
- Given 모바일(375·390px)로 각 공개 라우트를 연다
- Then document.scrollWidth ≤ innerWidth 이다 ✅ — 16라우트 × 2뷰포트 전부 오버플로우 0, 이미지/카드/미디어 이탈 0건
- Then 태블릿(768×1024)에서도 동일하다 ✅ — 스팟체크 6라우트 오버플로우 0
- Then 고정 요소가 본문을 가리지 않는다 ✅ — sticky 헤더 64~65px(뷰포트 높이의 ~8%)만 존재, 하단 고정 요소 없음
- Then 모달/드로어가 뷰포트를 넘지 않는다 ✅ — /license/verify 모바일 드로어 270px 패널 정상, 오버플로 패널 없음
MP2. 모바일 내비게이션
- Then 랜딩(/)에서 모바일 내비 링크에 도달할 수 있다 ❌ (D37:
pages/index.vue:30nav.hidden.md:flex+ 햄버거 없음 — "주요 기능/이용 방법/FAQ" 앵커 불가; 본문은 스크롤로 도달 가능하고 푸터에 legal 링크 있어 LOW) - Then /license/verify의 메뉴가 공개 사용자에게 적합하다 ❌ (D35:
pages/license/verify.vuelayout:'dashboard' — 익명에게 판매자 콘솔 내비 13개(프로젝트/상품/정산/구독/설정…) 노출, 전부 /login?redirect= 로 게이트됨; "내 스토어 보기"는 href=/store/(빈 slug)라 /license/verify로 되돌아오는 데드 링크) - Then 스토어/결제 표면의 헤더 링크가 동작한다 ✅ — 스토어 홈·"스토어로 돌아가기"·"판매자 콘솔 →"(→/login?redirect=/dashboard) 정상
- Then 로그인/가입/legal 페이지에서 홈 복귀가 가능하다 ✅ — 브랜드 링크 존재
MP3. 라우트 렌더 + 상호작용 완주 @375
- Then 일회성·구독 상품 페이지가 렌더된다 ✅ — prod_661d517f1e50(일회성)·prod_ae0f413d13c6(구독) 정상, 구매 CTA → /checkout 이동
- Then 후원형 상품 페이지가 렌더된다 ❌ (D34: prod_6920db25d52f —
TypeError: i.plans.some is not a function+s.value?.plans.find is not a function(pages/store/[sellerSlug]/p/[productId].vue:51-55, plans 비배열 가정) → 헤더+"Powered by" 푸터만 렌더, 상품 본문 전부 소실) - Then Mock 체크아웃이 모바일에서 완주된다 ✅ — ?store= 포함: 이메일/이름 입력 → 결제수단(카드/토스/네이버/카카오) → "결제 성공" 시뮬 → "₩1,000 결제하기" → /checkout/complete/ord_277e16473c33 주문 확인서 렌더 (D1·D27 회귀 없음 — 익명 완주 확인)
- Then ?store= 없는 /checkout/{productId}도 의미 있는 화면이다 ❌ (D38: 상품 존재에도 "상품을 찾을 수 없습니다" — store 파라미터 필수 설계(
pages/checkout/[productId].vue:28주석 확인)이나 오류 문구가 오해 소지) - Then 후원 플로우가 완주된다 ✅ — 프리셋/직접 금액+이름+이메일 → "₩10,000 후원이 완료되었습니다" (D8 회귀 없음 — 익명·올바른 slug로 완주)
- Then 라이선스 검증 도구가 동작한다 ✅ — "데모 키 채우기"→라이선스 없을 때 "데모 키 없음" 안내로 그레이스풀 처리 (D17 회귀 없음 — TypeError 미발생)
- Then 존재하지 않는 리소스가 not-found 화면이다 ✅ — /store/404-slug·/checkout/complete/ord_nonexistent·/checkout/item/item_nonexistent·/demo/invalid·/h/invalid 전부 전용 화면 (D9 회귀 없음 — /h 라우트 배포됨)
- Then 회원가입 약관 링크가 열린다 ✅ — target=_blank로 /legal/* 새 탭
- Then 다크모드 토글이 전 표면에서 동작한다 ✅
- Then 폼 제출·버튼에 pageerror/console.error가 없다 ✅ — D34 단일 예외 외 전 라우트·전 클릭 무오류
MP4. 탭 대상·가독성
- Then 버튼/입력 탭 대상이 44px 이상이다 ❌ (D36: 전역 토큰 문제 — .ng-btn h=40, .input-base h=40, 아이콘 버튼(다크모드 토글 33–36px·햄버거 36px), 헤더 링크 h=19~32, 후원 프리셋 h=40, 가입 동의 체크박스 13×13, 본문 인라인 legal 링크 h=16. WCAG 2.5.8 AA(24px) 기준으로도 체크박스·헤더 링크·인라인 링크 실패)
- Then 본문 텍스트가 12px 이상이다 ❌ (D39: .ng-badge 11.5px 전역(SANDBOX/MOCK 결제/상품 타입), mock 고지·license/verify pre·라벨 11.5px)
- 비고: 스토어 이름 헤더 말줄임은
truncate의도된 ellipsis — 결함 아님
MP5. 이전 공개-표면 결함 재확인 (모바일 @375, 익명)
- D1 ✅ FIXED 관측 — 익명 checkout POST 401 없이 주문 생성·완료 화면 도달
- D2 ✅ FIXED 관측 — /license/verify가 익명에게 렌더 (블록 화면 없음; 대신 D35 레이아웃 문제로 잔존)
- D8 ✅ FIXED 관측 — /donate/[slug] slug 존중 + 익명 후원 완주
- D9 ✅ FIXED 관측 — /h/[publicId] 라우트 배포, not-found 화면 정상
- D17 ✅ FIXED 관측 — 데모 키 부재 시 안내 문구, TypeError 없음
- D27 ✅ FIXED 관측 — /checkout/complete/[orderId]가 구매자(익명 세션)에게 주문 확인 렌더
- D26 NOT_VERIFIED — 데모 프로젝트 미공개(proj_2f071a8d5455 "프로젝트를 찾을 수 없습니다"), 구매 시뮬 불가
- D3/D4/D19 — 모바일 레인 범위 밖(카피·시각) — 본 스윕에서 미판정
MOBILE-SELLER (모바일 뷰포트 스윕 — SELLER 콘솔 표면 전용)
실행: 2026-11-02 (KST), staging.yetdapay.com, Playwright chromium — 375×812 / 390×844 전수 (+768×1024 태블릿 스팟체크). seller JWT(
usr_b55014acd556→ seller_7b6b0bef65da, 검증 NOT_REGISTERED, 데이터: proj 1·prod 1·ord 2·lic 2, subscriptions/settlements/feedbacks/widgets 빈 상태). 스크립트fe/e2e/qa-sweep/mobile/{lib,sweep,interact}.mjs, 증거/tmp/qa-mobile/{sweep-report,sweep-report-768}.json+/tmp/qa-mobile/shots/{375,390,768,interact}/. 커버 라우트 23: /onboarding /dashboard /projects(+new·[id]·items·fulfillments) /products(+new·[id]) /orders(+[id]) /licenses(+[id]) /subscriptions /settlements /settings(+settlement) /feedback(+widgets) /donations/widgets(+mcp) /integrations/ai. 공개·/admin 표면은 타 레인 소관으로 미측정. ⚠️ 측정 주의: isMobile 에뮬레이션에서 가로 오버플로우 발생 시 Chromium이 레이아웃 뷰포트 자체를 확장 →innerWidth가 375→661로 늘어scrollWidth > innerWidth비교가 거짓음성이 됨. 본 스윕은innerWidth != 장치폭여부를 오버플로우 지표로 함께 사용.
MS1. 가로 오버플로우 / 뷰포트 적합성
- Given 판매자 JWT로 모바일(375·390px)로 콘솔 23라우트를 연다
- Then document가 장치폭에 맞는다 ❌ — 3라우트에서 실측 오버플로우 (스크린샷
shots/375/):- /dashboard: innerWidth=661 (+286px @375, +271px @390) — D30 재현 확인.
pages/dashboard.vue:194grid grid-cols-1 lg:grid-cols-2+ UiTable(min 640) 카드 2장. grid 아이템min-width:auto가 카드 min-content(테이블 640+패딩)로 팽창 → "최근 주문"·"최근 라이선스" 카드 화면 밖 - /products/prod_dab857d10da3: innerWidth=502 (+127px @375, +111px @390) —
pages/products/[id].vue:100동일 패턴, UiTable min-width 480 카드 2장 - /settlements: innerWidth=461 (+86px @375, +71px @390) —
pages/settlements/index.vue:69grid-cols-1 lg:grid-cols-2+ UiTable min-width 440. 카드(442px) 안의 overflow-x-auto 래퍼까지 448px로 뷰포트 이탈 — 스크롤 컨테이너 자체가 잘려 닿을 수 없는 열 발생
- /dashboard: innerWidth=661 (+286px @375, +271px @390) — D30 재현 확인.
- Then 동일 원인이 다른 라우트에 잠복해 있다 ⚠️ —
grid-cols-1 lg:grid-cols-*+ 넓은 자식(min-width 테이블·고정폼) 패턴이projects/[id]/index.vue:71·orders/[id].vue:74·licenses/[id].vue:72·feedback/widgets.vue:184·donations/widgets:110·integrations/ai.vue:131·settings/index.vue:125에도 존재. 현재 데이터로는 오버플로우 미발생(빈 상태/좁은 콘텐츠)이나 데이터 의존적 — 공통 수정(min-w-0on grid items 또는minmax(0,1fr)) 권장 - Then 나머지 20라우트는 장치폭에 맞는다 ✅ — 375/390 전수, 태블릿 768×1024도 전수 오버플로우 0
- Then 목록 테이블이 페이지를 밀지 않고 카드 안에서 스크롤된다 ✅ — orders(928px)·licenses(968px)·products index(868px)·dashboard 첫 테이블(648px) 모두 UiTable overflow-x-auto로 격리 (단, MS2 affordance 참고)
MS2. 모바일 드로어 / 내비게이션
- When 햄버거(aria-label=메뉴, 실측 36×36)를 탭한다 → 270px 드로어 + 백드롭이 열린다 ✅
- Then 드로어 내비 15개 전부가 존재하고 각각 올바른 라우트로 이동한다 ✅ — 대시보드/프로젝트/상품/후원 버튼/피드백/피드백 위젯/AI 연동 센터/주문·결제/정산 내역/정산 등록/라이선스/라이선스 검증/구독/설정/내 스토어 보기 전수 탭-네비게이션 검증 (단 /license/verify는 공개 표면 — 별도 판정 참고 D35)
- Then 항목 탭 시 드로어가 자동으로 닫힌다 ✅, 백드롭 탭으로도 닫힌다 ✅
- Then 드로어 오픈 중 본문이 눌리거나 오버플로하지 않는다 ✅ (offenders 0)
- 비고: 드로어/백드롭 실측 높이 1432px(뷰포트 812 초과, 문서 높이 기준) — 기능적 문제는 아니나 fixed 레이어가 뷰포트가 아닌 문서 기준으로 잡히는 징후; 스크롤된 상태에서의 재확인 여지 있음 (INFO)
MS3. 모달 / 오버레이
- Then 모달이 뷰포트 안에 렌더되고 취소로 닫힌다 ✅ — 4종 측정 전부
w=343 x=16, 뷰포트 내: 라이선스 수동 발급(347h)·설정 데이터 초기화(226h)·주문 환불(226h)·아이템 등록(626h, y=171 — 상단 여유 작으나 화면 내) - Then 아바타 드롭다운(내 스토어 보기/설정)이 오버플로 없이 열린다 ✅
MS4. 탭 대상·공통 요소
- Then 인터랙티브 요소가 WCAG 2.5.8(24px) 이상이다 ❌ — D36의 판매자 표면 인스턴스:
- /settings API 키 보기 토글 버튼 실측 15×15 (settings/index.vue:187)
- UiSwitch
.ng-switch36×20 — /products/new·/products/[id]·/settings (components/ui/UiSwitch.vue) - /projects/new 컬러 스와치 6개 실측 17×28 (
w-7 h-7선언이나 flex 수축, projects/new.vue:83) - 브레드크럼·인라인 링크 h=15~20 — "← 프로젝트"·"상품"·"주문"·"라이선스"·"검증 도구"·주문번호/라이선스 인라인 링크 (licenses/[id] 4건, orders/[id] 2건 등)
- Then 검색·명령(⌘K)에 모바일에서 도달할 수 있다 ❌ — 헤더 검색 버튼
hidden sm:flex(layouts/dashboard.vue:249)이라 375에서 0개, 드로어에도 대체 진입점 없음. 터치 기기에서 커맨드 메뉴 완전 비노출 - Then 표 넘침에 시각적 affordance가 있다 ⚠️ INFO — UiTable은 카드 내 가로 스크롤되나 edge-fade/힌트 없음. 스크롤 가능 열 존재를 모바일 사용자가 인지하기 어려움 (공용 컴포넌트, PLATFORM_FE-shared)
- viewport meta
width=device-width, initial-scale=1— user-scalable 제한 없음 ✅, pinch-zoom 가능
MS5. 렌더 안정성
- Then 전 라우트에서 pageerror/console.error/HTTP≥400이 없다 ✅ — 46회 방문(23×2뷰포트) 전부 clean
- Then pullAll 완료 전 차단 오버레이가 모바일에서도 정상 표시·해제된다 ✅ (단, "서버에 연결 중"이 라우트 진입마다 수 초간 표시 — D13 연장 관측, 별도 판정 아님)
- Then KPI 카드가 스태킹된다 ✅ — dashboard KPI 2열(166px)·CTA 그리드 2열(164px) @375
신규 결함 (요약표 병합 시 D40+ 부여 권장):
| ID | 심각도 | 표면 | 요약 | 소유 추정 |
|---|---|---|---|---|
| MS-A(=D30 재현) | MAJOR | /dashboard 모바일 | 375/390px 가로 오버플로우 +286/+271px — lg:grid-cols-2 + min-width 테이블 카드. 판매자 본인 세션에서도 확인 (D30은 act-as 관측) | SELLER_FE |
| MS-B | MAJOR | /products/[id] 모바일 | +127px @375 — 동일 패턴(products/[id].vue:100, 테이블 min 480) | SELLER_FE |
| MS-C | MAJOR | /settlements 모바일 | +86px @375 — 카드+스크롤 래퍼가 뷰포트 이탈, 도달 불가 열 발생(settlements/index.vue:69, 테이블 min 440) | SELLER_FE |
| MS-D | MINOR | 판매자 콘솔 전반 | 탭 대상 <24px — 15×15 토글·ng-switch 36×20·스와치 17×28·인라인 링크 h15~20 (D36 계열) | SELLER_FE + PLATFORM_FE(공용) |
| MS-E | MINOR | 모바일 헤더 | 검색/⌘K 커맨드 메뉴가 모바일에서 진입 경로 0 (hidden sm:flex) | PLATFORM_FE (shared layout) |
| MS-F | INFO | UiTable | 가로 스크롤 affordance 부재 — 잘림 단서 없음 | PLATFORM_FE (components/ui) |
결함 요약표
| ID | 심각도 | 표면 | 요약 | 소유 추정 |
|---|---|---|---|---|
| D1 | BLOCKER | /checkout | 익명·구매자 결제 페이지 "상품을 찾을 수 없습니다" + POST 401 | PLATFORM_FE + BACKEND_INFRA |
| D2 | BLOCKER | /license/verify | 비판매자에게 영구 블록 화면 (공개 API인데 페이지가 차단) | PLATFORM_FE |
| D3 | MINOR | 전역 | "Nougat DevPay"/"Frontend PoC" 잔존 (title·FAQ·약관·스토어 푸터·생성 코드) | PLATFORM_FE + PRODUCT_DESIGN |
| D4 | MINOR | /donate | "10천원/30천원/50천원" 부자연 카피 | PLATFORM_FE |
| D5 | MINOR | /onboarding | 온보딩 완료 판매자도 폼 재진입 가능 (리다이렉트 없음) | SELLER_FE |
| D6 | MINOR | 403 페이지 | 일반 오류문구 + 403로 되돌아가는 "대시보드로" CTA | PLATFORM_FE |
| D7 | MAJOR | /donations/widgets·/integrations/ai | 생성 링크가 sellerSlug='nougat' 고정 — 다른 판매자 스토어로 유도 | SELLER_FE |
| D8 | BLOCKER | /donate/[slug] | slug 무시(로컬 seller 사용)·익명/buyer 무반응·완료 후 /store/(빈 slug) 링크 | PLATFORM_FE (+BE 계약) |
| D9 | MAJOR | /h/[publicId] | 배포 워커에 public integrations 라우트·테이블 부재 — 기능 자체 미배포 | BACKEND_INFRA (+PRODUCT_DESIGN) |
| D10 | BLOCKER | /admin act-as | ALLOW_ACT_AS_SELLER 미설정 → 헤더 무시 → 판매자 콘솔 영구 블록 | BACKEND_INFRA (env) + PLATFORM_FE (실패 감지 UX) |
| D11 | MAJOR | /admin 컨텍스트 스위처 | 스크롤/검색 없음 — 뷰포트 밖 판매자 선택 불가 (35+ sellers) | PLATFORM_FE |
| D12 | MINOR | /admin 사용자 목록 | "QA Sto" 등 절단된 이름 표기 (픽스처 데이터 추정 — 재확인 필요) | triage |
| D13 | MAJOR | 상세 페이지 공통 | 자체 pullAll이 generation 무효화 → 5~10초 가짜 "서버와 연결할 수 없습니다" | PLATFORM_FE (sync 기반) |
| D14 | MINOR | /orders/[id] | 환불 성공 후에도 "결제 완료" 유지·토스트 없음 (remote()가 상태 미갱신) | SELLER_FE |
| D15 | MAJOR | /feedback/widgets | 새 위젯 POST 400 "invalid json" — FE bool ↔ BE int 계약 불일치 | BACKEND_INFRA + SELLER_FE |
| D16 | MAJOR | POST /licenses/verify | product_id·device_id 무시 (불일치 검사·기기 등록 미구현) — 페이지 문구와 불일치 | BACKEND_INFRA |
| D17 | MINOR | /license/verify | "데모 키 채우기" → key undefined → TypeError(trim) | SELLER_FE |
| D18 | MINOR | checkout→license | 상품 license_json.maxDevices 무시 — 판매자 기본값(3) 적용, 스토어는 2대 표기 | BACKEND_INFRA |
| D19 | MINOR | 전역 | UTC naive datetime 로컬 파싱 → 9시간 오차(모든 상대·절대 시각) | PLATFORM_FE |
| D20 | MINOR | ⌘K | 비관리자에게 "운영자 콘솔" 노출 (클릭 시 403) | PLATFORM_FE/SELLER_FE |
| D21 | MINOR | /settlements | 403 거부 시 사용자에게 오류 미표시 (콘솔만) | SELLER_FE |
| D22 | MINOR | /onboarding | pre-onboard 계정의 pullAll 10×403 낭비 | PLATFORM_FE/SELLER_FE |
| D23 | INFO | /onboarding | (D24로 대체) 진행 불가 원인은 UiCard 슬롯 | — |
| D24 | BLOCKER | /onboarding·/settings/settlement | UiCard #footer 슬롯 미구현 — "계속"/"가입 완료"/"심사 신청" 버튼 미렌더 | PLATFORM_FE |
| D25 | MAJOR | PATCH /items/:id | 부분 갱신 아님 — 생략 필드(price/qty/description) 초기화 | BACKEND_INFRA |
| D26 | MINOR | /demo 구매 | grant_unit 빈 아이템 409 INVALID_UNIT + UI 오류 미표시 | BACKEND_INFRA + SELLER_FE |
| D27 | MINOR | /checkout/complete | 구매자 본인에게 "주문을 찾을 수 없습니다" (seller-scoped 데이터 의존) | PLATFORM_FE |
| D28 | MINOR | /feedback/widgets | 생성 iframe URL(/widgets/embed/*) 404 — placeholder 고지는 있으나 생성물이 사장 링크 | BACKEND_INFRA/SELLER_FE |
| D29 | MAJOR | /admin 모바일 | 375/390px에서 헤더 고정폭(619px) → 페이지 가로 오버플로우 +244/+229px; 판매자 콘솔·컨텍스트 해제·테마·로그아웃 화면 밖 (가로 스크롤로만 도달) | PLATFORM_FE (layouts/admin.vue) |
| D30 | MAJOR | /dashboard 모바일(act-as 관측) | grid lg:grid-cols-2 암시적 auto 트랙 + UiTable min-width 640 → 카드 642px, +286px 가로 오버플로우 @375; seller 공통 패턴 전파 의심 | SELLER_FE (pages/dashboard.vue) |
| D31 | MINOR | /admin 컨텍스트 드롭다운 | 모바일에서 우측 1px 뷰포트 오버플로 (right=376 vs 375) — cosmetic | PLATFORM_FE |
| D32 | MINOR | /admin 모바일 토스트 | 페이지 가로 오버플로우 시 fixed 토스트가 619px 레이아웃 기준 배치 → 화면 밖(right=599); D29 파생 | PLATFORM_FE |
| D33 | MINOR | /admin 모바일 헤더 | 다크모드 토글 탭 대상 실측 17×36px (24px 미만, 오프스크린 클립 부산물) | PLATFORM_FE |
| D34 | MAJOR | /store/[slug]/p/[donation-product] | 후원형 상품 페이지 JS 크래시 — p.plans.some/find TypeError(plans 비배열) → 본문 전체 미렌더 (375/390 공통, 뷰포트 무관) | PLATFORM_FE (+BE plans DTO 계약 확인) |
| D35 | MINOR | /license/verify | 공개 페이지가 layout:'dashboard' — 익명에게 판매자 콘솔 내비 13개 + href=/store/(빈 slug) 데드 "내 스토어 보기" | PLATFORM_FE |
| D36 | MAJOR | 전역(모바일) | 탭 대상 <44px 전역 토큰 — ng-btn/input-base h=40, 아이콘 버튼 33–36, 체크박스 13×13, 헤더 링크 h=19~32, 인라인 링크 h=16 (일부는 WCAG 2.5.8 AA 24px도 실패) | PLATFORM_FE (components/ui 공용) |
| D37 | MINOR | / 모바일 | nav.hidden.md:flex + 햄버거 없음 — 섹션 앵커 내비 불가 (스크롤로 대체 가능) | PLATFORM_FE |
| D38 | MINOR | /checkout/[productId] | ?store= 필수 — 생략 시 상품 존재에도 "상품을 찾을 수 없습니다" 오해 문구 | PLATFORM_FE |
| D39 | MINOR | 전역 | 11.5px 텍스트 — .ng-badge·mock 고지·verify pre/라벨 (12px 미만) | PLATFORM_FE |
커버리지
- 방문 라우트: 40+ (공개 17, 판매자 21, 관리자 1 + act-as, 게스트 차단 경로 4)
- 클릭/제출한 상호작용: 내비 전수, 폼 9종(로그인·가입·온보딩·검증·주문·정산·위젯·상품·후원), 버튼 80+, 모달 2종, ⌘K, 스위처, 토글
- 계정 롤: 익명·buyer·seller·admin + act-as(무효 확인)
- 모바일: 공개 16라우트 × 375·390px 전수 + 6라우트 × 768px, 전 상호작용 요소 클릭(버튼/링크/셀렉트), 햄버거·다크모드·체크아웃·후원 완주 (## MOBILE-PUBLIC, D34–D39); ADMIN 표면 별도 섹션(D29–D33)
- API 직접 검증: checkout·licenses/verify·purchase·refund·settlements·verification·admin overview/users/sellers/act-as·items PATCH·widget POST·public stores
미검증 / 한계 (NOT_VERIFIED)
- 결제 실패·취소 시뮬레이션 UX(토스트) — 버튼 존재만 확인, 미클릭
- 피드백 공개 제출(위젯 embed) — embed 엔드포인트 미배포로 경로 자체 부재
- /h/* 실제 콘텐츠 — 픽스처 생성 경로 자체 부재
- 모바일 뷰포트 레이아웃 — ADMIN 표면(/admin + act-as→/dashboard)은 375/390/768px 측정 완료(## MOBILE-ADMIN, D29–D33); 공개 표면 16라우트 375/390/768px 측정 완료(## MOBILE-PUBLIC, D34–D39); 판매자 표면은 별도 레인 소관
- /demo/[projectId] 실제 데모 콘텐츠 — 픽스처 프로젝트 미공개로 not-found 상태만 확인
- 모바일 구매자 인증 세션(JWT) — 익명 플로우만 검증; buyer 로그인 상태 모바일 표면은 미커버
- 스크린리더 접근성 — 미측정
- 설정 저장 후 실제 반영(토스트/지속성) — 저장 호출 200 확인, UI 피드백 미확인