Product Management
Đăng nhập
ESC

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

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

Bài 26 — Dark Mode & Theme Testing

Đã đọc 0/14 mục · còn khoảng 11 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

Từ khi Apple giới thiệu Dark Mode ở iOS 13 (2019) và Google đưa dark theme system-wide vào Android 10 cùng năm, chế độ tối không còn là "tính năng cho vui" mà đã trở thành một kỳ vọng mặc định của người dùng. Theo khảo sát nội bộ của nhiều app thương mại điện tử Việt Nam, có tới 55–70% người dùng bật Dark Mode ở cấp hệ điều hành, đặc biệt nhóm dùng điện thoại buổi tối trước khi ngủ — vốn là khung giờ vàng cho app đọc tin, xem video, mua sắm.

Điều nguy hiểm với QA là: Dark Mode không phải chỉ "đổi màu nền thành đen". Nó là một trạng thái song song của toàn bộ UI. Mỗi màn hình, mỗi thành phần, mỗi trạng thái (loading, error, empty, disabled) đều tồn tại ở hai phiên bản. Khi một team chỉ test Light Mode, họ vô tình bỏ qua một nửa số tổ hợp giao diện. Và lỗi Dark Mode thì cực kỳ dễ lọt: chữ trắng trên nền trắng, icon đen tàng hình trên nền đen, logo có viền trắng bao quanh xấu xí, ảnh chụp màn hình chia sẻ lên Zalo bị chói mắt.

Bài này dạy bạn cách tư duy Dark Mode như một chiều kiểm thử (test dimension) riêng, cách thiết kế test scenario xoay quanh việc app phải "tôn trọng" (respect) cài đặt hệ thống, và cách bắt những lỗi tương phản, hardcode màu mà mắt thường dễ bỏ qua. Đây là kỹ năng mà bất kỳ Mobile QA nào cũng cần trong 2026.

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

System dark mode vs in-app theme

Có hai nguồn quyết định app hiển thị tối hay sáng, và bạn phải phân biệt rõ:

  • System-wide dark mode: người dùng bật ở Cài đặt hệ điều hành (iOS: Settings → Display & Brightness → Dark; Android: Settings → Display → Dark theme). Đây là tín hiệu OS phát ra cho mọi app.
  • In-app theme setting: một số app cho phép người dùng chọn Light / Dark / "Theo hệ thống" (System) ngay trong app, ghi đè lên cài đặt OS.
Từ hai nguồn này sinh ra ba chế độ mà app cần hỗ trợ:

  • Follow system: app tự động đổi theo OS. Đây là hành vi mặc định được khuyến nghị.
  • Force Light: app luôn sáng bất kể OS.
  • Force Dark: app luôn tối bất kể OS.
Nguyên tắc vàng: nếu app khai báo hỗ trợ Dark Mode thì phải respect cài đặt hệ thống. Người dùng bật Dark ở OS mà mở app vẫn thấy sáng chói là một lỗi UX nghiêm trọng, không phải "tính năng chưa làm".

Semantic color vs hardcoded color

Đây là kiến thức nền để bạn hiểu tại sao lỗi Dark Mode xảy ra. Cách làm đúng là dùng màu ngữ nghĩa (semantic color): thay vì code "màu chữ = #000000", lập trình viên khai báo "màu chữ = label", và hệ thống tự resolve thành đen ở Light, trắng ở Dark.

  • iOS: dùng Dynamic System Colors (như label, secondaryLabel, systemBackground) hoặc Named Color trong Asset Catalog có định nghĩa cho Any/Dark Appearance.
  • Android: dùng theme attributes (?attr/colorOnSurface, ?attr/colorSurface) và resource qualifier values-night/ chứa bảng màu tối riêng.
Lỗi Dark Mode gần như luôn xuất phát từ một chỗ nào đó hardcode màu tuyệt đối (ví dụ đặt cứng Color.WHITE cho nền một card). Ở Light thì không ai thấy vấn đề, nhưng ở Dark thì cái card trắng đó nổi bật như một cục gạch chói mắt. Nhiệm vụ của QA là săn tìm những "vết hardcode" này.

