본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 6장: 시각적 회귀 테스트
2026년 3월 14일·AI / ML·

6장: 시각적 회귀 테스트

픽셀 비교의 한계를 넘어 Visual AI 기반 시각적 회귀 테스트를 다룹니다. Applitools Eyes, Percy, Chromatic 비교 분석과 동적 콘텐츠 처리, 반응형 레이아웃 테스트, 스토리북 통합을 안내합니다.

17분601자9개 섹션
testingautomationquality-assuranceai
공유
ai-testing6 / 10
12345678910
이전5장: 변이 테스트(Mutation Testing)다음7장: 테스트 유지보수 자동화

학습 목표

  • 전통적 픽셀 비교 방식의 한계를 이해합니다
  • Visual AI 기반 시각적 회귀 테스트의 동작 원리를 파악합니다
  • Applitools Eyes, Percy, Chromatic의 특성을 비교 분석합니다
  • 동적 콘텐츠, 반응형 레이아웃, 스토리북 통합 전략을 학습합니다

시각적 회귀 테스트의 필요성

기능 테스트가 "동작이 올바른지"를 검증한다면, Visual Regression Test(시각적 회귀 테스트)는 "보이는 것이 올바른지"를 검증합니다. CSS 변경, 폰트 누락, 레이아웃 깨짐, 요소 겹침 등 기능 테스트로는 잡을 수 없는 시각적 결함을 감지합니다.

기능 테스트가 놓치는 것

실제 프로젝트에서 시각적 결함은 배포 후 사용자가 먼저 발견하는 경우가 많습니다. 시각적 회귀 테스트는 이 간극을 메웁니다.


픽셀 비교의 한계

전통적인 시각적 테스트는 Pixel-to-pixel Comparison(픽셀 대 픽셀 비교) 방식입니다. 기준 스크린샷과 현재 스크린샷을 픽셀 단위로 비교하여 차이를 감지합니다.

문제점

문제설명
과도한 노이즈안티앨리어싱, 서브픽셀 렌더링 차이로 위양성 발생
동적 콘텐츠날짜, 광고, 추천 콘텐츠 등이 매번 달라져 실패
플랫폼 차이동일 코드가 OS/브라우저별로 다르게 렌더링
유지보수 부담의도적 변경 시마다 기준 이미지를 수동 업데이트
의미 해석 불가중요한 변화와 사소한 변화를 구분하지 못함
plaintext
[기준 이미지] vs [현재 이미지]
  -> 1px 안티앨리어싱 차이 -> 실패 (위양성)
  -> 날짜 텍스트 변경 -> 실패 (위양성)
  -> 버튼 누락 -> 실패 (진양성)
 
모두 "실패"로 보고되어 진짜 문제를 놓치기 쉽습니다.
Info

픽셀 비교 도구에서 threshold(임계값)를 높이면 위양성은 줄지만, 미묘한 실제 결함도 함께 무시됩니다. 이 딜레마가 Visual AI의 등장 배경입니다.


Visual AI의 동작 원리

Visual AI는 픽셀 수준의 비교가 아닌, 사람의 시각 인지 방식을 모방하여 "의미 있는" 차이만 감지합니다.

Applitools Eyes의 접근 방식

Applitools Eyes는 강화 학습(Reinforcement Learning) 기반의 Visual AI를 사용합니다. 수억 장의 스크린샷 비교 데이터에서 학습하여, 사람이 "문제"라고 판단할 변화와 "무시해도 될" 변화를 구분합니다.

비교 수준(Match Level)

Applitools는 네 가지 비교 수준을 제공합니다.

수준설명사용 시나리오
Exact픽셀 단위 정밀 비교디자인 시스템 검증
StrictAI 기반 -- 사람이 인지할 수 있는 차이만 감지일반적인 시각적 테스트
Content콘텐츠(텍스트/이미지)만 비교, 스타일 무시콘텐츠 중심 검증
Layout구조와 배치만 비교, 콘텐츠 무시반응형 레이아웃 검증

