Keystone QA 시나리오 카탈로그
03 하네스 Markdown

QA 시나리오 카탈로그

렌더링·인증·폼·CRUD·접근성·보안 등 14개 카테고리 100여 개 브라우저 QA 시나리오와 게이트 판정 기준

Source
src/content/docs/qa-scenarios.md
Order
55

한눈에 보기

브라우저 QA 에이전트가 태스크 완료 전 실행하는 시나리오 카탈로그입니다. “빌드 성공”이 아니라 “사용자 플로우가 실제로 동작하는가”를 판정 기준으로 만듭니다.

WebMCP(CDP) 기반 브라우저 QA 시나리오. QA 에이전트가 모든 태스크 완료 전 실행하는 시나리오 정의입니다. 예시 코드의 URL은 모두 로컬 개발 서버 기준의 일반 예시입니다.

QA 레벨

레벨실행 시점범위소요 시간
micro-QA개별 태스크 완료 시변경된 기능만 타겟 테스트1-2분
feature-QA기능 단위 완료 시해당 기능 전체 시나리오3-5분
full-QAPhase 완료 / 최종 검증전체 시나리오10-15분

카테고리별 QA 시나리오

1. 페이지 렌더링 (RENDERING)

모든 라우트가 정상 렌더링되는지 검증.

ID시나리오검증 방법 (WebMCP)심각도
R-01메인 페이지 렌더링navigate("/")qa_page_state() → bodyLength > 100CRITICAL
R-02모든 등록 라우트 렌더링각 라우트 순회 → bodyLength > 0, readyState === “complete”CRITICAL
R-03빈 페이지(White Screen) 감지bodyLength < 50 → FAILCRITICAL
R-04에러 바운더리 표시”error”, “something went wrong” 텍스트 감지 → FAILCRITICAL
R-05로딩 스피너 무한 대기5초 후에도 spinner/loading 요소 존재 → HIGHHIGH
R-06404 페이지 존재하지 않는 라우트/nonexistent-route 접근 → 404 페이지 또는 리다이렉트MEDIUM
R-07SSR/CSR 하이드레이션 불일치콘솔에 “hydration” 에러 감지HIGH
// WebMCP 실행 예시
navigate("http://localhost:3000/");
const state = await mcpCall('qa_page_state');
assert(state.bodyLength > 100, 'R-01: 메인 페이지 빈 화면');
assert(state.readyState === 'complete', 'R-01: 로드 미완료');

2. 인증 / 인가 (AUTH)

로그인, 로그아웃, 세션 관리, 권한 제어 검증.

ID시나리오검증 방법심각도
A-01로그인 폼 렌더링/login → qa_form_state() → email/password 필드 존재CRITICAL
A-02올바른 자격증명 로그인폼 입력 → 제출 → 대시보드 리다이렉트CRITICAL
A-03잘못된 자격증명 거부잘못된 비밀번호 → 에러 메시지 표시, 페이지 유지CRITICAL
A-04미인증 접근 리다이렉트보호된 경로 직접 접근 → /login 리다이렉트CRITICAL
A-05로그아웃 기능로그아웃 버튼 클릭 → 세션 삭제, 로그인 페이지 이동HIGH
A-06세션 만료 처리만료된 토큰으로 API 호출 → 401 → 로그인 리다이렉트HIGH
A-07회원가입 유효성 검증빈 필드/짧은 비밀번호/잘못된 이메일 → 각각 에러 메시지HIGH
A-08소셜 로그인 버튼OAuth 버튼 존재 + 클릭 시 올바른 URL로 이동MEDIUM
A-09비밀번호 재설정 플로우이메일 입력 → 전송 → 성공 메시지MEDIUM
A-10권한별 UI 분기admin은 관리 메뉴 보임, 일반 사용자는 안 보임HIGH
// A-01: 로그인 폼 검증
navigate("http://localhost:3000/login");
const forms = await mcpCall('qa_form_state');
assert(forms.length > 0, 'A-01: 로그인 폼 없음');
const loginForm = forms[0];
const hasEmail = loginForm.fields.some(f => f.name === 'email' || f.type === 'email');
const hasPassword = loginForm.fields.some(f => f.type === 'password');
assert(hasEmail && hasPassword, 'A-01: 이메일/비밀번호 필드 누락');

