How to Test Dark Mode on Android (Complete Guide)

Dark mode is no longer a novelty; it is a user expectation baked into Android since API 29. When an app fails to respect the system theme, users notice immediately: text becomes unreadable, icons disa

April 21, 2026 · 17 min read · How-To Guides

Why Dark Mode Testing Matters on Android

Dark mode is no longer a novelty; it is a user expectation baked into Android since API 29. When an app fails to respect the system theme, users notice immediately: text becomes unreadable, icons disappear, and the overall experience feels broken. Beyond aesthetics, dark mode impacts battery life on OLED screens, reduces eye strain in low‑light environments, and can affect accessibility compliance.

From a quality perspective, dark mode introduces a second visual state that must be validated alongside the light state. Many defects only surface when the theme switches: hard‑coded colors, missing night‑mode resources, incorrect contrast ratios, and unintended side‑effects in custom views. Because the theme can change at runtime—via system settings, battery saver, or an in‑app toggle—tests must cover both static and dynamic scenarios.

Neglecting dark‑mode testing leads to poor user ratings, increased support tickets, and potential violations of accessibility guidelines (WCAG 2.1 AA contrast). In regulated industries, such as finance or health, a contrast failure can even trigger compliance issues. Therefore, a systematic approach to dark‑mode validation is essential for any Android release pipeline.

Dark Mode Fundamentals: System UI Modes and App Themes

Android provides two primary mechanisms for dark mode: the system‑wide UI mode (UI_MODE_NIGHT_YES/UI_MODE_NIGHT_NO) and AppCompat’s AppCompatDelegate.setDefaultNightMode. Understanding how these interact helps you design tests that cover the full spectrum of user‑initiated changes.

UI Mode vs. AppDelegate Night Mode

When the app calls setDefaultNightMode, Android recreates the activity (unless you handle uiMode changes yourself). This recreation is a prime moment for bugs: resources that rely on android:night qualifiers may not be loaded, or view inflation may use stale cached values.

Resource Qualifiers and Theme Attributes