도구 비교 분석

Applitools Eyes

visual-test-applitools.spec.ts
typescript
import { test } from "@playwright/test";
import { Eyes, Target, Configuration, BatchInfo } from "@applitools/eyes-playwright";
 
test.describe("시각적 회귀 테스트 -- Applitools", () => {
  let eyes: Eyes;
 
  test.beforeEach(async () => {
    eyes = new Eyes();
    const config = new Configuration();
    config.setBatch(new BatchInfo("Sprint 42 Visual Tests"));
    config.setApiKey(process.env.APPLITOOLS_API_KEY!);
    
    // 반응형 테스트를 위한 다중 뷰포트
    config.addBrowser(1920, 1080, "chrome");
    config.addBrowser(1366, 768, "firefox");
    config.addBrowser(375, 812, "chrome"); // 모바일
    
    eyes.setConfiguration(config);
  });
 
  test.afterEach(async () => {
    await eyes.close();
  });
 
  test("메인 페이지 시각적 검증", async ({ page }) => {
    await eyes.open(page, "MyApp", "메인 페이지");
    await page.goto("/");
 
    // 전체 페이지 캡처
    await eyes.check("전체 페이지", Target.window().fully());
 
    // 특정 영역만 캡처
    await eyes.check(
      "헤더 영역",
      Target.region("#header").strict()
    );
 
    // 동적 콘텐츠 무시
    await eyes.check(
      "메인 콘텐츠",
      Target.region("#main-content")
        .ignoreRegions(".ad-banner", ".datetime-display")
        .layout()
    );
  });
 
  test("다크 모드 시각적 검증", async ({ page }) => {
    await eyes.open(page, "MyApp", "다크 모드");
    await page.goto("/");
 
    // 다크 모드 전환
    await page.click("[data-testid='theme-toggle']");
    await page.waitForTimeout(300); // 트랜지션 완료 대기
 
    await eyes.check("다크 모드 전체", Target.window().fully().strict());
  });
});

Percy

Percy(BrowserStack)는 스크린샷 비교 기반의 시각적 테스트 플랫폼입니다. Applitools보다 단순하지만, CI 통합이 쉽고 가격이 합리적입니다.

visual-test-percy.spec.ts
typescript
import { test } from "@playwright/test";
import percySnapshot from "@percy/playwright";
 
test.describe("시각적 회귀 테스트 -- Percy", () => {
  test("메인 페이지 스냅샷", async ({ page }) => {
    await page.goto("/");
    await percySnapshot(page, "메인 페이지", {
      widths: [375, 768, 1280, 1920],
      minHeight: 1024,
    });
  });
 
  test("상품 목록 페이지 스냅샷", async ({ page }) => {
    await page.goto("/products");
    
    // 데이터 로딩 완료 대기
    await page.waitForSelector("[data-testid='product-list']");
    
    await percySnapshot(page, "상품 목록", {
      percyCSS: `
        .ad-banner { display: none !important; }
        .timestamp { visibility: hidden !important; }
      `,
    });
  });
});

Chromatic

Chromatic은 Storybook과 깊이 통합된 시각적 테스트 플랫폼입니다. 컴포넌트 단위의 시각적 테스트에 최적화되어 있습니다.

Chromatic 설치 및 실행
bash
npm install --save-dev chromatic
npx chromatic --project-token=YOUR_TOKEN

도구 비교 요약

특성Applitools EyesPercyChromatic
AI 수준Visual AI (강화 학습)스마트 비교DOM 기반 비교
동적 콘텐츠 처리자동 무시 (AI)CSS 오버라이드Storybook args 제어
반응형 테스트다중 뷰포트 자동다중 너비 지정뷰포트 애드온
Storybook 통합가능가능네이티브
가격 모델스크린샷 기반스냅샷 기반스냅샷 기반
위양성 비율매우 낮음낮음중간
Tip