// A-04: 미인증 리다이렉트
navigate("http://localhost:3000/dashboard");
const state = await mcpCall('qa_page_state');
assert(state.url.includes('/login'), 'A-04: 미인증 접근이 차단되지 않음');

3. 폼 입력 및 유효성 검증 (FORM)

모든 폼의 입력, 제출, 유효성 검사 검증.

ID시나리오검증 방법심각도
F-01필수 필드 빈 제출 차단required 필드 비우고 제출 → 에러 메시지HIGH
F-02이메일 형식 검증”invalid-email” 입력 → 포맷 에러HIGH
F-03최소/최대 길이 검증1자 입력 → 최소 길이 에러MEDIUM
F-04비밀번호 강도 검증약한 비밀번호 → 강도 표시기 / 에러MEDIUM
F-05폼 제출 성공유효한 데이터 입력 → 제출 → 성공 피드백CRITICAL
F-06중복 제출 방지빠른 더블 클릭 → 버튼 비활성화 또는 1회만 처리HIGH
F-07폼 초기화/리셋리셋 버튼 → 모든 필드 초기값 복원LOW
F-08파일 업로드파일 선택 → 미리보기 → 업로드 성공HIGH
F-09드롭다운/셀렉트 동작옵션 선택 → 값 반영MEDIUM
F-10날짜/시간 입력기날짜 선택 → 올바른 포맷 반영MEDIUM
F-11동적 폼 필드조건부 필드 표시/숨기기 → 상태 일관성MEDIUM
F-12서버 사이드 유효성 에러서버 422 → 필드별 에러 메시지 매핑HIGH
// F-01: 필수 필드 빈 제출
navigate("http://localhost:3000/signup");
click('button[type="submit"]');
const errors = await mcpCall('qa_assert_element', {
  selector: '[role="alert"], .error-message, .text-red-500',
  visible: true
});
assert(errors.found, 'F-01: 필수 필드 빈 제출 시 에러 메시지 없음');

4. CRUD 작업 (CRUD)

데이터 생성, 조회, 수정, 삭제 전체 플로우 검증.

ID시나리오검증 방법심각도
C-01리스트 조회 (Read)목록 페이지 → 아이템 렌더링, 빈 상태 처리CRITICAL
C-02상세 조회 (Read)아이템 클릭 → 상세 정보 표시HIGH
C-03새 항목 생성 (Create)폼 작성 → 제출 → 리스트에 추가됨CRITICAL
C-04항목 수정 (Update)수정 버튼 → 폼 프리필 → 변경 → 저장 → 반영CRITICAL
C-05항목 삭제 (Delete)삭제 버튼 → 확인 다이얼로그 → 삭제 → 리스트에서 제거HIGH
C-06낙관적 업데이트수정 후 즉시 UI 반영 → 서버 응답 후 확정MEDIUM
C-07빈 상태 UI데이터 0건 → “데이터 없음” 메시지 또는 생성 유도MEDIUM
C-08페이지네이션다음/이전 페이지 → 데이터 변경, 현재 페이지 표시MEDIUM
C-09검색/필터검색어 입력 → 결과 필터링 → 초기화 시 전체 복원MEDIUM
C-10정렬컬럼 헤더 클릭 → 오름/내림차순 토글LOW
C-11무한 스크롤스크롤 하단 도달 → 추가 데이터 로드MEDIUM
C-12삭제 확인 다이얼로그삭제 → 확인 모달 → 취소 시 유지, 확인 시 삭제HIGH
// C-01: 리스트 조회
navigate("http://localhost:3000/items");
const state = await mcpCall('qa_page_state');
// 아이템이 있거나 빈 상태 메시지가 있어야 함
const hasItems = state.bodyPreview.length > 50;
const hasEmptyState = state.bodyPreview.includes('없') || state.bodyPreview.includes('empty');
assert(hasItems || hasEmptyState, 'C-01: 리스트 미렌더링');

