Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 38 — Visual Regression Testing

Đã đọc 0/14 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là QA cho một app thương mại điện tử. Đội dev vừa nâng cấp thư viện thiết kế (design system) từ phiên bản 2.3 lên 3.0 — một thay đổi "chỉ nội bộ", không đụng gì tới logic. Tất cả 480 test case chức năng của bạn đều xanh (pass). Bạn ký duyệt release. Ba giờ sau, tổng đài chăm sóc khách hàng gọi lên: nút "Thanh toán" trên màn hình giỏ hàng bị đẩy lệch xuống dưới, nằm chồng một phần lên footer, và trên iPhone SE thì bị che khuất hoàn toàn. Doanh số giờ vàng tụt 18%.

Đây chính là loại lỗi mà functional testing không bao giờ bắt được. Test chức năng hỏi: "Nhấn nút này có gọi đúng API không?" — và câu trả lời là có. Nhưng nó không hỏi: "Nút này trông có đúng không? Nó có nằm đúng chỗ không? Màu sắc, khoảng cách, font chữ có bị vỡ không?". Con người nhìn một cái là thấy sai; máy chạy Appium thì thấy nút vẫn tồn tại trong cây view, vẫn click được, nên báo pass.

Visual Regression Testing (VRT) — kiểm thử hồi quy trực quan — ra đời để lấp đúng khoảng trống này. Nó biến "cảm nhận bằng mắt của con người" thành một bài kiểm tra tự động, lặp lại được, chạy trên mọi build. Với mobile, nơi mà một app phải trông đẹp trên hàng chục kích thước màn hình, mật độ điểm ảnh, hệ điều hành và ngôn ngữ khác nhau, VRT không còn là thứ "có thì tốt" mà là tấm lưới an toàn thực sự. Bài này sẽ dạy bạn hiểu bản chất, chọn công cụ, và vận hành VRT một cách thực dụng cho QA di động.

Khái niệm cốt lõi

Định nghĩa: baseline, so sánh, và diff

Visual Regression Testing hoạt động theo một vòng lặp ba bước đơn giản đến bất ngờ:

  • Chụp baseline (ảnh chuẩn): Ở một build được xác nhận là "đúng", bạn chụp ảnh (screenshot) của từng màn hình quan trọng. Đây là ảnh mốc — sự thật được chấp nhận.
  • Chụp ảnh của build mới: Mỗi lần có build mới, framework tự động chụp lại đúng những màn hình đó, trong đúng điều kiện đó.
  • So sánh pixel-by-pixel: Công cụ đặt hai ảnh chồng lên nhau, tính toán sự khác biệt. Nếu khác biệt vượt ngưỡng cho phép, test fail và tạo ra một ảnh diff — thường tô đỏ hoặc hồng những vùng thay đổi để bạn nhìn thấy ngay chỗ nào lệch.
Điểm mấu chốt để hiểu: VRT không biết thay đổi là tốt hay xấu. Nó chỉ biết "khác". Nếu team cố tình đổi màu nút từ xanh sang cam, VRT vẫn fail — và đúng ra là phải fail. Lúc đó nhiệm vụ của con người là duyệt (approve) ảnh mới thành baseline mới. Cơ chế này gọi là review & approve, và nó là trái tim của toàn bộ quy trình.

Ba cấp độ so sánh: pixel, layout, và perceptual

Không phải mọi so sánh đều giống nhau, và hiểu điều này giúp bạn tránh cơn ác mộng "flaky" (test chập chờn):

  • Pixel-exact: So sánh từng điểm ảnh một. Cực kỳ nhạy — nhạy đến mức một chút khác biệt trong cách GPU render chữ, hay một anti-aliasing khác nhau giữa hai lần chạy, cũng làm fail. Ít dùng cho mobile vì quá dễ vỡ.
  • Threshold-based (theo ngưỡng): Cho phép một tỉ lệ pixel khác biệt nhất định (ví dụ 0.1%) trước khi báo fail. Đây là cấu hình phổ biến nhất, cân bằng giữa độ nhạy và độ ổn định.
  • Perceptual (theo cảm nhận thị giác): Dùng thuật toán mô phỏng mắt người (như SSIM) để bỏ qua những khác biệt mà mắt người không nhận ra, và chỉ báo cái mà con người thực sự thấy. Đây là hướng hiện đại, giảm cực mạnh số báo giả (false positive).

Vì sao mobile khó hơn web

