AI 에이전트 스크린샷 증거로 환각 방지 및 작업 증명 방법

Ashley Innocent

Ashley Innocent

19 March 2026

AI 에이전트 스크린샷 증거로 환각 방지 및 작업 증명 방법

Apidog 엔터프라이즈

온프레미스 배포

SSO & RBAC

SOC 2 준수

Apidog Enterprise 살펴보기

핵심 요약

4단계로 AI 환각을 멈추세요: (1) Playwright를 설치하고 중단점(데스크톱, 태블릿, 모바일)을 구성합니다. (2) 전체 페이지, 반응형 레이아웃 및 상호 작용을 캡처하는 스크린샷 테스트 스위트를 만듭니다. (3) ./qa-playwright-capture.sh를 실행하여 증거를 수집합니다. (4) 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는 이를 변화시킵니다. 의견 대신 다음을 얻습니다:

더 이상 "나를 믿어줘"는 없습니다. 오직 증명만.

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.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 코드 세션을 여세요:

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에 대한 자동 현실 점검

다음 단계

워크플로우 확장:

주장 데이터베이스 구축:

팀과 공유:

일반적인 문제 해결

Playwright 테스트 시간 초과:

스크린샷 캡처 안 됨:

콘솔 오류 캡처 안 됨:

모바일 스크린샷에 데스크톱 레이아웃 표시:

Ubuntu에서 CI/CD 파이프라인 실패:

고급 현실 점검 패턴

패턴 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는 이런 모습입니다: 명령을 실행하고, 스크린샷을 확인하고, 증명을 요구합니다.

이제 당신 차례입니다: 당신의 워크플로우에 현실 점검을 추가하세요. 자신감을 가지고 배포하세요.

button

자주 묻는 질문

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 승인에 스크린샷 검토를 의무화하며, 어떤 에이전트가 가장 정확한 평가를 내리는지 추적하세요.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요

AI 에이전트 스크린샷 증거로 환각 방지 및 작업 증명 방법