Wiki

← Tất cả bài wiki

Visual Comparisons (toHaveScreenshot — so khớp ảnh chụp màn hình)

automationplaywright

Visual comparison (snapshot testing) là cơ chế chụp màn hình page/element rồi so pixel với ảnh baseline đã lưu: toHaveScreenshot tạo baseline lần đầu, các lần sau diff từng pixel — vượt ngưỡng cho phép (threshold/maxDiffPixels) thì fail kèm ảnh diff. Giá trị lớn nhất: bắt regression về giao diện (vỡ layout, đổi màu, font rớt) mà assertion text/DOM không thấy — đổi lại là test rất nhạy với môi trường render.

Khái niệm & Bản chất

  • Snapshot/baseline testing: lần chạy đầu toHaveScreenshot chụp và LƯU ảnh vào thư mục snapshot (không fail); các lần sau chụp mới rồi so pixel-by-pixel với baseline — sai khác vượt ngưỡng → fail kèm diff image (ảnh gốc, ảnh mới, mask vùng khác).
  • Pixel-diff, không phải AI: Playwright so màu từng pixel qua thuật toán (pixelmatch). threshold là độ lệch màu cho phép trên MỘT pixel (0–1), maxDiffPixels/maxDiffPixelRatio là tổng số/tỉ lệ pixel khác cho phép.
  • Baseline theo project + OS: tên file tự động kèm hậu tố <project>-<platform> (vd chromium-darwin, firefox-linux) — vì font render, sub-pixel anti-aliasing khác nhau giữa macOS/Linux/Windows và giữa browser.
  • Vai trò: phủ lỗ hổng của functional test — DOM vẫn 'đúng' (text đúng, element visible) nhưng CSS vỡ, nút bị che, font biến mất. Visual test là lớp bắt cuối cho regression giao diện.

Cách hoạt động & API chính

  • API: await expect(page).toHaveScreenshot('homepage.png') (theo viewport, thêm fullPage: true để chụp cả trang cuộn) hoặc await expect(locator).toHaveScreenshot() (chỉ element). Không truyền tên → tự gen theo tên test.
  • Option chống rung: animations: 'disabled' (tắt CSS animation trước khi chụp), caret: 'hide' (ẩn con trỏ input), scale: 'css' (chụp theo CSS pixel thay vì device pixel — giảm khác biệt giữa máy retina/thường).
  • Ngưỡng chịu lỗi: threshold: 0.2 (độ nhạy màu từng pixel), maxDiffPixels: 100 hoặc maxDiffPixelRatio: 0.01 (cho phép 1% pixel lệch — dung sai anti-aliasing).
  • Che vùng động: mask: [page.getByTestId('timestamp'), page.locator('.ad-banner')] — phủ màu đặc lên vùng thay đổi mỗi lần chạy (đồng hồ, quảng cáo, ảnh random).
  • Config global: playwright.config.ts → expect: { toHaveScreenshot: { maxDiffPixelRatio: 0.01, animations: 'disabled' } } — và snapshotPathTemplate tuỳ biến nơi lưu baseline.
  • Update baseline: npx playwright test --update-snapshots (hoặc --update-snapshots changed chỉ cập nhật cái sai khác) — chạy sau khi CỐ Ý đổi giao diện.