VRT trên web đã khó, mobile còn khó hơn nhiều tầng:

  • Đa dạng thiết bị: Một app Android phải chạy trên màn 5 inch của Samsung Galaxy A03 lẫn màn gập của Galaxy Z Fold. Mỗi màn hình một độ phân giải, một mật độ (dpi) — nghĩa là baseline không thể "một cỡ cho tất cả".
  • Nội dung động: Đồng hồ trạng thái, phần trăm pin, quảng cáo, avatar người dùng, thời gian "3 phút trước" — tất cả thay đổi mỗi lần chụp và sẽ làm test fail oan nếu không xử lý.
  • Animation và transition: Nếu chụp đúng lúc màn hình đang trượt vào, ảnh sẽ nhòe hoặc lệch.
  • Font hệ thống: Người dùng có thể chỉnh cỡ chữ lớn trong Cài đặt, làm layout co giãn khác baseline.

Bộ công cụ VRT cho mobile

Đây là những công cụ bạn cần biết tên và biết chọn:

  • Percy (thuộc BrowserStack): Dịch vụ đám mây. Bạn tích hợp SDK vào bộ test Appium/XCUITest/Espresso, mỗi lần chạy Percy tải ảnh lên cloud, render và so sánh trên hạ tầng của họ, rồi cho bạn một dashboard đẹp để review & approve. Ưu điểm lớn: xử lý được chuyện đa trình duyệt/đa thiết bị và có review workflow trực quan. Nhược điểm: trả tiền theo số ảnh (screenshot) mỗi tháng.
  • Applitools Eyes: Đối thủ cao cấp của Percy, mạnh về AI. Công nghệ "Visual AI" của họ so sánh theo cảm nhận, tự bỏ qua nhiễu, và có tính năng Root Cause Analysis chỉ thẳng ra phần tử DOM/view nào gây lệch. Đắt hơn nhưng false-positive thấp nhất thị trường.
  • Giải pháp mã nguồn mở tự dựng: Bạn tự chụp screenshot bằng Appium (driver.getScreenshotAs()) rồi so sánh bằng thư viện như resemblance.js, pixelmatch, hoặc OpenCV. Miễn phí, toàn quyền kiểm soát, nhưng bạn phải tự xây dựng phần lưu baseline, quản lý diff, và dựng dashboard review. Với XCUITest có sẵn iOSSnapshotTestCase (trước đây là FBSnapshotTestCase của Facebook); với Espresso/Android có Shot hoặc Paparazzi (chụp UI mà không cần cả emulator — cực nhanh).

Tình huống thực tế

Tình huống 1 — Sàn TMĐT và cú "vỡ layout thầm lặng"

Một sàn thương mại điện tử tầm trung ở TP.HCM (giả định gọi là ShopViet) có app với hơn 60 màn hình. Đội frontend nâng cấp thư viện React Native từ 0.71 lên 0.73. Toàn bộ 350 test chức năng pass sạch. Nhưng khi họ bật Percy đã tích hợp sẵn, dashboard hiện 23 màn hình có visual diff. Phần lớn là thay đổi vô hại (khoảng cách padding lệch 1px do thư viện làm tròn khác). Nhưng có 3 màn hình thật sự vỡ: màn hình chi tiết sản phẩm bị mất đường viền ảnh, màn hình thanh toán có nút bị co lại 40%, và màn hình hồ sơ bị tràn chữ trên thiết bị nhỏ.

QA lead ngồi review 15 phút, bấm approve cho 20 diff vô hại (biến chúng thành baseline mới) và reject + tạo bug cho 3 cái thật. Bài học: VRT không thay con người quyết định — nó thu hẹp 60 màn hình cần soi bằng mắt xuống còn 23, và trong 23 đó chỉ 3 là vấn đề. Không có VRT, đội QA đã phải bấm tay qua từng màn hình, và gần như chắc chắn bỏ sót ít nhất một cái vì mỏi mắt. Con số 15 phút review so với vài giờ kiểm tra thủ công là lý do khiến ban lãnh đạo duyệt ngân sách Percy.

Tình huống 2 — Ngân hàng số và bài học "nội dung động"

Một ví điện tử/ngân hàng số tại Việt Nam (giả định: PayNhanh) muốn áp dụng VRT cho màn hình "Trang chủ" hiển thị số dư. Lần đầu triển khai, mọi test đều fail. Lý do: số dư thay đổi mỗi lần, đồng hồ trên thanh trạng thái nhảy phút, và banner khuyến mãi xoay vòng. Đội QA suýt bỏ cuộc, cho rằng "VRT không hợp với app tài chính".

