pile·
프론트엔드·카카오 엔터테인먼트 FE카카오 엔터테인먼트 FE·

시각적 회귀 테스트 BackstopJS 적용하기 (Visual Regression Test)

공통 컴포넌트를 고칠 때마다 생기는 예상치 못한 UI 변화를 잡으려고 BackstopJS 로 시각적 회귀 테스트를 구축한 기록이다. Percy·Chromatic·Applitools 같은 유료 서비스 대신 무료 오픈소스이면서 변경 전/후 이미지를 겹쳐 비교할 수 있다는 점을 보고 골랐고, 스토리북 빌드 결과를 대상으로 시나리오를 구성해 PR 리뷰에 붙였다.

핵심 포인트
  • BackstopJS 는 Chrome headless 로 화면을 캡처해 저장된 reference 이미지와 비교하고, 달라진 영역을 색으로 칠해 좌우 슬라이더로 대조하게 해 준다.
  • 도입 동기는 Next/Image 도입처럼 공통 컴포넌트를 일괄 교체할 때 padding·이미지 크기가 의도와 다르게 변하는 사이드이펙트를 잡기 위해서였다.
  • 설정은 `backstop.json` 대신 `backstop.config.js` 로 빼고 시나리오 배열을 별도 모듈로 분리하면 중복 설정을 피할 수 있다.
  • 테스트 대상은 실서비스 페이지가 아니라 스토리북 빌드의 `iframe.html`, 아토믹 디자인의 organism 단위 스토리로 좁혔다.
  • puppeteer 타임아웃은 도커 리소스와 `engineOptions` 조정으로 해결했고, `asyncCaptureLimit`/`asyncCompareLimit` 를 올려 속도를 확보했다.
상세 정리
  • 도구 선택: 유료 VRT 서비스들을 검토했지만 무료이면서 변경 전후를 한 화면에서 겹쳐 비교하는 기능이 결정적이었다.
  • 기본 명령 흐름: `npm install backstopjs` → `backstop init` 으로 `backstop_data/engine_scripts` 와 `backstop.json` 생성 → `backstop reference` 로 기준 이미지 확보 → `backstop test` 로 비교.
  • reference 는 최초 세팅에만 쓰고, 이후 변경을 받아들일 때는 `backstop approve` 로 `bitmaps_test` 의 최신 결과를 `bitmaps_reference` 에 덮어쓴다.
  • `backstop openReport` 는 테스트를 다시 돌리지 않고 마지막 결과만 브라우저로 연다. `"report": ["browser"]` 설정 시 `html_report` 가 생성된다.
  • 결과물이 계속 쌓이므로 `backstop_data/html_report/`, `bitmaps_test/` 는 .gitignore 에 넣는다.
  • viewports: `{label:'phone', width:390, height:844}` 와 `{label:'pc', width:2400, height:1300}` 처럼 지정하면 시나리오 하나당 결과 이미지가 뷰포트 수만큼 생긴다.
  • scenarios: 스토리북 URL 은 `.storybookOutput/iframe.html?id=${key}&viewMode=story` 형태로 넣어 컨트롤러·메뉴가 캡처에 잡히지 않게 한다.
  • 시나리오 중복 제거: `createBackstopScenarios.js` 에 `[라벨, 스토리id]` 배열을 두고 map 으로 펼쳐 `backstop.config.js` 에서 불러온다. 커스텀 설정 파일을 쓰면 모든 명령에 `--config=backstop.config.js` 를 붙여야 한다.
  • 시나리오 옵션: `delay`(타이밍 지연), `hideSelectors`/`removeSelectors`(무시할 영역), `hoverSelector`/`clickSelector`(상호작용), `misMatchThreshold`(허용 오차) 를 상황에 맞게 조합한다.
  • CI 연동: Dockerfile 에서 `npm run backstop:test; exit 0` 로 실패해도 빌드를 이어가고, `backstop_data` 를 스토리북 출력 폴더로 복사해 프리뷰 URL `/backstop/html_report/` 로 리포트를 연다.
  • 타임아웃 대응: `TimeoutError: waiting for selector ... timeout 30000ms exceeded` 는 `slowMo: 500` 과 `--no-sandbox`, `--disable-gpu`, `--disable-dev-shm-usage`, `--shm-size 512mb` 등 puppeteer args, `asyncCaptureLimit: 30` / `asyncCompareLimit: 50` 로 잡았다.
  • 운영 방식: 컴포넌트 수정 PR 에 backstop 프리뷰 링크를 첨부해 깨진 부분을 리뷰하고, 머지 시점에 `backstop approve` 로 기준 이미지를 갱신한다.
왜 읽나디자인 시스템·공통 컴포넌트를 건드릴 때마다 회귀가 두려운 프론트엔드 팀에게 무료 도구로 VRT 를 세팅하는 최단 경로를 준다.
카카오 엔터테인먼트 FE
카카오 엔터테인먼트 FE 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2