Auto-dark (Force Dark) trên Android

Android có một cơ chế đặc biệt: Force Dark. Nếu app chưa hỗ trợ Dark Mode, hệ thống (từ Android 10) có thể tự động "đảo màu thông minh" giao diện sáng thành tối. Cơ chế này tiện nhưng hay tạo ra kết quả kỳ quặc: ảnh bị đảo màu, biểu đồ sai màu, text tương phản kém. QA cần biết app của mình đang dựa vào Force Dark (rủi ro cao) hay đã có Dark theme thủ công (kiểm soát tốt).

Những thứ tuyệt đối phải kiểm ở Dark Mode

  • Độ tương phản chữ/nền đạt chuẩn WCAG (tối thiểu 4.5:1 cho chữ thường).
  • Icon, logo không bị "tàng hình" hoặc dính viền trắng.
  • Ảnh, illustration trong suốt (PNG) không lộ nền trắng.
  • Trạng thái đặc biệt: disabled button, placeholder, divider, shadow (đổ bóng đen trên nền đen là vô hình).
  • Status bar / navigation bar đổi màu chữ theo theme (icon pin, sóng phải đọc được).
  • Splash screen — thường bị bỏ quên, hay flash trắng lóa khi mở app trong Dark Mode.

Tình huống thực tế

Ví dụ 1 — Tiki và cái hóa đơn "vô hình"

Một sàn TMĐT lớn tại Việt Nam (bối cảnh dựa trên tình huống thực tế thường gặp) release tính năng Dark Mode. Ở màn hình chính, mọi thứ đẹp long lanh. Nhưng khi người dùng vào màn hình "Chi tiết đơn hàng", phần bảng kê giá — tạm tính, phí ship, giảm giá, tổng cộng — dùng chữ màu xám đậm #333333 hardcode trên nền đổi thành #121212 (dark surface). Kết quả: người dùng gần như không đọc được con số tiền của chính mình.

Lý do lọt lưới: đội thiết kế làm bảng giá này từ 2 năm trước, khi Dark Mode chưa tồn tại, nên hardcode màu chữ. Khi thêm Dark Mode, không ai rà lại các màn hình cũ. QA chỉ smoke-test trang chủ ở Dark, thấy ổn là ký.

Bài học: Dark Mode phải được test trên toàn bộ hành trình quan trọng (critical journey), đặc biệt các màn hình cũ, chứ không chỉ vài màn hình mới. Màn hình liên quan tiền bạc là ưu tiên số một.

Ví dụ 2 — App fintech và cú "flip" giữa phiên

Một ví điện tử (bối cảnh giả định kiểu MoMo/ZaloPay) có in-app setting cho phép chọn Light/Dark/System. Một QA phát hiện bug hiếm: người dùng để app ở chế độ "Theo hệ thống", đang mở màn hình xác nhận thanh toán, thì đến 18:00 — thời điểm hẹn giờ tự bật Dark của điện thoại. Hệ điều hành phát tín hiệu đổi theme ngay giữa lúc màn hình đang mở. App re-render, nhưng một dialog OTP đang hiện bị kẹt màu cũ (không nhận trait change), tạo ra một hộp thoại nửa sáng nửa tối, nút "Xác nhận" biến mất vì trắng trên trắng.

Đây là lỗi runtime theme change — cực kỳ khó bắt vì cần đúng kịch bản: app đang chạy foreground + có màn hình phức tạp đang mở + đúng lúc OS đổi theme. Nhiều app chỉ apply theme lúc khởi động (cold start) nên bị lỗi này.

Bài học: Đừng chỉ test bằng cách bật Dark rồi mở app. Phải test flip theme trong lúc app đang chạy ở nhiều màn hình, đặc biệt màn hình có dialog, bottom sheet, WebView đang mở.

Ví dụ 3 — Ảnh chia sẻ và cái logo có "quầng"