Giải pháp không phải bỏ VRT mà là kiểm soát môi trường. Họ làm ba việc: (1) dùng dữ liệu giả cố định (mock) để số dư luôn là "12.345.678 đ" trong môi trường test; (2) dùng tính năng ignore region của Applitools để "che" vùng thanh trạng thái và banner động, bảo công cụ đừng so sánh vùng đó; (3) tắt animation của app trong build test (Settings > Developer options > Animation scale = 0 trên Android, hoặc cờ launch argument trên iOS). Sau đó, tỉ lệ false-positive rơi từ gần 100% xuống dưới 2%. Bài học: 80% công sức của VRT là chuẩn hóa môi trường chụp, không phải bản thân việc so sánh. Một app mà không kiểm soát được dữ liệu động thì VRT sẽ trở thành nguồn tiếng ồn khiến cả team mất niềm tin.

Tình huống 3 — Startup và cái bẫy "baseline sai từ đầu"

Một startup giao đồ ăn ở Đông Nam Á triển khai VRT tự dựng bằng Appium + pixelmatch để tiết kiệm chi phí. Họ chụp baseline vào một ngày, rồi hai tuần sau mới bật so sánh. Vấn đề: baseline được chụp khi app đang có một bug hiển thị — một icon giỏ hàng bị mờ. Vì baseline đã "đóng đinh" cái bug đó là chuẩn, nên mọi build sau — kể cả build đã sửa bug — đều bị VRT báo fail. Đội dev bối rối, tưởng mình làm hỏng, và mất nửa ngày mới nhận ra baseline mới là thủ phạm.

Bài học: Baseline chỉ đáng tin khi nó được chụp từ một build đã được kiểm định kỹ càng bằng mắt người. Đừng bao giờ "chụp đại rồi tính sau". Baseline là hợp đồng — nếu hợp đồng ký sai, mọi thứ phía sau đều sai theo. Quy trình đúng là: một QA có kinh nghiệm duyệt bằng mắt từng ảnh baseline đầu tiên trước khi commit nó vào kho.

Hướng dẫn từng bước

Dưới đây là quy trình triển khai VRT cho một dự án mobile, dùng Appium làm ví dụ (nguyên tắc tương tự cho XCUITest/Espresso):

Bước 1 — Chọn phạm vi. Đừng chụp mọi màn hình. Bắt đầu với 8–12 màn hình quan trọng nhất về mặt hình ảnh và doanh thu: trang chủ, danh sách sản phẩm, chi tiết, giỏ hàng, thanh toán, đăng nhập, hồ sơ. Đây là các "crown jewel" — vỡ ở đây là mất tiền thật.

Bước 2 — Chọn công cụ và tích hợp. Nếu có ngân sách và cần tốc độ: Percy hoặc Applitools. Nếu cần miễn phí và có kỹ sư giỏi: Appium + pixelmatch. Ví dụ với Percy, bạn thêm SDK và gọi percyScreenshot(driver, "cart-screen") ngay tại điểm màn hình đã ổn định trong test hiện có.

Bước 3 — Chuẩn hóa môi trường (bước quan trọng nhất). Cố định mọi thứ động: khóa dữ liệu bằng mock, tắt animation, cố định locale và múi giờ, ẩn thanh trạng thái nếu có thể (dùng "demo mode" của iOS/Android để status bar luôn 9:41, pin đầy). Cấu hình ignore region cho những vùng không thể cố định.

Bước 4 — Chụp và duyệt baseline. Chạy lần đầu, rồi ngồi soi từng ảnh bằng mắt. Chỉ approve khi bạn chắc chắn màn hình đó đúng. Commit baseline vào kho (hoặc vào cloud của Percy/Applitools).

Bước 5 — Đặt ngưỡng hợp lý. Bắt đầu với ngưỡng khác biệt khoảng 0.1–0.2%. Quá chặt sẽ flaky, quá lỏng sẽ bỏ sót. Điều chỉnh dần theo thực tế dự án.

Bước 6 — Tích hợp CI. Cho VRT chạy tự động mỗi pull request. Khi có diff, CI báo fail và gắn link tới dashboard review. Không ai được merge cho tới khi diff được duyệt.

