본문으로 건너뛰기

9.2 Visual Comparison

Visual comparison은 현재 screenshot과 승인된 baseline의 pixel 차이를 검증합니다. DOM assertion이 놓치는 layout, font, color, overlap 회귀를 잡지만 실행 환경 차이에도 민감합니다.

Baseline, actual, diff를 나란히 본 학습용 캡처

최소 예제

test('Todo Lab 기본 화면', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('todo-home.png');
});

첫 실행은 baseline을 생성하고 실패로 알릴 수 있습니다. 생성된 이미지를 사람이 리뷰해 의도한 UI인지 확인한 뒤 version control에 commit합니다.

안정적인 baseline 조건

  • 동일한 OS와 container image
  • 동일한 Playwright/browser version
  • 동일한 font file과 font rendering 환경
  • 고정 viewport, color scheme, locale, timezone
  • animation과 caret 통제
  • API data, 날짜, random ID 고정
export default defineConfig({
use: {
viewport: { width: 1280, height: 720 },
colorScheme: 'light',
locale: 'ko-KR',
timezoneId: 'Asia/Seoul',
},
expect: {
toHaveScreenshot: {
animations: 'disabled',
caret: 'hide',
maxDiffPixelRatio: 0.001,
},
},
});

Page 전체보다 component 범위

const card = page.getByRole('region', { name: '요약 카드' });
await expect(card).toHaveScreenshot('summary-card.png');

관심 영역을 좁히면 unrelated footer, 광고, timestamp 때문에 실패하지 않습니다. 반대로 전체 layout 회귀를 잡는 smoke snapshot도 소수 유지할 수 있습니다.

동적 영역 마스킹과 style

await expect(page).toHaveScreenshot('dashboard.png', {
mask: [page.getByTestId('live-clock')],
stylePath: './screenshot.css',
});

Mask를 늘리기 전에 그 영역을 API mock이나 Clock으로 결정적으로 만들 수 있는지 검토합니다. 중요한 제품 영역을 mask하면 회귀 검출 가치가 사라집니다.

허용치 설계

threshold, maxDiffPixels, maxDiffPixelRatio를 크게 올리면 noise도 줄지만 진짜 회귀도 놓칩니다. 먼저 환경 차이를 제거하고, 남은 anti-aliasing noise에만 최소 허용치를 적용합니다.

Baseline 변경 workflow

  1. CI가 actual, expected, diff를 artifact로 보존합니다.
  2. 작성자는 제품 변경 근거와 함께 diff를 리뷰합니다.
  3. 승인된 환경에서만 --update-snapshots를 실행합니다.
  4. Baseline change를 일반 code review와 같이 검토합니다.
  5. 의도하지 않은 대량 update는 되돌리고 root cause를 찾습니다.

실습

  1. Todo card 하나만 element screenshot으로 저장합니다.
  2. CSS padding을 바꿔 diff가 어느 영역에 나타나는지 확인합니다.
  3. Current time을 mask한 경우와 Clock으로 고정한 경우를 비교합니다.
  4. Local macOS baseline을 Linux CI에서 사용했을 때 차이를 기록합니다.

참고: Visual comparisons, Screenshots