핵심 요약
4단계로 AI 환각을 멈추세요:
- Playwright 설치 및 중단점(데스크톱, 태블릿, 모바일) 구성
- 전체 페이지, 반응형 레이아웃 및 상호 작용을 캡처하는 스크린샷 테스트 스위트 생성
-
./qa-playwright-capture.sh실행해 증거 수집 - Reality Checker 에이전트로 주장, grep 결과, 스크린샷 교차 참조 에이전트는 PASS 또는 NEEDS WORK(차단 문제 포함)를 출력합니다. 더 이상 환상적인 승인은 없습니다.
서론
AI 에이전트로부터 "아주 좋아 보인다"는 말을 더 이상 받아들이지 마세요. 어떤 승인 전에 시각적 증명을 요구하는 Playwright 스크린샷 기반의 증거 기반 QA 워크플로우를 구축하세요.
AI 에이전트에게 랜딩 페이지 검토를 요청하면 다음과 같이 응답합니다:
The design looks premium and polished. The glassmorphism effects are well-implemented. The page is fully responsive. Ready for production!
하지만 실제로 페이지를 열어보면 "글래스모피즘"은 단색 회색 배경일 뿐이고, "완전 반응형" 레이아웃은 모바일에서 깨집니다. 고급스럽거나 세련된 것은 아무것도 없습니다.
AI 에이전트는 환각을 일으킵니다. 그들은 듣고 싶어 하는 말을 합니다. 갈등을 피하고, 모든 것을 승인합니다.
Agency 컬렉션의 Reality Checker 에이전트는 다른 접근 방식을 취합니다:
Status: NEEDS WORK
Evidence:
- "glassmorphism"에 대한 grep 결과: 프리미엄 기능 없음
- responsive-mobile.png: 375px 너비에서 깨진 레이아웃 표시
- test-results.json: 3개의 콘솔 오류, 2.1초 로드 시간 표시
Blocking issues: 4
감정도, 의견도 없습니다. 오직 증거만.
이 튜토리얼에서는 API 테스트 파이프라인을 보완하는 증거 기반 QA 워크플로우를 구축합니다. 프런트엔드 레이아웃을 검증하든 Apidog에서 API 응답을 확인하든 원칙은 동일합니다: 승인 전에 증명을 요구하세요. Playwright로 자동 스크린샷 캡처, 현실 점검 스크립트 생성, 에이전트 주장 교차 참조, 배포 전 PASS/FAIL 인증을 요구하는 방법을 다룹니다.
증거가 중요한 이유
AI 에이전트는 사람들을 기쁘게 하는 것을 좋아합니다. 그들은 돕고 싶어 하고, 당신이 그들을 좋아하길 바랍니다. 그래서 듣기 좋은 말만 합니다:
- "코드가 견고해 보인다!" (테스트 안 함)
- "성능이 좋을 것이다!" (측정 안 함)
- "완전 반응형이다!" (모바일에서 확인 안 함)
증거 기반 QA는 이런 흐름을 바꿉니다. 더 이상 의견이 아니라, 다음과 같은 것을 얻게 됩니다:
- 데스크톱, 태블릿, 모바일 중단점에서의 스크린샷
- 실제 페이지 로드에서 얻은 성능 지표
- 기능 존재 여부(grep 결과)
- 콘솔 오류 수 (헤드리스 브라우저 테스트)
이제 "나를 믿어줘"는 없습니다. 오직 증명만 있습니다.
1단계: Playwright 설정
Playwright 설치:
npm install -D @playwright/test
npx playwright install chromium
qa-playwright.config.ts 작성:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testMatch: '**/qa-screenshots.spec.ts',
timeout: 30000,
use: {
baseURL: process.env.BASE_URL || 'http://localhost:8000',
screenshot: 'on',
trace: 'on-first-retry',
headless: true,
},
projects: [
{
name: 'desktop',
use: { viewport: { width: 1920, height: 1080 } },
},
{
name: 'tablet',
use: { viewport: { width: 768, height: 1024 } },
},
{
name: 'mobile',
use: { viewport: { width: 375, height: 667 } },
},
],
reporter: [['json', { outputFile: 'public/qa-screenshots/test-results.json' }]],
outputDir: 'public/qa-screenshots',
});
2단계: 스크린샷 테스트 스위트 생성
qa-screenshots.spec.ts 작성:
import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
// 출력 디렉토리 존재 확인
const outputDir = 'public/qa-screenshots';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
test.describe('Reality Check Screenshots', () => {
test('capture full page at all breakpoints', async ({ page, browserName }) => {
const errors: string[] = [];
const consoleLogs: string[] = [];
// 콘솔 오류 캡처
page.on('console', msg => {
if (msg.type() === 'error') {
consoleLogs.push(`[ERROR] ${msg.text()}`);
}
});
// 네트워크 실패 캡처
page.on('requestfailed', request => {
errors.push(`[NETWORK] ${request.url()} failed`);
});
// 페이지로 이동
await page.goto('/');
await page.waitForLoadState('networkidle');
// 성능 지표 캡처
const metrics = await page.metrics();
const performance = {
jsHeapSize: metrics.JSHeapUsedSize,
loadTime: await page.evaluate(() => performance.timing.loadEventEnd - performance.timing.navigationStart),
domContentLoaded: await page.evaluate(() => performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart),
};
// 스크린샷 저장
const projectName = browserName || 'chromium';
await page.screenshot({
path: path.join(outputDir, `full-page-${projectName}.png`),
fullPage: true,
});
// 지표 저장
fs.writeFileSync(
path.join(outputDir, 'performance-metrics.json'),
JSON.stringify({ performance, consoleErrors: consoleLogs, networkErrors: errors }, null, 2)
);
});
test('capture responsive layouts', async ({ page }) => {
const breakpoints = [
{ name: 'desktop', width: 1920, height: 1080 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'mobile', width: 375, height: 667 },
];
for (const breakpoint of breakpoints) {
await page.setViewportSize({ width: breakpoint.width, height: breakpoint.height });
await page.goto('/');
await page.waitForLoadState('networkidle');
await page.screenshot({
path: path.join(outputDir, `responsive-${breakpoint.name}.png`),
fullPage: true,
});
}
});
test('capture navigation interactions', async ({ page }) => {
await page.goto('/');
// 내비게이션 항목 찾기 및 클릭
const navItems = await page.$$('nav a, header a, .nav a');
for (let i = 0; i < Math.min(navItems.length, 5); i++) {
await page.screenshot({ path: path.join(outputDir, `nav-${i}-before.png`) });
await navItems[i].click();
await page.waitForLoadState('networkidle');
await page.screenshot({ path: path.join(outputDir, `nav-${i}-after.png`) });
await page.goBack();
await page.waitForLoadState('networkidle');
}
});
test('capture form interactions', async ({ page }) => {
await page.goto('/');
// 양식 찾기
const forms = await page.$$('form');
for (let i = 0; i < forms.length; i++) {
const form = forms[i];
await form.screenshot({ path: path.join(outputDir, `form-${i}-initial.png`) });
// 입력 필드 채우기
const inputs = await form.$$('input[type="text"], input[type="email"], input[type="password"]');
for (const input of inputs) {
await input.fill('test@example.com');
}
await form.screenshot({ path: path.join(outputDir, `form-${i}-filled.png`) });
}
});
test('capture accordion/dropdown interactions', async ({ page }) => {
await page.goto('/');
// 아코디언 찾기
const accordions = await page.$$('[data-accordion], details, .accordion');
for (let i = 0; i < accordions.length; i++) {
await accordions[i].screenshot({ path: path.join(outputDir, `accordion-${i}-closed.png`) });
await accordions[i].click();
await page.waitForTimeout(300);
await accordions[i].screenshot({ path: path.join(outputDir, `accordion-${i}-open.png`) });
}
});
});
3단계: 현실 점검 스크립트 생성
qa-playwright-capture.sh 작성:
#!/usr/bin/env bash
#
# qa-playwright-capture.sh — 현실 점검을 위한 Playwright 스크린샷 캡처 실행
#
# 사용법: ./qa-playwright-capture.sh [BASE_URL] [OUTPUT_DIR]
#
set -euo pipefail
BASE_URL="${1:-http://localhost:8000}"
OUTPUT_DIR="${2:-public/qa-screenshots}"
echo "현실 점검 스크린샷 캡처 시작 중..."
echo " 기본 URL: $BASE_URL"
echo " 출력: $OUTPUT_DIR"
# 출력 디렉토리 존재 확인
mkdir -p "$OUTPUT_DIR"
# Playwright 테스트 실행
export BASE_URL
npx playwright test --config=qa-playwright.config.ts --grep "@screenshot"
# 요약 생성 중...
echo ""
echo "요약 생성 중..."
# 스크린샷 개수 세기
SCREENSHOT_COUNT=$(find "$OUTPUT_DIR" -name "*.png" | wc -l)
echo " 캡처된 스크린샷: $SCREENSHOT_COUNT개"
# 콘솔 오류 확인
if [ -f "$OUTPUT_DIR/performance-metrics.json" ]; then
ERROR_COUNT=$(cat "$OUTPUT_DIR/performance-metrics.json" | grep -c '"\[ERROR\]"' || echo "0")
echo " 콘솔 오류: $ERROR_COUNT개"
fi
# 로드 시간 확인
if [ -f "$OUTPUT_DIR/performance-metrics.json" ]; then
LOAD_TIME=$(cat "$OUTPUT_DIR/performance-metrics.json" | grep -o '"loadTime": [0-9.]*' | head -1 | awk '{print $2}')
echo " 로드 시간: ${LOAD_TIME:-해당 없음}ms"
fi
echo ""
echo "현실 점검 완료. 다음에서 스크린샷을 검토하세요: $OUTPUT_DIR"
echo ""
echo "다음 단계: Reality Checker 에이전트를 실행하여 증거 유효성 검사"
실행 권한 부여:
chmod +x qa-playwright-capture.sh
4단계: 현실 점검 명령 실행
AI 에이전트가 작업을 승인하기 전에 다음 명령을 실행하세요:
# 1. 실제로 빌드된 내용 확인
ls -la resources/views/ || ls -la *.html
ls -la src/components/ || ls -la components/
# 2. 주장된 기능 교차 확인
grep -r "glassmorphism\|backdrop-filter\|blur" . --include="*.css" --include="*.html" || echo "글래스모피즘 없음"
grep -r "responsive\|media-query\|@media" . --include="*.css" || echo "반응형 CSS 없음"
grep -r "jwt\|authentication\|auth" . --include="*.ts" --include="*.js" || echo "인증 기능 없음"
# 3. 스크린샷 캡처 실행
./qa-playwright-capture.sh http://localhost:8000 public/qa-screenshots
# 4. 증거 검토
ls -la public/qa-screenshots/
# 예상 파일:
# - responsive-desktop.png
# - responsive-tablet.png
# - responsive-mobile.png
# - nav-*-before.png, nav-*-after.png
# - form-*-initial.png, form-*-filled.png
# 5. 지표 확인
cat public/qa-screenshots/test-results.json
cat public/qa-screenshots/performance-metrics.json
5단계: Reality Checker 에이전트 활성화
Claude 코드 세션을 열고 다음과 같이 Reality Checker 모드를 활성화하세요:
Activate Reality Checker mode.
필수 현실 점검 프로세스를 실행합니다:
1. 파일 존재 확인: ls -la src/components/
2. 주장된 기능 교차 참조: "premium", "glassmorphism"에 대한 grep
3. 스크린샷 증거 검토: public/qa-screenshots/
4. test-results.json에서 지표 확인
프로젝트 URL: http://localhost:8000
출력: PASS 또는 특정 차단 문제가 있는 NEEDS WORK.
예상 출력 예시:
## 현실 점검 결과
### 파일 검증: PASS
- 컴포넌트 파일 존재: 12개 파일 발견
- 예상 구조 일치
### 기능 검증: NEEDS WORK
- 주장: "프리미엄 글래스모피즘 디자인"
- grep 결과: 글래스모피즘 없음
- 상태: 주장 지원되지 않음
### 스크린샷 증거: NEEDS WORK
- 데스크톱 (1920x1080): 레이아웃 올바름
- 태블릿 (768x1024): 내비게이션 겹침 감지
- 모바일 (375x667): 제품 그리드 깨짐 (1열 대신 2열)
### 성능 지표: NEEDS WORK
- 로드 시간: 2.3초 (목표: <1초)
- 콘솔 오류: 3개 (목표: 0)
- 네트워크 실패: 1개 (목표: 0)
## 최종 상태: NEEDS WORK
### 차단 문제:
1. 글래스모피즘 주장되었으나 구현되지 않음
2. 375px에서 모바일 레이아웃 깨짐
3. 로드 시간이 1초 목표 초과
4. 수정해야 할 콘솔 오류 3개
### 비차단 문제:
- 태블릿 내비게이션 겹침
- 로딩 상태 추가
차단 문제가 해결될 때까지 승인하지 마세요.
6단계: 증거와 주장 교차 참조
주장 체크리스트를 만드세요:
## 주장 vs. 증거 체크리스트
| 주장 | 증거 명령 | 결과 |
|-------|------------------|--------|
| "프리미엄 글래스모피즘" | grep "backdrop-filter" | 찾을 수 없음 |
| "완전 반응형" | responsive-mobile.png | 실패 (깨진 그리드) |
| "콘솔 오류 없음" | test-results.json | 3개 오류 발견 |
| "빠른 로드 시간" | performance-metrics.json | 2.3초 (목표: <1초) |
| "JWT 인증" | grep "jsonwebtoken" | 발견됨 |
| "속도 제한" | grep "rateLimit" | 찾을 수 없음 |
모든 프로젝트에 대해 이 체크리스트를 업데이트하세요. 모든 주장에 대한 증거를 요구하세요.
완전한 현실 점검 워크플로우
┌─────────────────────────────────────────────────────────────────┐
│ 1. 개발자/AI 작업 완료 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. 현실 점검 명령 실행 │
│ - ls를 사용하여 파일 확인 │
│ - grep을 사용하여 기능 확인 │
│ - 스크린샷용 Playwright │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. Reality Checker 에이전트 활성화 │
│ - 파일 검증 검토 │
│ - 주장 교차 참조 │
│ - 스크린샷 분석 │
│ - 지표 확인 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. 출력: PASS 또는 NEEDS WORK │
│ - PASS: 자신감 있게 배포 │
│ - NEEDS WORK: 차단 문제 수정 후 다시 실행 │
└─────────────────────────────────────────────────────────────────┘
CI/CD 통합
CI 파이프라인에 현실 점검을 추가하세요:
# .github/workflows/qa-reality-check.yml
name: Reality Check
on: [pull_request]
jobs:
reality-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install chromium
- name: Start server
run: npm start &
env:
PORT: 8000
- name: Wait for server
run: sleep 5
- name: Run reality check screenshots
run: ./qa-playwright-capture.sh http://localhost:8000 public/qa-screenshots
- name: Upload screenshots
uses: actions/upload-artifact@v4
if: always()
with:
name: reality-check-screenshots
path: public/qa-screenshots/
- name: Check for console errors
run: |
ERRORS=$(cat public/qa-screenshots/performance-metrics.json | grep -c '"\[ERROR\]"' || echo "0")
if [ "$ERRORS" -gt "0" ]; then
echo "콘솔 오류 발견: $ERRORS"
exit 1
fi
- name: Check load time
run: |
LOAD_TIME=$(cat public/qa-screenshots/performance-metrics.json | grep -o '"loadTime": [0-9.]*' | head -1 | awk '{print $2}')
if (( $(echo "$LOAD_TIME > 1000" | bc -l) )); then
echo "로드 시간 너무 느림: ${LOAD_TIME}ms (목표: <1000ms)"
exit 1
fi
구축한 내용
| 구성 요소 | 목적 |
|---|---|
| Playwright 설정 | 3개의 중단점에서 자동 스크린샷 캡처 |
| 테스트 스위트 | 전체 페이지, 반응형, 상호 작용 |
| 현실 점검 스크립트 | 단일 명령 증거 수집 |
| 주장 체크리스트 | grep 결과와 AI 주장 교차 참조 |
| CI/CD 통합 | PR에 대한 자동 현실 점검 |
다음 단계
워크플로우 확장:
- 성능 점수를 위한 Lighthouse 통합 추가
- 접근성 감사 추가 (axe-core)
- 시각적 회귀 테스트 추가 (픽셀 비교)
주장 데이터베이스 구축:
- 모든 AI 주장을 증거 상태와 함께 기록
- 어떤 에이전트가 가장 많이 환각을 일으키는지 추적
- 시간 경과에 따른 정확도 점수 생성
팀과 공유:
- 현실 점검 프로세스 문서화
- 어떤 PR 승인 전에도 증거 요구
- "스크린샷 보여줘"를 팀 습관으로 만들기
일반적인 문제 해결
Playwright 테스트 시간 초과:
- 설정에서 시간 초과 증가:
timeout: 60000 - 서버가 실행 중인지 확인:
curl http://localhost:8000 - 네트워크 유휴 상태에 대한 대기 시간 연장:
await page.waitForLoadState('networkidle', { timeout: 30000 }) - 디버깅을 위해 헤드 모드로 실행:
npx playwright test --headed
스크린샷 캡처 안 됨:
- 출력 디렉토리가 존재하고 쓰기 가능한지 확인:
mkdir -p public/qa-screenshots - 파일 권한 확인:
chmod 755 public/qa-screenshots - Chromium이 설치되었는지 확인:
npx playwright install chromium - 디버그 출력으로 실행:
DEBUG=pw:api npx playwright test
콘솔 오류 캡처 안 됨:
- 페이지 이동 전에 리스너 추가:
page.goto()전에page.on('console', ...) - 오류 유형 필터링 확인:
msg.type() === 'error' - 디버깅을 위해 모든 콘솔 메시지 로깅:
page.on('console', msg => console.log(msg.text())) - 페이지가 실제로 콘텐츠를 로드하는지 확인 (빈 스크린샷 없음)
모바일 스크린샷에 데스크톱 레이아웃 표시:
- 페이지 이동 전에 뷰포트가 설정되었는지 확인
- 모바일 사용자 에이전트 추가:
await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)') - 장치 에뮬레이션 사용:
use: { ...devices['iPhone 12'] } - HTML에서 반응형 메타 태그 확인:
<meta name="viewport" content="width=device-width">
Ubuntu에서 CI/CD 파이프라인 실패:
- 시스템 의존성 설치:
sudo apt-get install -y libnss3 libnspr4 libatk1.0-0 - Playwright의 공식 이미지 사용:
mcr.microsoft.com/playwright:v1.40.0-jammy - 브라우저 설치 전에
npx playwright install-deps실행 - 컨테이너 환경에
--no-sandbox플래그 추가
고급 현실 점검 패턴
패턴 1: 시각적 회귀 테스트
예기치 않은 변경 사항을 감지하기 위해 기준선과 스크린샷 비교:
import { expect } from '@playwright/test';
test('visual regression check', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage-base.png', {
maxDiffPixels: 100, // 미미한 차이 허용
fullPage: true,
});
});
패턴 2: 접근성 감사
접근성 증거를 위해 axe-core 통합:
import AxeBuilder from '@axe-core/playwright';
test('accessibility audit', async ({ page }) => {
await page.goto('/');
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
// 결과 저장
const fs = require('fs');
fs.writeFileSync(
'public/qa-screenshots/accessibility-results.json',
JSON.stringify(accessibilityScanResults, null, 2)
);
// 심각한 위반 시 실패
const criticalViolations = accessibilityScanResults.violations.filter(
v => v.impact === 'critical' || v.impact === 'serious'
);
expect(criticalViolations).toHaveLength(0);
});
패턴 3: 성능 예산 강제 적용
성능 임계값을 초과하는 빌드 실패:
test('performance budget', async ({ page }) => {
await page.goto('/');
const metrics = await page.metrics();
const loadTime = await page.evaluate(() =>
performance.timing.loadEventEnd - performance.timing.navigationStart
);
// 예산 임계값
expect(loadTime).toBeLessThan(2000); // 최대 2초
expect(metrics.JSHeapUsedSize).toBeLessThan(5 * 1024 * 1024); // 최대 5MB
});
AI 에이전트들은 더 이상 "아주 좋다"는 말로 넘어갈 수 없습니다. 이제 스크린샷, 지표, grep 결과로 작업을 증명해야 합니다.
환각은 없습니다. 환상적인 승인도 없습니다. 오직 증거만 있습니다.
증거 기반 QA는 바로 이런 모습입니다: 명령을 실행하고, 스크린샷을 확인하고, 증명을 요구하세요.
이제 당신 차례입니다. 워크플로우에 현실 점검을 추가하고, 자신감을 가지고 배포하세요.
자주 묻는 질문
AI 에이전트가 코드를 검토할 때 왜 환각을 일으키나요?
AI 에이전트는 도움이 되고 agreeable하도록 훈련되었습니다. 그들은 검증된 것보다는 듣기 좋은 말로 응답합니다. 증거 요구 사항이 없으면 갈등을 피하기 위해 “아주 좋다”고 말합니다.
스크린샷 테스트를 위해 Playwright를 어떻게 설정하나요?
npm install -D @playwright/test로 설치하고, npx playwright install chromium을 실행하고, 뷰포트 중단점이 있는 설정 파일을 생성하고, 각 중단점에서 스크린샷을 캡처하는 테스트 스위트를 작성하세요.
승인 전에 어떤 현실 점검 명령을 실행해야 하나요?
ls로 파일 존재 여부를 확인하고, grep으로 코드에 주장된 기능이 있는지 확인하고, 스크린샷용 Playwright 테스트를 실행하고, test-results.json에서 콘솔 오류와 성능 지표를 확인하세요.
Reality Checker 에이전트는 무엇인가요?
Reality Checker는 The Agency의 전문 AI 에이전트로, 증거를 사용하여 작업을 검증합니다. 검증 명령을 실행하고, 스크린샷을 검토하고, 주장을 교차 참조하며, 특정 차단 문제가 있는 PASS 또는 NEEDS WORK를 출력합니다.
CI/CD에 현실 점검을 어떻게 통합하나요?
Playwright를 설치하고, 서버를 시작하고, 스크린샷 캡처를 실행하고, 아티팩트를 업로드하며, 콘솔 오류가 0을 초과하거나 로드 시간이 임계값을 초과하면 빌드를 실패시키는 GitHub Actions 워크플로우를 추가하세요.
스크린샷에 문제가 있는데 에이전트가 PASS라고 하면 어떻게 해야 하나요?
에이전트가 잘못 구성되었습니다. Reality Checker는 상태를 출력하기 전에 증거를 검토해야 합니다. 다음을 요구하도록 재훈련하세요: (1) 기능을 증명하는 grep 결과, (2) 스크린샷 검토, (3) 임계값 내의 지표.
우리 팀이 증거 기반 QA를 채택하도록 하려면 어떻게 해야 하나요?
현실 점검 프로세스를 문서화하고, 통과 테스트를 요구하는 CI/CD 게이트를 추가하고, PR 승인에 스크린샷 검토를 의무화하며, 어떤 에이전트가 가장 정확한 평가를 내리는지 추적하세요.
Top comments (0)