Bước 7 — Vận hành vòng review. Mỗi diff, người review quyết định: đây là bug (reject, tạo ticket) hay đây là thay đổi cố ý (approve, cập nhật baseline). Vòng này phải nhanh và rõ ràng — nếu không, VRT sẽ bị bỏ xó.

Lỗi thường gặp & mẹo

  • Bỏ qua nội dung động → false positive tràn lan. Đây là lỗi số một giết chết mọi dự án VRT. Luôn mock dữ liệu và ignore vùng động trước khi trách công cụ.
  • Không tắt animation. Chụp giữa lúc chuyển cảnh cho ảnh nhòe. Luôn chờ màn hình "yên" (idle) rồi mới chụp, và tắt animation scale trong build test.
  • Baseline chụp từ build lỗi. Như tình huống 3 — luôn có người duyệt baseline bằng mắt.
  • Ngưỡng quá chặt. Pixel-exact trên mobile gần như luôn flaky vì cách render font/anti-aliasing khác nhau giữa các lần chạy và giữa các máy CI. Ưu tiên perceptual hoặc threshold.
  • Approve mù (rubber-stamp). Khi có quá nhiều diff, người review dễ bấm approve tất cả cho xong. Đây là lúc bug lọt lưới. Giữ số màn hình đủ ít để mỗi diff đều được nhìn thật sự.
  • Không cố định thiết bị/độ phân giải trong CI. Nếu CI chạy trên emulator cấu hình khác baseline, sẽ fail hàng loạt. Ghim rõ cùng một thiết bị, OS version, độ phân giải.
  • Mẹo — chụp theo component, không chỉ full-screen. Với React Native/Flutter, chụp từng thành phần riêng (như Paparazzi làm) nhanh hơn nhiều và diff dễ định vị hơn màn hình toàn phần.
  • Mẹo — kết hợp với Dark Mode và đa ngôn ngữ. VRT là nơi lý tưởng để bắt lỗi tràn chữ khi chuyển sang tiếng Đức (chữ dài) hoặc lỗi màu khi bật Dark Mode — chụp cùng màn hình ở nhiều cấu hình.

Bài tập thực hành

  • Chọn crown jewels: Lấy một app bất kỳ trên máy bạn (hoặc app công ty). Liệt kê ra 10 màn hình mà nếu vỡ layout sẽ gây thiệt hại lớn nhất. Giải thích tại sao chọn từng cái.
  • Săn nội dung động: Mở 3 màn hình đó và liệt kê mọi phần tử sẽ thay đổi giữa hai lần chụp (đồng hồ, số dư, avatar, quảng cáo, thời gian tương đối...). Với mỗi phần tử, ghi cách xử lý: mock, ignore region, hay ẩn đi.
  • Dựng VRT tối thiểu: Dùng Appium chụp screenshot một màn hình đăng nhập ở hai lần chạy khác nhau, rồi so sánh bằng thư viện pixelmatch hoặc resemblance.js. Cố ý đổi một thuộc tính CSS/style nhỏ giữa hai lần và xem ảnh diff có bắt được không.
  • Thiết kế quy trình review: Viết một đoạn quy trình (5–7 câu) mô tả: ai duyệt diff, duyệt trong bao lâu, khi nào approve baseline mới, khi nào tạo bug. Đây là tài liệu bạn sẽ đưa cho team thật.

Tóm tắt

Visual Regression Testing lấp đúng khoảng trống mà functional testing bỏ lại: nó kiểm tra app trông có đúng không, không chỉ chạy có đúng không. Cơ chế cốt lõi là baseline → chụp build mới → so sánh diff → con người review & approve. Trên mobile, thách thức lớn nhất không phải là so sánh mà là chuẩn hóa môi trường chụp: mock dữ liệu động, tắt animation, ignore vùng biến đổi, ghim thiết bị và độ phân giải. Công cụ chia làm hai nhóm — cloud có review workflow đẹp (Percy, Applitools với Visual AI giảm false-positive) và mã nguồn mở tự dựng (Appium + pixelmatch, Paparazzi, iOSSnapshotTestCase) miễn phí nhưng tốn công. Ba bài học xương máu từ thực tế: baseline phải được người duyệt bằng mắt trước khi tin, nội dung động phải bị kiểm soát nếu không VRT thành máy phát tiếng ồn, và vòng review phải đủ nhanh gọn để team giữ được niềm tin. Làm đúng, VRT biến việc soi hàng chục màn hình bằng mắt mỏi mệt thành vài phút xem đúng những chỗ thực sự thay đổi — đó là giá trị thật của nó.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi