1. Bối cảnh: vì sao pixel diff thuần khiến visual test chết yểu
Kiểm thử hồi quy hình ảnh (visual regression) ra đời để bắt những lỗi mà test chức năng bỏ sót: nút bị lệch, chữ tràn khung, màu sắc sai thương hiệu, layout vỡ trên màn hình nhỏ. Vấn đề là thế hệ công cụ đầu tiên so ảnh theo kiểu pixel-to-pixel, tức là chỉ cần một điểm ảnh khác nhau là báo đỏ. Trong thực tế, chống răng cưa (anti-alias), cách render font khác nhau giữa hệ điều hành, con trỏ nhấp nháy hay một mốc thời gian động cũng đủ làm hàng loạt ảnh khác nhau ở mức pixel dù mắt người chẳng thấy khác biệt nào đáng kể.
Hệ quả là một vòng xoáy tiêu cực quen thuộc: quá nhiều báo động giả (false positive) khiến người kiểm thử mất niềm tin vào bộ ảnh chuẩn (baseline). Họ bắt đầu bấm 'chấp nhận tất cả' cho nhanh, và đúng lúc đó một lỗi giao diện thật lọt qua vì bị vùi trong đống khác biệt vô nghĩa. Đây chính là phiên bản trực quan của bài toán test giòn (フレーキー / flaky) mà ai làm tự động hóa cũng từng gặp: khi tín hiệu quá nhiễu, con người ngừng đọc tín hiệu.
2. Pixel diff so với AI diff: cơ chế và sự khác biệt
Pixel diff hoạt động đơn giản: đặt ảnh mới chồng lên ảnh chuẩn, đếm số điểm ảnh khác nhau, nếu vượt ngưỡng thì báo lỗi. Cách này nhanh, dễ hiểu, nhưng không biết đâu là khác biệt 'quan trọng với con người'. Ngược lại, AI diff (hay perceptual diff) mô phỏng cách mắt người cảm nhận: nó bỏ qua sai khác sub-pixel do răng cưa, chịu được khác biệt nhỏ về màu do nén ảnh, và tập trung vào những thay đổi có ý nghĩa như phần tử bị dịch chuyển, đổi kích thước, đổi màu rõ rệt hay chữ bị đổi.
Điều then chốt để hiểu là AI diff không phải phép màu 'luôn đúng'. Nó là một mô hình có ngưỡng nhạy cảm và các quy tắc bỏ qua vùng động mà bạn phải cấu hình đúng. Cấu hình quá lỏng thì bỏ sót lỗi thật; quá chặt thì quay lại cảnh nhiễu như pixel diff. Vì thế người kiểm thử vẫn là người điều chỉnh độ nhạy, khai báo vùng cần bỏ qua, và quyết định thay đổi nào là hồi quy (回帰) ngoài ý muốn, thay đổi nào là cập nhật thiết kế chủ ý.
// Playwright snapshot: toHaveScreenshot có ngưỡng + mask vùng động
import { test, expect } from '@playwright/test';
test('trang giỏ hàng khớp baseline (bỏ nhiễu động)', async ({ page }) => {
await page.goto('/cart');
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('cart.png', {
maxDiffPixelRatio: 0.01, // cho phép ~1% nhiễu anti-alias
threshold: 0.2, // ngưỡng khác biệt màu mỗi pixel (0..1)
mask: [page.getByTestId('promo-countdown'), // vùng thời gian động
page.getByTestId('user-avatar')], // avatar random
animations: 'disabled', // tắt animation trước khi chụp
});
});3. Baseline và phê duyệt: hợp đồng hình ảnh của sản phẩm
Baseline là tập ảnh chuẩn mà mọi lần chạy sau đều đem so. Về bản chất, baseline chính là oracle của visual test: nó nói 'giao diện đúng phải trông như thế này'. Do đó, việc quản lý baseline không phải chuyện kỹ thuật vặt mà là quản lý một hợp đồng. Mỗi lần bạn 'chấp nhận' một ảnh mới, bạn đang tuyên bố rằng trạng thái mới đó là đúng và trở thành chuẩn cho tương lai. Chấp nhận cẩu thả sẽ đóng băng một lỗi thành 'đúng', và từ đó về sau không test nào bắt được nó nữa.
Trong công cụ đám mây kiểu Applitools hay Percy, luồng phê duyệt được thiết kế như một bước review có người: khi có khác biệt, hệ thống hiển thị ảnh chuẩn, ảnh mới và vùng thay đổi được tô sáng, rồi người review bấm chấp nhận hoặc từ chối. Với Playwright snapshot, baseline là các file .png nằm trong repo, cập nhật bằng cờ --update-snapshots và được review qua diff của pull request. Dù công cụ nào, nguyên tắc bất di bất dịch là baseline chỉ đổi khi có người chủ ý duyệt, không bao giờ tự động cập nhật trong CI.
# Playwright: baseline là file .png trong repo, review qua PR diff
# Lần đầu tạo baseline (chủ ý):
npx playwright test --update-snapshots
# CI: KHÔNG update, chỉ so — khác baseline ⇒ fail để người xem
npx playwright test # không có --update-snapshots
# Khi thiết kế đổi CHỦ Ý: update rồi commit ảnh mới, review trong PR
npx playwright test cart.spec.ts --update-snapshots
git add tests/__screenshots__/cart.png
git commit -m "chore(visual): cập nhật baseline giỏ hàng sau redesign header"
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!