비주얼 회귀 테스트 자동화 6주 후기(스크린샷 312장 중 거짓 실패 47건을 줄인 기록

비주얼 회귀 테스트 자동화

비주얼 회귀 테스트를 처음 도입할 때는 화면 스크린샷을 많이 찍어두면 UI 버그를 쉽게 잡을 수 있을 거라고 생각했습니다. 버튼 위치가 바뀌거나, 반응형 화면이 깨지거나, 배포 전에 의도하지 않은 스타일 변경을 자동으로 발견할 수 있을 것 같았습니다. 하지만 실제로 2026년 3월부터 6주 동안 React 프로젝트에 비주얼 회귀 테스트를 붙여보니, 문제는 스크린샷 수가 아니라 실패 결과를 믿을 수 있느냐였습니다.

테스트 환경은 React 프로젝트, Playwright, GitHub Actions, Windows 11 로컬 환경이었습니다. 테스트한 페이지 수는 18개였고, 반응형 뷰포트는 3개로 나눴습니다. 비교한 해상도는 모바일 375px, 태블릿 768px, 데스크톱 1440px이었습니다. 전체 캡처한 스크린샷 수는 312장이었습니다.

처음에는 실패가 많을수록 잘 잡는 테스트라고 착각했다

초기 비주얼 테스트 실패 수는 68건이었습니다. 처음에는 “생각보다 UI 변경이 많았나?” 싶어서 긴장했습니다. 그런데 하나씩 확인해보니 실제 UI 버그로 확인된 실패는 21건뿐이었습니다. 나머지 47건은 거짓 실패였습니다.

거짓 실패는 실제 화면 버그가 아닌데 테스트가 실패로 잡는 경우였습니다. 폰트 렌더링 차이, 이미지 로딩 지연, 날짜 텍스트 변동, 애니메이션 캡처 같은 문제가 대부분이었습니다. 특히 로컬 Windows 11 환경에서 찍은 기준 이미지와 GitHub Actions에서 실행된 결과 이미지가 미묘하게 달라지는 일이 자주 있었습니다.

가장 자주 발생한 문제는 폰트와 로딩 타이밍이었다

가장 자주 발생한 문제는 폰트 렌더링 차이였습니다. 같은 페이지라도 로컬에서는 글자 폭이 조금 다르게 보이고, CI에서는 줄바꿈 위치가 달라졌습니다. 겉으로 보면 큰 문제는 아니었지만, 이미지 비교에서는 실패로 잡혔습니다.

두 번째는 이미지 로딩 지연이었습니다. 카드 리스트나 배너 영역에서 이미지가 완전히 로딩되기 전에 캡처가 실행되면 회색 placeholder가 찍히거나, 이미지 높이가 늦게 잡히면서 버튼 위치가 달라졌습니다.

가장 당황했던 실패 사례는 로딩 완료를 기다리지 않고 캡처해 버튼 위치가 매번 다르게 찍힌 경험이었습니다. 버튼 자체에는 문제가 없었는데, 상단 이미지 로딩이 끝나기 전과 후의 레이아웃 높이가 달라서 하단 버튼이 8~12px씩 움직였습니다. 처음에는 CSS 버그라고 생각했지만, 실제 원인은 캡처 타이밍이었습니다.

날짜 텍스트 하나 때문에 매일 실패한 테스트

또 다른 실패 사례는 날짜가 들어간 영역이었습니다. 대시보드 상단에 “오늘 기준 2026-03-xx”처럼 날짜가 표시되는 영역이 있었는데, 이 부분을 마스킹하지 않아 매일 테스트가 실패했습니다.

처음에는 baseline 이미지를 매일 갱신하면 해결될 줄 알았습니다. 하지만 그렇게 하면 비주얼 회귀 테스트의 의미가 사라졌습니다. 기준 이미지가 매번 바뀌면 실제 UI 변화와 데이터 변화가 섞이기 때문입니다. 결국 날짜 영역은 마스킹 처리했습니다. 날짜, 시간, 랜덤 ID, 사용자별 문구처럼 매번 바뀌는 영역은 비교 대상에서 제외해야 했습니다.

비주얼 테스트 개선표

문제 유형초기 실패 횟수실제 버그 여부개선 방식개선 후 실패 횟수배운 점
폰트 렌더링 차이18건대부분 거짓 실패테스트 환경 폰트 고정, 비교 임계값 조정5건폰트 차이는 실제 버그와 구분 기준이 필요했음
이미지 로딩 지연14건일부 실제 레이아웃 문제 포함이미지 로딩 완료 대기, network idle 조건 추가3건캡처 전에 화면 안정화가 먼저였음
날짜 텍스트 변동9건거짓 실패날짜 영역 마스킹1건매일 바뀌는 데이터는 비교하면 안 됐음
애니메이션 캡처11건거짓 실패가 많음CSS 애니메이션 비활성화, transition 제거2건움직이는 화면은 캡처 시점에 따라 계속 달라졌음
반응형 깨짐10건실제 UI 버그 다수375px, 768px, 1440px 기준으로 컴포넌트 수정1건반응형 테스트는 실제 버그 탐지에 도움이 컸음
기준 스크린샷 관리 오류6건혼합baseline 갱신 규칙 작성, PR에서 변경 사유 기록0건기준 이미지를 아무 때나 바꾸면 테스트 신뢰도가 떨어졌음

거짓 실패 47건을 12건으로 줄인 과정

초기에는 거짓 실패가 47건이었습니다. 이 정도면 테스트 결과를 볼 때마다 “이번에도 또 가짜 실패겠지”라는 생각부터 들었습니다. 실제로 팀에 공유하기도 애매했습니다. 테스트가 자주 실패하지만 대부분 의미 없는 실패라면, 결국 아무도 알림을 믿지 않게 됩니다.

개선 후 거짓 실패 수는 12건으로 줄었습니다. 가장 효과가 컸던 방법은 다섯 가지였습니다. 애니메이션 비활성화, 날짜 영역 마스킹, 이미지 로딩 대기, 기준 스크린샷 관리, 실패 임계값 조정입니다.

특히 애니메이션 비활성화는 바로 효과가 있었습니다. 토스트 메시지, fade-in 카드, skeleton UI가 캡처되는 시점에 따라 다르게 찍히던 문제가 줄었습니다. 테스트 전역 CSS에 transition과 animation을 꺼두는 설정을 넣으니 반복 실패가 크게 줄었습니다.

Playwright 캡처 전에 기다리는 조건을 명확히 했다

초기에는 페이지에 접속한 뒤 바로 스크린샷을 찍었습니다. 이 방식은 빠르지만 불안정했습니다. 이미지가 늦게 로딩되거나, API 응답 후 레이아웃이 다시 잡히는 화면에서는 캡처 결과가 매번 달랐습니다.

이후에는 캡처 전에 대기 조건을 추가했습니다. 페이지 로드 완료, 주요 이미지 로딩 확인, 특정 selector 표시 여부, 네트워크 안정화 상태를 확인한 뒤 스크린샷을 찍었습니다.

await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
await page.locator('[data-testid="main-content"]').waitFor();
await page.locator('img').first().waitFor();
await expect(page).toHaveScreenshot('dashboard-1440.png', {
  maxDiffPixelRatio: 0.01
});

이 코드를 넣었다고 모든 문제가 사라진 것은 아니지만, 최소한 로딩 중 화면을 캡처하는 실수는 줄었습니다. 비주얼 테스트에서는 “언제 찍느냐”가 “무엇을 찍느냐”만큼 중요했습니다.

반응형 뷰포트 3개는 꼭 필요했다

비교한 해상도는 375px, 768px, 1440px이었습니다. 처음에는 데스크톱 화면만 테스트해도 충분하다고 생각했습니다. 하지만 실제 UI 버그로 확인된 21건 중 일부는 모바일이나 태블릿에서만 보였습니다.

특히 375px 화면에서는 버튼이 줄바꿈되거나, 카드 내부 텍스트가 넘치거나, 하단 CTA가 화면 밖으로 밀리는 문제가 있었습니다. 768px에서는 사이드바가 접히는 경계 구간에서 레이아웃이 애매하게 깨졌습니다. 1440px에서는 여백이 너무 넓어지거나 이미지 비율이 어색하게 보이는 문제가 있었습니다.

반응형 뷰포트 수를 3개로 고정하니 테스트 범위와 실행 시간 사이의 균형이 맞았습니다. 더 많이 늘리면 좋을 수도 있지만, 유지보수와 CI 시간이 부담됐습니다.

CI 실행 시간은 11분 40초에서 6분 25초로 줄였다

초기 CI 평균 실행 시간은 11분 40초였습니다. 스크린샷 312장을 모두 비교하다 보니 GitHub Actions에서 시간이 꽤 걸렸습니다. 처음에는 그냥 감수하려고 했지만, PR마다 10분 넘게 기다리는 것은 부담이었습니다.

개선 후 CI 평균 실행 시간은 6분 25초로 줄었습니다. 줄인 방식은 단순했습니다. 첫째, 모든 페이지를 매번 테스트하지 않고 변경 영향이 큰 페이지를 우선 실행했습니다. 둘째, 불필요한 중복 캡처를 제거했습니다. 셋째, baseline 이미지 관리를 정리해 비교 대상이 꼬이지 않게 했습니다.

무조건 많은 화면을 찍는 방식은 오래가지 않았습니다. 실제로 자주 깨지는 핵심 페이지와 반응형 경계 구간을 중심으로 테스트하는 편이 더 현실적이었습니다.

기준 스크린샷 관리는 생각보다 까다로웠다

비주얼 회귀 테스트에서 기준 스크린샷은 사실상 테스트의 기준선입니다. 그런데 초반에는 baseline 이미지를 너무 쉽게 갱신했습니다. 실패가 나오면 “이번 변경이 맞겠지” 하고 기준 이미지를 덮어썼습니다.

이 방식은 위험했습니다. 실제 버그가 섞여 있어도 기준 이미지로 등록해버리면 이후에는 정상으로 취급됩니다. 그래서 기준 스크린샷 갱신 규칙을 만들었습니다. UI 변경 의도가 명확한 PR에서만 baseline을 갱신하고, 변경 사유를 PR 설명에 적었습니다.

예를 들어 “버튼 padding 변경으로 375px, 768px 기준 이미지 갱신”처럼 이유를 남겼습니다. 이 과정은 귀찮았지만 테스트 신뢰도를 유지하는 데 필요했습니다.

비교 임계값은 너무 엄격하면 오히려 독이었다

처음에는 픽셀 차이를 거의 허용하지 않도록 설정했습니다. 그래야 작은 UI 변경도 잡을 수 있을 것 같았습니다. 하지만 폰트 렌더링 차이나 안티앨리어싱 차이까지 모두 실패로 잡히면서 거짓 실패가 늘어났습니다.

이후에는 비교 임계값을 조정했습니다. 모든 차이를 허용한 것은 아니지만, 실제 사용자에게 보이지 않는 미세한 픽셀 차이는 넘길 수 있게 했습니다. 중요한 것은 1px 차이를 모두 잡는 것이 아니라, 버튼이 밀리거나 영역이 깨지는 실제 UI 문제를 잡는 것이었습니다.

팀에 적용하려면 실패 알림을 믿을 수 있어야 했다

혼자 테스트할 때는 거짓 실패가 많아도 직접 확인하면 됐습니다. 하지만 팀에 적용하려면 이야기가 달랐습니다. 실패 알림이 자주 틀리면 팀원들이 테스트를 무시하게 됩니다. 실제로 초반에는 실패가 많아 PR 확인 시간이 길어졌습니다.

그래서 비교 기준을 바꿨습니다. 테스트 수를 늘리기보다 실패 원인을 줄였습니다. 날짜처럼 매번 바뀌는 영역은 마스킹하고, 애니메이션은 끄고, 이미지 로딩을 기다리고, 기준 스크린샷 갱신 규칙을 만들었습니다. 이 과정을 거친 뒤에야 테스트 결과를 공유할 수 있었습니다.

비교 기준별 실제 체감

실제 버그 탐지율

초기 실패 68건 중 실제 UI 버그는 21건이었습니다. 거짓 실패가 많았지만, 반응형 깨짐과 버튼 위치 문제를 잡은 것은 분명 도움이 됐습니다.

거짓 실패 수

거짓 실패는 47건에서 12건으로 줄었습니다. 이 수치가 줄어들면서 테스트 결과를 더 신뢰할 수 있게 됐습니다.

CI 실행 시간

처음 11분 40초였던 CI 평균 실행 시간은 개선 후 6분 25초가 됐습니다. 테스트 범위를 줄인 것이 아니라 중복과 불안정한 캡처를 줄인 결과였습니다.

유지보수 난이도

기준 스크린샷이 늘어날수록 관리 난이도가 올라갔습니다. baseline 갱신 규칙과 PR 기록이 없으면 금방 혼란스러워졌습니다.

팀 적용 가능성

거짓 실패가 많을 때는 팀 적용이 어려웠습니다. 실패 알림이 실제 버그일 가능성이 높아져야 팀에서도 받아들이기 쉬웠습니다.

결론: 비주얼 회귀 테스트는 신뢰 가능한 실패를 만드는 작업이었다

2026년 3월부터 6주 동안 React 프로젝트에 Playwright와 GitHub Actions로 비주얼 회귀 테스트를 구축해본 결론은 분명합니다. 비주얼 회귀 테스트는 스크린샷을 많이 찍는 것이 아니라, 실패 원인을 줄여 신뢰 가능한 알림을 만드는 것이 핵심이었습니다.

저는 18개 페이지를 대상으로 375px, 768px, 1440px의 3개 반응형 뷰포트를 테스트했고, 총 312장의 스크린샷을 캡처했습니다. 초기 비주얼 테스트 실패 수는 68건이었지만, 실제 UI 버그는 21건이었고 거짓 실패는 47건이었습니다. 개선 후 거짓 실패는 12건으로 줄었습니다.

처음에는 테스트가 많이 실패하면 좋은 테스트라고 생각했습니다. 하지만 실제로는 거짓 실패가 많을수록 테스트를 믿기 어려웠습니다. 로딩 완료를 기다리지 않아 버튼 위치가 매번 다르게 찍히거나, 날짜 영역을 마스킹하지 않아 매일 테스트가 실패하는 경험을 겪고 나서야 기준을 바꿨습니다.

애니메이션 비활성화, 날짜 영역 마스킹, 이미지 로딩 대기, 기준 스크린샷 관리, 실패 임계값 조정은 단순한 보조 설정이 아니었습니다. 비주얼 회귀 테스트를 실제 개발 흐름에 넣기 위한 필수 조건이었습니다.

비주얼 회귀 테스트를 도입하기 전 확인해야 할 체크리스트

  • 테스트할 핵심 페이지 수를 먼저 정했는가?
  • 모바일, 태블릿, 데스크톱 뷰포트를 구분했는가?
  • 375px, 768px, 1440px처럼 기준 해상도를 명확히 정했는가?
  • 스크린샷 캡처 전에 이미지 로딩을 기다리고 있는가?
  • 날짜, 시간, 랜덤 ID처럼 매번 바뀌는 영역을 마스킹했는가?
  • 애니메이션과 transition을 테스트 환경에서 비활성화했는가?
  • 폰트 렌더링 차이를 줄이기 위한 환경 고정이 되어 있는가?
  • 비교 임계값이 너무 엄격하지 않은가?
  • 기준 스크린샷 갱신 규칙을 정했는가?
  • PR에서 baseline 변경 사유를 기록하고 있는가?
  • CI 실행 시간이 팀이 감당할 수 있는 수준인가?
  • 거짓 실패가 많아 테스트 알림을 무시하게 되는 상태는 아닌가?
  • 실제 UI 버그와 거짓 실패를 구분해 기록하고 있는가?

비주얼 회귀 테스트는 도입 자체보다 안정화 과정이 더 중요했습니다. 스크린샷 312장을 찍는 것보다, 그중 실패한 결과를 믿을 수 있게 만드는 과정이 훨씬 어려웠습니다. 지금은 실패 알림이 뜨면 “또 가짜겠지”가 아니라 “어떤 화면이 실제로 바뀌었는지 확인하자”는 생각이 먼저 듭니다. 그 변화가 이번 6주 테스트에서 얻은 가장 큰 성과였습니다.

댓글 남기기