Resources placed in values-night/ are automatically selected when uiMode & UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES. Similarly, theme attributes like ?attr/colorBackground or custom attributes` app‑switch colors without creating duplicate XML files.

Custom views that read colors directly from ContextCompat.getColor(context, R.color.my_red) ignore night qualifiers unless the color itself is defined in a night‑specific resource file. This is a common source of hard‑coded color bugs.

Configuration Changes

If you declare android:configChanges="uiMode" in the manifest, the activity will not be recreated on theme change. Instead, you receive a callback in onConfigurationChanged. Failing to update UI elements in that callback leads to stale colors after a switch.

Understanding these mechanics gives you a map of where to look for dark‑mode regressions: resource loading, activity recreation, manual view updates, and third‑party library behavior.

Test Matrix for Dark Mode (Happy Path, Error Paths, Edge Cases, Accessibility, Security/Privacy)

A structured matrix ensures you cover the dimensions that matter. Below is a comprehensive table you can adapt to your test plan. Each cell lists a concrete verification step, the expected result, and notes on automation feasibility.

CategoryTest IDDescriptionExpected ResultAutomation Feasibility
Happy PathHP‑01Launch app with system dark theme enabledAll screens render using night resources; no hard‑coded light colorsHigh (UI Automator/Espresso)
HP‑02Toggle dark mode via in‑app setting (if provided)UI updates instantly without restart; night resources appliedMedium (requires setting interaction)
HP‑03Navigate through core flow (login → home → settings → logout) in dark modeEvery screen maintains correct contrast and element visibilityHigh
HP‑04Rotate device while in dark modeConfiguration change handled; UI remains consistentHigh
HP‑05Return from background after system theme switchApp reflects new theme instantlyMedium (depends on handling of onConfigurationChanged)
Error PathsEP‑01Attempt to use a color resource missing night qualifierFallback to default color; no crashLow (detect via screenshot diff)
EP‑02Custom view reads color via Resources.getSystem()Color ignores night mode, causing contrast breakLow (static analysis)
EP‑03Third‑party library forces light theme via AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO)App overrides user preference; potential accessibility failMedium (requires spy or instrumentation)
EP‑04Activity launched with android:theme forcing light mode regardless of systemUI stays light; violates user settingHigh (manifest inspection)
Edge CasesEC‑01Enable dark mode while Battery Saver is on (may force night)UI stays dark; no flickerMedium
EC‑02Switch theme while a dialog is openDialog updates or dismisses cleanlyLow (depends on dialog implementation)
EC‑03Use multi‑window mode; one half in dark, other half in light (rare but possible on some OEM skins)Each window respects its own themeLow (device‑specific)
EC‑04Launch app on a foldable device with dual screens; inner screen dark, outer screen lightEach screen applies correct themeLow
EC‑05Change font scale to 200% while in dark modeText scales, contrast remains ≥ 4.5:1Medium (requires accessibility tools)
AccessibilityAC‑01Verify contrast ratio of all text against background ≥ 4.5:1 (AA) or 3:1 (large text)Passes WCAG AAHigh (using Android Accessibility Test Framework)
AC‑02Ensure icons and non‑text UI meet 3:1 contrastPasses WCAG AA for UI componentsHigh
AC‑03Run TalkBack navigation; all focusable elements announce correctlyNo missing labels, proper reading orderMedium
AC‑04Test with color blindness simulator (deuteranopia, protanopia)Information not conveyed solely by colorLow (requires external tool)
AC‑05Verify that dynamic theme change does not reset accessibility font sizeFont size persists after theme switchMedium
Security/PrivacySE‑01Confirm that screenshot flag (Secure) is respected in dark modeScreenshot of secure area yields black imageHigh (adb shell screencap)
SE‑02Ensure that password fields do not reveal characters via tooltip or hint color change in night modeHint remains obscuredLow (visual inspection)
SE‑03Check that dark mode does not inadvertently expose hidden debug overlaysNo overlay visibleLow
SE‑04Verify that intent extras containing sensitive data are not logged when theme changesNo logcat output with PIIMedium (logcat monitoring)
SE‑05Test that biometric prompt background follows dark theme and does not leak preview framesPrompt uses night background; no frame leakageLow (device‑specific)

How to Use the Matrix

The matrix also serves as a living document: add new rows when you discover a theme‑related defect in production.

Manual Testing Step‑by‑Step Guide

Even with automation, a disciplined manual pass catches nuances that scripts overlook—especially those tied to system UI, OEM skins, or user‑generated content.

Preparing Devices and Emulators

  1. Select a matrix of devices: Include at least one OLED phone (for battery‑saver dark), one LCD phone, a tablet, and an emulator with API 33+.
  2. Enable Developer options: Settings → About phone → Tap build number 7 times.
  3. Turn on “Show layout bounds” and “Show surface updates” to visualize invalidations.
  4. Install the app under test via adb install -r app-debug.apk.
  5. Clear app data (adb shell pm clear com.example.app) to start from a clean state each round.

Switching System Theme

Verify with adb shell cmd uimode night.

Navigating Core Flows

  1. Launch the app from launcher or via adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 1.
  2. Perform the primary user journey (e.g., sign‑up → verify email → add payment method → purchase).
  3. At each screen, pause for 2‑3 seconds to let any animations finish and observe:

Checking UI Elements

Logging and Capturing Issues

Compare with a perceptual diff tool (e.g., ImageMagick compare -metric AE).

Post‑Test Cleanup

Repeating this checklist for each build ensures you catch regressions early and provides reproducible steps for bug reports.

Automated Testing Approaches

Manual checks are indispensable, but scaling them across dozens of device configurations requires automation. Below are strategies that integrate dark‑mode‑specific, ranging from unit‑level assertions to full‑blown UI tests.

Using UI Automator and Espresso for Dark Mode Checks

Both frameworks let you query the current UI mode and assert on view properties.

Espresso test to validate night‑mode resources:


@Test
fun `dark mode applies night colors`() {
    // Force night mode via UiModeManager (requires API 29+)
    val uiMode = InstrumentationRegistry.getInstrumentation()
        .targetContext.getSystemService(UiModeManager::class.java)
    uiMode.nightMode = UiModeManager.MODE_NIGHT_YES

    // Launch activity under test
    ActivityScenario.launch(MainActivity::class.java)

    // Verify a TextView uses the night color
    onView(withId(R.id.title_text))
        .check(matches(withTextColor(R.color.title_night))) // custom matcher
}

*The withTextColor matcher can be built using ContextCompat.getColor.*

UI Automator for system‑wide theme toggle:


@Test
public void themeSwitchViaSettings() throws Exception {
    UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
    // Open Settings
    device.pressHome();
    new UiObject(new UiSelector().description("Apps")).click();
    new UiObject(new UiSelector().text("Settings")).click();
    // Navigate to Display → Theme
    new UiObject(new UiSelector().text("Display")).click();
    new UiObject(new UiSelector().text("Theme")).click();
    new UiObject(new UiSelector().text("Dark")).click();

    // Return to app and verify
    device.pressHome();
    new UiObject(new UiSelector().text("MyApp")).click();
    // Example: check that a button background is dark
    UiObject button = new UiObject(new UiSelector().resourceId("com.example.app:id/action_button"));
    assertTrue(button.getBackgroundColor() == Color.DKGRAY);
}

These tests can be run on a device farm (Firebase Test Lab, AWS Device Farm) to cover multiple API levels and OEM skins.

Leveraging Android Jetpack’s UiModeManager

Instead of relying on external intents, you can directly manipulate the UI mode inside your test suite:


@Before
fun setNightMode() {
    val uiModeManager = ApplicationProvider.getApplicationContext()
        .getSystemService(UiModeManager::class.java)
    uiModeManager.nightMode = UiModeManager.MODE_NIGHT_YES
}

Remember to call ActivityScenario.recreate() after changing the mode if your activity does not handle uiMode config changes.

Screenshot Comparison with PixelMatch or Falco

Visual regression testing catches subtle color shifts that assertions miss.

Falco setup (Gradle):


dependencies {
    debugImplementation "com.github.zhuinden:falco:1.5.0"
}

Test example:


@Rule
@JvmField
val falcoRule = FalcoRule()

@Test
fun `dark mode screen matches baseline`() {
    // Ensure night mode
    val uiMode = ApplicationProvider.getApplicationContext()
        .getSystemService(UiModeManager::class.java)
    uiMode.nightMode = UiModeManager.MODE_NIGHT_YES

    launchActivity<MainActivity>()
    falcoRule.capture("main_screen_dark")
}

Falco stores a baseline image on the first run (usually light mode) and compares subsequent captures using a perceptual diff algorithm. You can maintain separate baselines for light and night by switching the UI mode before capture.

Accessibility Automated Checks (Accessibility Test Framework)

Google’s androidx.test.espresso.accessibility library runs checks like contrast, touch target size, and content description.


@Test
fun `accessibility in dark mode`() {
    // Set night mode via UiModeManager as before
    // ...

    // Enable accessibility checks
    AccessibilityChecks.enable()

    launchActivity<MainActivity>()
    // Espresso actions that navigate through the screen
    onView(withId(R.id.next_button)).perform(click())
}

The framework will fail the test if any view violates WCAG AA contrast or lacks a description.

Integrating Dark Mode Checks into CI

  1. Unit‑level: Run Espresso/UI Automator tests on every PR against a matrix of API levels (e.g., 21, 28, 30, 33) with both light and night modes forced via UiModeManager.
  2. Screenshot: Use Falco or Shot in a separate job that pushes baseline images to a storage bucket; compare new PR screenshots and comment on the PR with a diff link.
  3. Accessibility: Add the Accessibility Test Framework as a Gradle task (connectedAndroidTest) and enforce a minimum score (e.g., no contrast failures).
  4. Exploratory: Trigger a SUSA agent run (see later) on a nightly basis to surface persona‑driven issues that scripted tests miss.

By layering these techniques, you achieve both deterministic verification and the ability to spot unexpected regressions.

Tooling and Frameworks Specific to Android Dark Mode

Beyond general UI testing libraries, several tools shine when diagnosing theme‑related problems.

Android Studio Layout Inspector

Dark Mode Debugging with ADB Commands

Screenshot Testing Libraries (Shot, Paparazzi)

Both let you define test scenarios like:


@Test
fun `shot dark mode`() {
    val scenario = launchActivity<MainActivity>()
    scenario.onActivity { activity ->
        Shot.create(activity.findViewById(R.id.root))
            .record("main_dark")
    }
}

Run the test twice—once with light mode forced, once with night mode forced—to generate two baselines.

Using SUSA (Autonomous QA) for Persona‑Driven Exploration

SUSA explores an app without pre‑written scripts, simulating distinct user personalities. When you point SUSA at an APK or a web URL and enable dark mode (via system settings or an in‑app toggle), it will:

Because SUSA builds a session‑level memory of visited screens and dead ends, each subsequent run becomes smarter: it avoids re‑testing paths that previously passed and concentrates on unexplored edge cases—such as a screen that only appears after a failed network request in dark mode.

The output includes a detailed report with screenshots, logcat excerpts, and a PASS/FAIL verdict for each explored flow (login, signup, settings, etc.). Integrating SUSA into your nightly CI pipeline provides a safety net that catches defects no unit test would think to probe.

Edge Cases That Only Appear in Production

Even the most thorough test matrix can miss issues that arise from real‑world usage patterns, device fragmentation, or interactions with external services. Below are some of the most insidious dark‑mode bugs observed in the wild.

Dynamic Theme Switching via App Settings

Many apps offer a separate “Dark mode” toggle that overrides the system setting. Bugs appear when:

Detection tip: Use Espresso to toggle the setting, then immediately navigate to a different fragment and assert that colors updated without a manual restart.

Third‑Party Libraries with Hardcoded Colors

Popular ad SDKs, analytics tools, or UI kits sometimes ship resources that lack night qualifiers. When your app enables dark mode, those views remain bright, creating a visual patchwork.

Mitigation:

Night Mode with Battery Saver

On Android 9+, Battery Saver can automatically activate night mode regardless of the user’s system theme setting. Some OEMs also expose a “Battery saver theme” switch that forces a dark UI.

Bugs manifest when:

Testing approach: Enable Battery Saver (adb shell dumpsys battery set saver true) and verify that UI still respects the user‑chosen theme (or that the app gracefully degrades to a low‑power mode without breaking readability).

Multi‑Display and Foldable Devices

Devices with secondary screens (e.g., LG V series, Microsoft Surface Duo) or foldables (Samsung Galaxy Z Fold) can report different UI modes per display.

Issues include:

Testing matrix addition:

Test IDDescriptionExpected
FD‑01Launch app on inner screen while outer screen is in light modeInner screen uses dark theme if system dark enabled
FD‑02Fold/unfold device while app is in foregroundTheme remains consistent; no flicker
FD‑03Drag app window to secondary display on a dual‑screen phoneUI updates to match display’s theme

Accessibility Services Overriding Theme

Services like font magnifiers, color inversion, or dark‑mode overlays can composite with your app’s theme, sometimes resulting in contrast that is either too low or too high.

Example: A user enables Color inversion (Settings → Accessibility → Color inversion) while your app is already in dark mode; the inversion flips dark to light, making text disappear against a light background.

Testing tip: Enable the inversion service (adb shell settings put secure accessibility_display_inversion_enabled 1) and run your standard dark‑mode matrix. Verify that all critical information remains perceivable (use TalkBack to confirm).

Checklist for Dark Mode Release

Before you tag a release as “dark‑mode ready,” run through this concise list. Mark each item as PASS or FAIL; any FAIL blocks the release.

#CheckHow to Verify
1All launcher icons and adaptive icons have night variantsInspect mipmap-* folders; verify ic_launcher-night.png exists
2No hard‑coded color literals in Java/Kotlin (use androidx.core.content.ContextCompat.getColor)Run detekt rule HardcodedColor or custom lint
3Every values-night/ resource has a matching default in values/Use ./gradlew :app:androidDependencies and inspect merged resources
4Activities correctly handle uiMode config changes (either recreate or update in onConfigurationChanged)Toggle theme via ADB while activity is in foreground; observe no stale colors
5Contrast ratio ≥ 4.5:1 for all text, ≥ 3:1 for icons and UI componentsRun Accessibility Scanner or Espresso AccessibilityChecks.enable()
6TalkBack navigation reads all labels correctly in dark modeEnable TalkBack, swipe through screens, listen for missing descriptions
7Screenshot of secure fields (FLAG_SECURE) remains black in dark modeUse adb shell screencap on a secure activity; verify black output
8Third‑party libraries do not force light mode (no setDefaultNightMode(MODE_NIGHT_NO))Search APK smali for Landroidx/appcompat/app/AppCompatDelegate;->setDefaultNightMode(I)V with argument 0
9Battery Saver activation does not break UI readabilityEnable saver, verify contrast and element visibility
10On foldable/multi‑display, each screen respects its own themeTest on a device with secondary screen or use Android Studio’s emulator with multiple displays
11No flicker or delayed updates when switching theme rapidlyPerform 10 quick toggles via ADB; visually inspect or record with screenrecord
12Accessibility services (magnification, inversion, color correction) do not render UI unusableEnable each service, run core flow, confirm usability

If any item fails, create a bug with reproduction steps, logcat snippet, and a screenshot comparing light vs. night states.

Closing Takeaways

Dark mode is more than a cosmetic toggle; it is a dual‑state contract between your app and the system. Treating it as a first‑class feature means:

By combining rigorous manual procedures, targeted automation, and exploratory, persona‑driven testing, you can ship Android apps that look great, feel comfortable, and remain accessible whether the user prefers light or dark. The effort pays off in higher user satisfaction, fewer support tickets, and confidence that your app respects the system contract that millions of Android users rely on every day. Happy testing!

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free