도구 선택 기준은 프로젝트의 특성에 따라 달라집니다. 컴포넌트 라이브러리 프로젝트라면 Chromatic이 적합하고, 풀 페이지 E2E 시각적 테스트에는 Applitools가, 합리적인 비용으로 시작하고 싶다면 Percy가 좋은 선택입니다.


동적 콘텐츠 처리

시각적 테스트에서 가장 큰 위양성 원인은 동적 콘텐츠입니다. 날짜, 시간, 광고, 추천 항목, 사용자별 데이터 등이 매번 달라져 테스트가 실패합니다.

처리 전략

dynamic-content-handling.ts
typescript
import { Page } from "@playwright/test";
 
/**
 * 시각적 테스트를 위해 동적 콘텐츠를 안정화합니다.
 */
export async function stabilizeForVisualTest(page: Page): Promise<void> {
  await page.addStyleTag({
    content: `
      /* 애니메이션 비활성화 */
      *, *::before, *::after {
        animation-duration: 0s !important;
        animation-delay: 0s !important;
        transition-duration: 0s !important;
        transition-delay: 0s !important;
      }
 
      /* 캐러셀 자동 전환 비활성화 */
      .carousel { animation-play-state: paused !important; }
 
      /* 커서 깜빡임 비활성화 */
      * { caret-color: transparent !important; }
    `,
  });
 
  // 날짜/시간 표시를 고정값으로 대체
  await page.evaluate(() => {
    document.querySelectorAll("[data-dynamic='date']").forEach((el) => {
      el.textContent = "2026-01-01";
    });
    document.querySelectorAll("[data-dynamic='time']").forEach((el) => {
      el.textContent = "12:00:00";
    });
  });
 
  // 이미지 로딩 완료 대기
  await page.evaluate(() => {
    return Promise.all(
      Array.from(document.images)
        .filter((img) => !img.complete)
        .map(
          (img) =>
            new Promise((resolve) => {
              img.onload = resolve;
              img.onerror = resolve;
            })
        )
    );
  });
}

반응형 레이아웃 테스트

반응형 디자인은 다양한 화면 크기에서 레이아웃이 올바르게 표시되는지 검증해야 합니다. Visual AI는 이 과정을 크게 단순화합니다.

핵심 브레이크포인트

responsive-visual.spec.ts
typescript
import { test, expect } from "@playwright/test";
 
const viewports = [
  { name: "모바일", width: 375, height: 812 },
  { name: "태블릿", width: 768, height: 1024 },
  { name: "데스크톱", width: 1280, height: 800 },
  { name: "와이드", width: 1920, height: 1080 },
];
 
for (const viewport of viewports) {
  test(`${viewport.name} 뷰포트에서 메인 페이지 검증`, async ({ page }) => {
    await page.setViewportSize({
      width: viewport.width,
      height: viewport.height,
    });
    await page.goto("/");
 
    // 레이아웃 모드로 비교 -- 콘텐츠 차이 무시, 구조만 검증
    const screenshot = await page.screenshot({ fullPage: true });
    expect(screenshot).toMatchSnapshot(
      `main-page-${viewport.name}.png`,
      { threshold: 0.05 }
    );
  });
}
Warning

반응형 테스트에서는 반드시 모든 기준 브레이크포인트를 포함해야 합니다. 특히 브레이크포인트 경계값(예: 767px와 768px)에서의 동작을 확인하는 것이 중요합니다. 이 경계에서 레이아웃이 갑자기 전환되면서 의도치 않은 겹침이나 오버플로우가 발생할 수 있습니다.


컴포넌트 스토리북 통합

Storybook과 시각적 테스트를 통합하면 컴포넌트 수준에서 시각적 회귀를 감지할 수 있습니다.

Storybook 설정

Button.stories.tsx
typescript
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
 