5. 네비게이션 (NAVIGATION)

라우팅, 링크, 브레드크럼, 뒤로가기 등 검증.

ID시나리오검증 방법심각도
N-01메인 네비게이션 링크모든 nav 링크 클릭 → 올바른 페이지 이동HIGH
N-02브라우저 뒤로/앞으로history.back() → 이전 페이지 렌더링MEDIUM
N-03딥 링크 직접 접근/items/123 직접 URL 입력 → 정상 렌더링HIGH
N-04브레드크럼 정확성현재 위치 반영, 각 레벨 클릭 가능LOW
N-05활성 메뉴 하이라이트현재 페이지에 해당하는 nav 아이템 활성 상태LOW
N-06404 라우트 처리존재하지 않는 경로 → 404 페이지MEDIUM
N-07리다이렉트 체인리다이렉트 시 무한 루프 없음, 최종 페이지 도달HIGH
N-08쿼리 파라미터 보존필터/정렬 상태가 URL에 반영, 새로고침 후 유지MEDIUM

6. 반응형 / 레이아웃 (RESPONSIVE)

다양한 뷰포트에서 레이아웃 검증.

ID시나리오검증 방법심각도
RS-01모바일 레이아웃 (375px)viewport 375px → 수평 스크롤 없음, 터치 타겟 48px+HIGH
RS-02태블릿 레이아웃 (768px)viewport 768px → 적절한 그리드 전환MEDIUM
RS-03데스크탑 레이아웃 (1280px)viewport 1280px → 풀 레이아웃 표시MEDIUM
RS-04수평 오버플로우 없음모든 뷰포트에서 body scrollWidth <= viewport widthHIGH
RS-05모바일 메뉴 토글햄버거 메뉴 → 사이드바/드로어 표시 → 닫기HIGH
RS-06이미지 반응형이미지가 컨테이너 넘지 않음, max-width: 100%MEDIUM
RS-07텍스트 가독성font-size >= 14px (모바일), line-height 적절MEDIUM
RS-08터치 타겟 크기버튼/링크 최소 44x44px (모바일)MEDIUM
// RS-01: 모바일 레이아웃 검증
evaluate_script(`
  document.documentElement.style.width = '375px';
  window.innerWidth
`);
navigate("http://localhost:3000/");
evaluate_script(`
  document.body.scrollWidth <= 375 ? 'PASS' : 'FAIL: 수평 오버플로우'
`);

7. 접근성 (ACCESSIBILITY)

WCAG 2.1 AA 기준 검증.

ID시나리오검증 방법심각도
AC-01이미지 alt 속성qa_accessibility() → missing-alt 0건HIGH
AC-02빈 인터랙티브 요소텍스트/aria-label 없는 button/a 감지HIGH
AC-03라벨 없는 inputlabel 또는 aria-label 없는 inputHIGH
AC-04헤딩 레벨 순서h1 → h2 → h3 순서 (스킵 금지)MEDIUM
AC-05lang 속성<html lang="ko"> 존재MEDIUM
AC-06키보드 네비게이션Tab 키로 모든 인터랙티브 요소 접근 가능HIGH
AC-07포커스 표시기focus 시 시각적 인디케이터(outline 등) 존재MEDIUM
AC-08색상 대비텍스트-배경 대비 4.5:1 이상MEDIUM
AC-09ARIA 역할모달 → role=“dialog”, 알림 → role=“alert”MEDIUM
AC-10Skip Navigation첫 Tab에서 “본문 바로가기” 링크LOW
// AC-01~05: 접근성 기본 검사
const a11y = await mcpCall('qa_accessibility');
assert(a11y.issues.filter(i => i.type === 'missing-alt').length === 0, 'AC-01: alt 없는 이미지');
assert(a11y.issues.filter(i => i.type === 'empty-interactive').length === 0, 'AC-02: 빈 버튼/링크');
assert(a11y.issues.filter(i => i.type === 'unlabeled-input').length === 0, 'AC-03: 라벨 없는 input');
assert(a11y.lang, 'AC-05: html lang 속성 없음');

