QA 브라우저 시나리오 — staging.yetdapay.com 전표면 스윕
Reading guide
A reproducible list of browser journeys across public, seller, and admin screens, tied to the test fixture described in the report.
Use the scenario list to plan verification, but check execution logs before claiming that every scenario passed.
작성: 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/qa2-admin-1793545663@e2e.invalid
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 피드백 미확인