const meta: Meta<typeof Button> = {
  title: "UI/Button",
  component: Button,
  argTypes: {
    variant: {
      control: "select",
      options: ["primary", "secondary", "danger", "ghost"],
    },
    size: {
      control: "select",
      options: ["sm", "md", "lg"],
    },
  },
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
export const Primary: Story = {
  args: { variant: "primary", children: "기본 버튼" },
};
 
export const Secondary: Story = {
  args: { variant: "secondary", children: "보조 버튼" },
};
 
export const Danger: Story = {
  args: { variant: "danger", children: "삭제" },
};
 
export const AllVariants: Story = {
  render: () => (
    <div style={{ display: "flex", gap: "16px", padding: "16px" }}>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="danger">Danger</Button>
      <Button variant="ghost">Ghost</Button>
    </div>
  ),
};
 
export const Disabled: Story = {
  args: { variant: "primary", children: "비활성", disabled: true },
};

각 스토리가 곧 시각적 테스트 케이스가 됩니다. Chromatic이나 Applitools는 모든 스토리를 자동으로 캡처하고, 이전 빌드와 비교합니다.


정리

이 장에서는 시각적 회귀 테스트의 전통적 방식과 AI 기반 방식을 비교하고, 실전 활용법을 살펴보았습니다.

핵심 내용을 정리하면 다음과 같습니다.

  • 픽셀 비교 방식은 동적 콘텐츠, 안티앨리어싱, 플랫폼 차이로 인한 위양성이 많습니다
  • Visual AI(Applitools Eyes)는 강화 학습으로 의미 있는 변화만 감지하여 위양성을 크게 줄입니다
  • 동적 콘텐츠는 영역 무시, 레이아웃 모드, 고정값 대체 전략으로 처리합니다
  • 반응형 테스트는 핵심 브레이크포인트와 경계값에서의 검증이 필수입니다
  • Storybook 통합은 컴포넌트 수준의 시각적 회귀를 효율적으로 감지합니다

다음 장 미리보기

7장에서는 테스트 유지보수 자동화를 다룹니다. 테스트 부패(Test Rot) 문제의 근본 원인을 분석하고, AI 기반 셀프 힐링, 셀렉터 자동 재바인딩, 중복 테스트 감지, 커버리지 갭 분석 등 유지보수 비용을 줄이는 전략을 살펴봅니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#testing#automation#quality-assurance#ai

관련 글

AI / ML

7장: 테스트 유지보수 자동화

테스트 로트(Test Rot) 문제의 근본 원인과 AI 기반 셀프 힐링, 셀렉터 자동 재바인딩, 테스트 코드 리팩터링, 중복 테스트 감지, 커버리지 갭 분석 등 유지보수 비용 절감 전략을 다룹니다.

2026년 3월 16일·16분
AI / ML

5장: 변이 테스트(Mutation Testing)

변이 테스트의 원리와 변이 연산자를 이해하고, Stryker, PIT, mutmut 도구로 AI 생성 테스트의 품질을 검증하는 방법을 다룹니다. 변이 점수 측정과 비용-효과 분석도 포함합니다.

2026년 3월 12일·15분
AI / ML

8장: AI QA 파이프라인 구축

변경 영향 분석 기반 테스트 선택, 위험 기반 우선순위, 플레이키 테스트 자동 격리, 병렬 실행 최적화, 결함 예측, GitHub Actions/GitLab CI 통합을 다루는 AI QA 파이프라인 구축 가이드입니다.

2026년 3월 18일·17분
이전 글5장: 변이 테스트(Mutation Testing)
다음 글7장: 테스트 유지보수 자동화

댓글

목차

약 17분 남음
  • 학습 목표
  • 시각적 회귀 테스트의 필요성
    • 기능 테스트가 놓치는 것
  • 픽셀 비교의 한계
    • 문제점
  • Visual AI의 동작 원리
    • Applitools Eyes의 접근 방식
    • 비교 수준(Match Level)
  • 도구 비교 분석
    • Applitools Eyes
    • Percy
    • Chromatic
    • 도구 비교 요약
  • 동적 콘텐츠 처리
    • 처리 전략
  • 반응형 레이아웃 테스트
    • 핵심 브레이크포인트
  • 컴포넌트 스토리북 통합
    • Storybook 설정
  • 정리
    • 다음 장 미리보기