Một app tin tức tại Đông Nam Á có tính năng "chụp màn hình trích dẫn để chia sẻ Facebook". Ở Dark Mode, ảnh chia sẻ tạo ra có nền đen, và logo tòa soạn (file PNG có sẵn viền trắng để nổi trên nền tối của web) khi đặt lên nền đen của app lại tạo ra một quầng trắng lem nhem quanh logo. Ảnh trông thiếu chuyên nghiệp, và vì được chia sẻ công khai nên ảnh hưởng trực tiếp tới thương hiệu.

Thêm nữa, QA đo thử thì thấy ảnh chia sẻ ở Dark Mode có kích thước file lớn hơn 20% do nền tối nén kém — chi tiết nhỏ nhưng ảnh hưởng tốc độ upload trên mạng 3G ở tỉnh.

Bài học: Dark Mode ảnh hưởng cả đầu ra do app tạo ra (ảnh export, PDF hóa đơn, QR code) chứ không chỉ giao diện hiển thị. Asset dùng trong hai chế độ nên có phiên bản riêng.

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

Đây là quy trình test Dark Mode có hệ thống bạn có thể áp dụng ngay:

Bước 1 — Xác định app hỗ trợ Dark Mode ở mức nào. Hỏi dev/đọc spec: app có Dark theme thủ công, hay đang dựa vào Force Dark của Android? Có in-app setting Light/Dark/System không? Điều này quyết định phạm vi test.

Bước 2 — Test ma trận 3 chế độ. Với mỗi màn hình quan trọng, kiểm ở: (a) OS Light + app follow, (b) OS Dark + app follow, (c) nếu có in-app setting thì thêm Force Light và Force Dark ghi đè. Ghi rõ trong test case cột "Theme" để không bị lẫn.

Bước 3 — Flip system dark mode và quan sát.

  • iOS: Settings → Display & Brightness → chọn Dark. Hoặc bật Control Center có nút Dark Mode để chuyển nhanh. Trên Simulator dùng xcrun simctl ui booted appearance dark / light.
  • Android: Settings → Display → Dark theme, hoặc dùng Quick Settings tile. Qua ADB: adb shell "cmd uimode night yes" để bật, no để tắt, auto để theo lịch.
Bước 4 — Test flip TRONG lúc app đang mở. Mở app tới một màn hình phức tạp (có dialog/bottom sheet/WebView), rồi flip theme hệ thống. Quan sát app có re-render đúng không, có bị kẹt màu cũ không, có bị flash trắng không.

Bước 5 — Rà từng trạng thái đặc biệt. Với mỗi màn hình, kiểm cả empty state, error state, loading skeleton, disabled control, form với validation lỗi. Đây là nơi màu hardcode hay ẩn nấp.

Bước 6 — Kiểm tương phản bằng công cụ. Đừng chỉ tin mắt. Dùng Accessibility Scanner (Android) hoặc chụp màn hình rồi kiểm bằng công cụ đo contrast ratio, đảm bảo đạt tối thiểu 4.5:1. Chú ý chữ xám nhạt trên nền tối rất hay fail.

Bước 7 — Kiểm các bề mặt hệ thống. Status bar (icon pin/sóng), navigation bar, splash screen, keyboard, share sheet, và các asset app export ra (ảnh, PDF).

Bước 8 — Kiểm tính bền vững (persistence). In-app theme setting phải được nhớ sau khi kill app và mở lại. Chọn "Force Dark", kill app, mở lại — phải vẫn Dark.

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

Lỗi 1 — Chỉ test cold start, bỏ qua runtime flip. Rất nhiều bug Dark Mode chỉ lộ khi đổi theme lúc app đang chạy. Luôn có test case "flip while running".

Lỗi 2 — Chỉ test màn hình mới. Màn hình cũ (được viết trước khi có Dark Mode) là ổ chứa màu hardcode. Ưu tiên rà lại các màn hình legacy, đặc biệt màn hình tiền bạc và pháp lý (điều khoản, hóa đơn).

Lỗi 3 — Tin vào mắt thường về độ tương phản. Mắt người dễ bị đánh lừa, nhất là trên màn hình OLED có độ sáng cao. Luôn dùng công cụ đo contrast ratio để có con số khách quan.