8. 에러 핸들링 (ERROR)

네트워크 실패, 서버 에러, 사용자 에러 시 UI 반응 검증.

ID시나리오검증 방법심각도
E-01API 500 에러 표시서버 에러 시 사용자 친화적 메시지 (스택트레이스 노출 금지)CRITICAL
E-02네트워크 오프라인fetch 실패 → “연결 확인” 메시지 또는 재시도 버튼HIGH
E-03404 API 응답존재하지 않는 리소스 → “찾을 수 없음” 메시지MEDIUM
E-04타임아웃 처리10초 초과 → 타임아웃 메시지, 재시도 옵션MEDIUM
E-05콘솔 에러 없음qa_console_errors() → 0건HIGH
E-06언핸들드 프로미스 거부unhandledrejection 이벤트 감지 → 0건HIGH
E-07에러 바운더리 작동컴포넌트 에러 시 전체 앱 크래시 방지CRITICAL
E-08유효성 에러 메시지 명확성에러 메시지가 해결 방법을 제시MEDIUM
E-09에러 후 복구에러 발생 후 정상 동작으로 복구 가능HIGH
// E-05: 콘솔 에러 확인
const errors = await mcpCall('qa_console_errors');
const criticalErrors = errors.filter(e =>
  !e.msg.includes('favicon') && !e.msg.includes('HMR')
);
assert(criticalErrors.length === 0, `E-05: 콘솔 에러 ${criticalErrors.length}건`);

9. API 통신 (API)

네트워크 요청/응답 검증.

ID시나리오검증 방법심각도
AP-01API 호출 성공qa_network_log() → 모든 요청 status 2xxHIGH
AP-02실패한 요청 없음status 4xx/5xx 또는 FAILED 없음HIGH
AP-03인증 토큰 전송Authorization 헤더 포함 (보호된 엔드포인트)CRITICAL
AP-04CORS 에러 없음콘솔에 CORS 관련 에러 0건HIGH
AP-05응답 시간 적정모든 API 응답 < 3000msMEDIUM
AP-06중복 요청 방지같은 엔드포인트 연속 호출 없음 (디바운스)MEDIUM
AP-07로딩 상태 표시API 호출 중 spinner/skeleton 표시MEDIUM
AP-08에러 응답 처리4xx/5xx → 사용자 피드백 (토스트/알림)HIGH
// AP-01, AP-02: 네트워크 요청 검증
const network = await mcpCall('qa_network_log');
const failures = network.filter(r =>
  r.status === 'FAILED' || (typeof r.status === 'number' && r.status >= 400)
);
assert(failures.length === 0, `AP-01: 실패한 API 요청 ${failures.length}건`);

10. 상태 관리 (STATE)

클라이언트 상태 일관성 검증.

ID시나리오검증 방법심각도
S-01상태 변경 → UI 반영데이터 변경 후 관련 컴포넌트 모두 업데이트CRITICAL
S-02새로고침 후 상태 유지F5 → 로그인 상태, 필터 등 유지HIGH
S-03탭 간 상태 동기화다른 탭에서 변경 → 현재 탭 반영 (해당 시)MEDIUM
S-04로딩/에러/성공 상태 전환각 상태별 적절한 UI 표시HIGH
S-05캐시 무효화데이터 변경 후 캐시된 목록 갱신HIGH
S-06동시 수정 충돌같은 데이터 동시 수정 시 충돌 처리MEDIUM
S-07localStorage 미사용Supabase Auth 내부 제외 localStorage 호출 0건HIGH
// S-07: localStorage 사용 감지
evaluate_script(`
  const originalSetItem = Storage.prototype.setItem;
  window.__lsWrites = [];
  Storage.prototype.setItem = function(key, value) {
    if (!key.startsWith('sb-')) window.__lsWrites.push(key);
    return originalSetItem.call(this, key, value);
  };
`);
// ... 앱 조작 후 ...
evaluate_script(`JSON.stringify(window.__lsWrites)`);
// __lsWrites 배열이 비어야 함

11. 성능 (PERFORMANCE)

페이지 로드 및 런타임 성능 검증.

ID시나리오검증 방법심각도
P-01초기 로드 < 3초navigate 후 DOMContentLoaded 타이밍HIGH
P-02LCP < 2.5초Largest Contentful Paint 측정MEDIUM
P-03번들 크기 적정빌드 output에서 main chunk < 500KBMEDIUM
P-04이미지 최적화2MB 이상 이미지 없음, WebP/AVIF 사용LOW
P-05메모리 누수 없음페이지 전환 반복 후 heap 증가 없음MEDIUM
P-06불필요한 리렌더링React DevTools Profiler 기준 과도한 렌더 감지LOW
P-07레이지 로딩초기 번들에 모든 라우트 포함 안 됨LOW
// P-01: 초기 로드 시간
evaluate_script(`
  const timing = performance.timing;
  const loadTime = timing.loadEventEnd - timing.navigationStart;
  JSON.stringify({ loadTime, threshold: 3000, pass: loadTime < 3000 });
`);

12. 보안 (SECURITY)

클라이언트 사이드 보안 검증.

ID시나리오검증 방법심각도
SEC-01XSS 방지<script>alert(1)</script> 입력 → 이스케이프 처리CRITICAL
SEC-02CSRF 토큰폼 제출 시 CSRF 토큰 포함 (해당 시)HIGH
SEC-03민감 정보 미노출페이지 소스에 API 키, 시크릿 없음CRITICAL
SEC-04HTTPS 리다이렉트HTTP → HTTPS 자동 리다이렉트 (프로덕션)HIGH
SEC-05보안 헤더 존재X-Content-Type-Options, CSP 등MEDIUM
SEC-06비밀번호 마스킹password 필드 type=“password” 유지HIGH
SEC-07자동완성 제어민감 필드 autocomplete=“off” 또는 적절한 값LOW
SEC-08인증 토큰 안전 저장토큰이 URL 파라미터나 로그에 노출되지 않음CRITICAL
SEC-09클릭재킹 방지X-Frame-Options 또는 CSP frame-ancestorsMEDIUM
// SEC-01: XSS 방지
type('input[name="search"]', '<script>alert("xss")</script>');
click('button[type="submit"]');
const state = await mcpCall('qa_page_state');
assert(!state.bodyPreview.includes('<script>'), 'SEC-01: XSS 취약점');

13. 토스트 / 알림 / 모달 (NOTIFICATION)

사용자 피드백 UI 검증.

ID시나리오검증 방법심각도
NT-01성공 토스트 표시작업 성공 → 성공 메시지 토스트MEDIUM
NT-02에러 토스트 표시작업 실패 → 에러 메시지 토스트HIGH
NT-03토스트 자동 닫힘3-5초 후 자동 사라짐LOW
NT-04모달 열기/닫기트리거 → 모달 표시 → 배경 클릭/ESC → 닫힘MEDIUM
NT-05모달 배경 스크롤 잠금모달 열림 시 body 스크롤 비활성화LOW
NT-06확인 다이얼로그위험한 작업 전 “정말 삭제하시겠습니까?” 확인HIGH
NT-07모달 포커스 트랩Tab이 모달 내부에서만 순환MEDIUM

14. 다국어 / i18n (I18N)

다국어 지원 시 검증.

ID시나리오검증 방법심각도
I-01기본 언어 표시설정된 기본 언어로 UI 렌더링HIGH
I-02언어 전환언어 선택 → 모든 UI 텍스트 변경MEDIUM
I-03번역 누락 감지번역 키가 그대로 표시되는 경우 감지MEDIUM
I-04RTL 레이아웃아랍어/히브리어 → 우→좌 레이아웃 전환LOW
I-05날짜/숫자 포맷로케일에 맞는 날짜/통화 형식MEDIUM

QA 레벨별 시나리오 매핑

micro-QA (태스크 완료 시)

변경된 파일/기능에 해당하는 시나리오만 실행:

