시각적 회귀 테스트로 UI 변경을 검증하는 방법
시각적 회귀 테스트의 비교 방식, 기준 이미지 관리, 플래키니스 제어와 Playwright 기반 CI 운영 방법을 정리한다.
2026-08-14 · 최초 발행 2025-12-24
화면이 바뀌었을 때 기능 테스트만으로는 부족하다
디자인 수정, 라이브러리 교체, 브라우저 업그레이드 뒤에는 기능은 정상이어도 레이아웃이나 스타일이 깨질 수 있다. 시각적 회귀 테스트는 기준 화면(baseline)과 변경 뒤 화면(current)을 같은 조건에서 캡처해 픽셀 또는 지각(perceptual) 차이를 찾는 방식이다. 차이 연산 결과를 임계치(threshold)로 판정하고, 실패 결과와 기준 이미지 갱신 여부를 리뷰 흐름으로 연결한다.
픽셀 단위 비교, 지각 기반 비교(SSIM/PDiff), 레이아웃 또는 DOM 스냅샷은 서로 보완적으로 쓸 수 있다. 다만 브라우저, 폰트, GPU, 색역이 달라지면 비교 자체가 흔들리므로 캡처 환경을 고정하는 작업이 선행돼야 한다.
기준 이미지와 실행 환경을 함께 관리한다
기준 이미지는 단순한 결과물이 아니라 변경 이력을 가진 검증 기준이다. 브랜치 전략과 승인 워크플로에 연결하고, 갱신을 허용할 담당자·라벨·코멘트 규칙을 정해둬야 한다.
캡처 과정에서는 애니메이션을 끄고 폰트·시간·네트워크 데이터를 고정한다. 스크롤과 포커스 상태도 정규화 대상이다. 뷰포트, DPR, 컬러 프로파일, OS와 브라우저 버전을 핀으로 고정하면 불필요한 차이를 줄일 수 있다.
비교 엔진은 픽셀 기반 또는 퍼셉처얼 알고리즘을 선택하고, diff-mask와 무시 영역을 설정한다. 전체 테스트에 적용하는 임계치와 특정 테스트에만 적용하는 상이 임계치를 병행해 회귀 민감도를 조절할 수 있다.
PR에서 테스트를 실행하고 실패하면 diff 아티팩트를 올린다. 리뷰어는 기준 이미지를 바꿀지, 버그를 고칠지 결정한다. 기준 갱신은 main에 병합된 경우에만 허용하는 가드레일을 둘 수 있다. 실패율, 플래키니스 지표, 평균 리뷰 시간은 대시보드로 추적하고, 테스트 범위·커버리지와 테스트 부채도 계속 관리한다.
비교와 승인 흐름
비교 방식마다 다른 운영 특성
| 방법 | 성능(속도/리소스) | 확장성(대규모 스냅샷) | 일관성(플래키) | 안정성(오탐/미탐) | 운영 편의(설정/유지) |
|---|---|---|---|---|---|
| 픽셀 기반 비교 | 매우 빠름, CPU 경량 | 매우 높음 | 환경 민감도 높음 | 미세 차이 오탐 증가 | 설정 단순, 무시 영역 필요 |
| 지각 기반(SSIM/PDiff) | 중간, GPU/CPU 부담 | 높음 | 픽셀 대비 양호 | 노이즈 내성, 미탐 가능 | 임계치 튜닝 필요 |
| DOM/레이아웃 스냅샷 보조 | 매우 빠름 | 매우 높음 | 렌더·폰트 영향 적음 | 시각적 차이 누락 가능 | 비주얼 테스트 보조 용도 |
| ML/AI 보정형 비교 | 중간~느림 | 중간 | 노이즈에 강함 | 의미 기반 탐지, 예측 불안정 가능 | 도구 의존성·비용 고려 |
화면 변화가 위험해지는 지점
디자인 시스템과 스토리북 컴포넌트에서는 토큰 변경, 테마 추가, 아이콘 교체의 영향을 확인할 수 있다. 장바구니·결제·체크아웃 같은 크리티컬 경로 E2E 화면에도 적용할 수 있다.
반응형, 다국어, 다크모드에서는 뷰포트 매트릭스를 통해 해상도와 언어 확장에 따른 레이아웃 붕괴를 찾는다. 이메일과 PDF 템플릿 변경 시에는 클라이언트별 표시 차이를 승인 흐름으로 확인할 수 있으며, 데이터 시각화나 캔버스 그래픽에서는 축·범례·렌더링 정합성을 검증한다.
이 방식은 시각적 결함을 조기에 찾아 릴리즈를 차단하고, 리뷰와 QA의 반복 작업을 줄인다. 디자인 토큰과 브랜드 확장에 대응하는 힘을 높이며, 오버레이 diff 리포트로 원인 파악 시간을 단축한다. 기능 테스트를 보완하는 장기적 커버리지도 확보할 수 있다.
Playwright로 기준 화면을 검사하기
전제조건: Node.js 18+, Playwright 1.48+, 헤드리스 크롬 고정, CI에서 Ubuntu 22.04, 한글 폰트 패키지 설치
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
use: {
browserName: 'chromium',
viewport: { width: 1280, height: 800 },
colorScheme: 'light',
deviceScaleFactor: 1,
// 안정화 옵션
javaScriptEnabled: true,
ignoreHTTPSErrors: true,
timezoneId: 'UTC',
locale: 'en-US',
launchOptions: { args: ['--font-render-hinting=none', '--disable-gpu'] },
},
reporter: [['html', { open: 'never' }]],
expect: {
toHaveScreenshot: {
maxDiffPixelRatio: 0.001, // 0.1% 허용
threshold: 0.2, // 퍼셉처얼 임계치(내부 엔진에 따라 무시됨)
animations: 'disabled',
scale: 'css',
maskColor: '#ff00ff',
},
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
예시 테스트
import { test, expect } from '@playwright/test';
test('로그인 화면: 첫 화면 회귀 검증', async ({ page }) => {
await page.route('**/api/session', route => route.fulfill({ status: 200, body: '{"user":null}' }));
await page.goto('http://localhost:3000/login', { waitUntil: 'networkidle' });
// 동적 요소 마스킹
const clock = page.locator('[data-dynamic="clock"]');
const ads = page.locator('.ad-banner');
await expect(page).toHaveScreenshot('login.png', { mask: [clock, ads] });
});
기준 이미지는 로컬에서 npx playwright test --update-snapshots를 실행한 뒤 PR로 갱신한다. CI에서는 main 브랜치에 병합된 커밋만 기준 업데이트를 허용한다. Ubuntu CI에는 fonts-noto 패키지를 설치하고, 프로젝트에는 웹폰트를 번들링해 폰트 차이를 줄인다.
오탐을 줄이되 회귀를 가리지 않으려면
브라우저 버전 핀, 웹폰트 self-host, 애니메이션 비활성화로 렌더링 결정론을 확보할 수 있다. 대신 환경 관리 복잡도는 커진다.
전역 기본값은 보수적으로 두고 노이즈가 많은 경우에만 상이 임계치를 허용한다. 너무 느슨하게 설정하면 미탐 위험이 있다. 광고, 시간, 랜덤 데이터에는 무시 영역이나 마스킹을 적용할 수 있지만, 범위를 과도하게 넓히면 의미 있는 회귀를 놓친다.
API 모킹과 시드 데이터는 네트워크와 데이터를 고정하는 데 도움이 되지만 실제 환경과의 괴리를 키울 수 있다. 기준 이미지 변경은 PR 리뷰, 라벨, 체크리스트로 통제하며 승인 지연이 배포 속도에 영향을 줄 수 있다는 점도 고려한다. 캐시, 병렬화, 스토리북 정적 캡처는 실행을 가속하지만 대규모 스냅샷에서는 스토리지와 CI 분 단가를 함께 봐야 한다.
컴포넌트에서 배포 파이프라인까지 넓히기
1단계에서는 스토리북 컴포넌트 스냅샷을 도입하고 픽셀 기반 비교로 빠르게 정착한다. 이어 핵심 사용자 흐름 3~5개에 E2E 시각적 테스트를 적용하며 데이터 모킹을 정착시킨다.
그 다음 반응형·다국어·다크모드 매트릭스로 범위를 확장하고 크로스 브라우저 샘플링을 실행한다. 기준 업데이트 정책과 실패율·리뷰 시간을 위한 SLO를 수립하고, 대시보드와 테스트 부채 정리 루틴을 운영하면 CI/CD의 배포 신뢰도와 연결할 수 있다.