Lỗi 4 — Quên WebView và nội dung nhúng. WebView trong app không tự động theo Dark Mode của native. Nội dung web, banner quảng cáo, trang điều khoản nhúng thường vẫn sáng chói giữa app tối. Cần kiểm riêng.

Lỗi 5 — Bỏ qua splash screen. Splash thường hardcode nền trắng + logo, gây flash lóa mắt khi mở app trong đêm ở Dark Mode. Đây là ấn tượng đầu tiên, đừng bỏ qua.

Mẹo tăng tốc:

  • Tạo một checklist Dark Mode dùng chung, đính kèm vào Definition of Done cho mọi feature UI. Không có Dark Mode pass thì không được coi là "done".
  • Chụp cặp ảnh Light/Dark đặt cạnh nhau cho mỗi màn hình — vừa dễ review, vừa là bằng chứng khi báo bug.
  • Kết hợp với Visual Regression Testing (sẽ học ở bài sau): chụp baseline riêng cho Light và Dark, để tự động phát hiện thay đổi màu ngoài ý muốn.
  • Với Android, hỏi rõ dev app có forceDarkAllowed không — nếu app dựa vào Force Dark tự động, hãy test kỹ vì kết quả đảo màu rất khó lường.

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

  • Ma trận theme: Chọn một app bạn đang dùng (ví dụ một app ngân hàng hoặc TMĐT Việt Nam có Dark Mode). Lập bảng test 5 màn hình quan trọng x 3 chế độ (Light-follow, Dark-follow, và Force ghi đè nếu có). Đánh dấu Pass/Fail và chụp ảnh minh chứng.
  • Săn màu hardcode: Bật Dark Mode ở OS, đi hết một critical journey (ví dụ: đăng nhập → xem sản phẩm → thêm giỏ → thanh toán). Ghi lại mọi chỗ chữ khó đọc, card trắng lạc lõng, icon tàng hình. Với mỗi lỗi, chụp ảnh và ghi giả thuyết nguyên nhân (hardcode màu gì).
  • Runtime flip: Mở app tới màn hình có dialog hoặc bottom sheet đang hiện, rồi dùng Quick Settings (Android) hoặc Control Center (iOS) flip Dark Mode. Ghi lại xem UI có re-render đúng không, có bị kẹt màu hay flash không. Viết thành bug report chuẩn nếu phát hiện lỗi.
  • Đo tương phản: Chụp màn hình một form ở Dark Mode, dùng công cụ đo contrast ratio kiểm phần chữ placeholder và chữ phụ (secondary text). Kết luận có đạt 4.5:1 không, đề xuất màu thay thế nếu fail.

Tóm tắt

Dark Mode không phải là "đổi nền sang đen" mà là một trạng thái song song của toàn bộ giao diện, nhân đôi số tổ hợp UI mà QA phải kiểm. Kể từ iOS 13 và Android 10, người dùng có thể bật dark mode ở cấp hệ thống, và nguyên tắc cốt lõi là: nếu app tuyên bố hỗ trợ Dark Mode thì phải respect cài đặt hệ thống.

Nguồn gốc gần như mọi lỗi Dark Mode là màu hardcode thay vì màu ngữ nghĩa (semantic color / theme attribute / values-night). Vì vậy QA cần: test ma trận 3 chế độ (Light/Dark/Force), đặc biệt chú ý flip theme lúc app đang chạy, rà kỹ các trạng thái đặc biệt và màn hình legacy, đo độ tương phản bằng công cụ chứ không tin mắt, và đừng quên các bề mặt hay bị bỏ sót: splash screen, status bar, WebView, và cả asset app export ra như ảnh chia sẻ hay hóa đơn PDF.

Ba câu chuyện trong bài — bảng giá vô hình, dialog OTP kẹt màu giữa phiên, và logo có quầng trắng khi chia sẻ — cho thấy lỗi Dark Mode vừa dễ lọt vừa ảnh hưởng trực tiếp tới tiền bạc và thương hiệu. Hãy biến Dark Mode thành một mục bắt buộc trong Definition of Done, và bạn sẽ chặn được cả một lớp bug mà nhiều team vẫn để lọt ra production.

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