변경 파일 분석 → 카테고리 매핑 → 해당 시나리오만 실행
변경 영역실행 시나리오
인증 관련 (auth/, login, signup)A-01~A-10
폼 컴포넌트 (form, input)F-01~F-12
리스트/CRUD 페이지C-01~C-12
레이아웃/네비게이션N-01N-08, RS-01RS-08
API 호출 수정AP-01~AP-08
에러 핸들링E-01~E-09
공통 (항상 실행)R-01R-04, E-05, AP-01AP-02

feature-QA (기능 단위 완료 시)

해당 기능의 전체 시나리오 + 관련 카테고리:

기능 범위 파악 → 1차 카테고리 전체 실행 → 2차 연관 카테고리 실행

full-QA (Phase 완료 / 최종 검증)

전체 시나리오 실행:

  1. R-01~R-07 (렌더링)
  2. A-01~A-10 (인증)
  3. F-01~F-12 (폼)
  4. C-01~C-12 (CRUD)
  5. N-01~N-08 (네비게이션)
  6. RS-01~RS-08 (반응형)
  7. AC-01~AC-10 (접근성)
  8. E-01~E-09 (에러)
  9. AP-01~AP-08 (API)
  10. S-01~S-07 (상태)
  11. P-01~P-07 (성능)
  12. SEC-01~SEC-09 (보안)
  13. NT-01~NT-07 (알림/모달)
  14. I-01~I-05 (다국어, 해당 시)

QA 증거 파일 확장 스키마

.qa-evidence.json 확장:

{
  "timestamp": "2026-03-11T12:00:00Z",
  "qa_level": "full",
  "task_id": "task-123",
  "code_qa": {
    "build": "pass",
    "typecheck": "pass",
    "lint": "pass"
  },
  "browser_test": {
    "executed": true,
    "method": "superpowers-chrome",
    "routes_tested": ["/", "/login", "/dashboard", "/items", "/items/new"],
    "screenshots": ["/tmp/qa-home.png", "/tmp/qa-login.png"],
    "console_errors": 0,
    "network_failures": 0
  },
  "scenarios_executed": {
    "total": 95,
    "passed": 93,
    "failed": 0,
    "skipped": 2,
    "categories": {
      "RENDERING": { "total": 7, "passed": 7 },
      "AUTH": { "total": 10, "passed": 10 },
      "FORM": { "total": 12, "passed": 12 },
      "CRUD": { "total": 12, "passed": 12 },
      "NAVIGATION": { "total": 8, "passed": 8 },
      "RESPONSIVE": { "total": 8, "passed": 6, "skipped": 2 },
      "ACCESSIBILITY": { "total": 10, "passed": 10 },
      "ERROR": { "total": 9, "passed": 9 },
      "API": { "total": 8, "passed": 8 },
      "STATE": { "total": 7, "passed": 7 },
      "PERFORMANCE": { "total": 4, "passed": 4 }
    }
  },
  "findings_count": {
    "critical": 0,
    "high": 0,
    "medium": 0,
    "low": 0
  },
  "verdict": "PASS"
}

QA Gate 판정 기준

PASS 조건 (모든 조건 충족)

  1. code_qa: build + typecheck 모두 pass
  2. browser_test.executed: true
  3. browser_test.method: “superpowers-chrome” 또는 “agent-browser” (curl-fallback은 SPA에서 불충분)
  4. findings_count.critical: 0
  5. findings_count.high: 0
  6. scenarios_executed.failed: 0

FAIL 조건 (1개라도 해당)

  1. 빌드 실패
  2. 브라우저 테스트 미실행
  3. CRITICAL 또는 HIGH FINDING 존재
  4. 필수 시나리오(공통) 실패

WARNING (블로킹하지 않음)

  1. MEDIUM FINDING 존재
  2. curl-fallback 사용 (SPA 검증 한계)
  3. 증거 파일 1시간 이상 경과
  4. 일부 시나리오 SKIPPED

Source Notes

이 문서는 KeystoneHub의 브라우저 QA 시나리오 정의를 공개용으로 정리한 것입니다. 예시의 URL/포트는 일반화된 로컬 개발 서버 예시이며 특정 서비스를 가리키지 않습니다. Provenance: keystone-native.