Ví dụ & Cú pháp thực tế

  • ```ts import { test, expect } from '@playwright/test'; test('trang chủ khớp baseline', async ({ page }) => { await page.goto('/'); await page.waitForLoadState('networkidle'); await expect(page).toHaveScreenshot('homepage.png', { fullPage: true, animations: 'disabled', // Cho phép lệch nhẹ do anti-aliasing font giữa các lần render maxDiffPixelRatio: 0.01, }); });
  • ```ts test('component snapshot + mask vùng động', async ({ page }) => { await page.goto('/dashboard'); await expect(page.getByTestId('stat-card')).toHaveScreenshot({ // Mask đồng hồ realtime & avatar random — vùng này luôn đổi mask: [ page.getByTestId('current-time'), page.locator('.user-avatar'), ], threshold: 0.2, // độ nhạy màu mỗi pixel }); });
  • ```ts // playwright.config.ts — chuẩn hoá cho cả suite import { defineConfig } from '@playwright/test'; export default defineConfig({ expect: { toHaveScreenshot: { animations: 'disabled', caret: 'hide', scale: 'css', maxDiffPixelRatio: 0.02, }, timeout: 10_000, // toHaveScreenshot cũng retry chờ ảnh ổn định }, snapshotPathTemplate: '{testFilePath}-snapshots/{testName}-{projectName}-{platform}{ext}', });
  • ```bash # Cập nhật baseline sau khi cố ý đổi UI — review diff trước khi commit npx playwright test --update-snapshots # Chỉ update những snapshot bị sai khác (giữ nguyên cái đang pass) npx playwright test --update-snapshots changed # Gen baseline ĐÚNG môi trường CI bằng Docker npx playwright test --update-snapshots --project=chromium

Best practices & Khi nào dùng

  • Gen & chạy baseline trong CÙNG một môi trường: tốt nhất là Docker image giống CI (mcr.microsoft.com/playwright:v1.x). Baseline gen trên macOS rồi CI Linux = fail hàng loạt vì khác font engine.
  • Mask mọi thứ động: timestamp, tên user đăng nhập, ảnh quảng cáo, captcha, số liệu realtime — hoặc mock dữ liệu cố định (route interception) để trang render deterministic.
  • Snapshot ở mức component thay vì full page khi có thể — vùng so sánh nhỏ hơn → ít flaky, diff report dễ đọc, lỗi khoanh đúng component hỏng.
  • Dung sai vừa đủ: bắt đầu với maxDiffPixelRatio: 0.01–0.02 + animations: 'disabled' + scale: 'css'; siết threshold: 0 chỉ cho component tĩnh hoàn toàn.
  • Review diff như review code: snapshot file là artifact phải commit vào git; khi --update-snapshots, mở ảnh diff (đính kèm trong HTML report) xác nhận đúng thay đổi mong muốn trước khi commit baseline mới.
  • Đặt vào suite riêng: visual test chậm và nhạy môi trường — tách project/tag riêng, không nhét lẫn functional suite để tránh noise.

Gotchas, So sánh & Lưu ý

  • Flaky kinh điển: font rendering, GPU rasterization, lazy-load ảnh, animation giữa chừng — giảm bằng networkidle/waitFor trước khi chụp, animations: 'disabled', ảnh mock cố định.
  • toHaveScreenshot cũng auto-retry: nó chụp lại nhiều lần chờ ảnh 'ổn định' khớp baseline trong timeout — nhưng nếu trang mãi không ổn định (banner xoay vòng) thì phải mask/freeze trước.
  • Baseline nằm cạnh file test, tách theo platform: snapshot tự sinh vào <file>-snapshots/ với hậu tố <project>-<os> (chromium-linux.png vs chromium-darwin.png là 2 baseline riêng) — phải commit vào git, CI fix một OS, đừng commit baseline gen trên máy dev lẫn lộn.
  • threshold ≠ maxDiffPixels: threshold là 'một pixel lệch bao nhiêu thì TÍNH là khác' (độ nhạy màu); maxDiffPixels là 'tổng cộng bao nhiêu pixel khác thì cho qua'. Nới threshold để chống răng cưa, nới maxDiff cho vùng động nhỏ.
  • So với Percy/Chromatic/Applitools: tool SaaS chụp trên infra chuẩn hoá + UI review đẹp, nhưng tốn tiền; toHaveScreenshot miễn phí, local-first, đủ dùng nếu kỷ luật Docker + mask tốt.
  • Không thay thế functional test: visual pass chưa chắc logic đúng (ảnh đẹp nhưng nút click không ăn) — nó là lớp bổ sung, không phải thay thế.