How to Write Test Cases for Dark Mode (With Examples)

How to Write Test Cases for Dark Mode (With Examples)

May 17, 2026 · 18 min read · How-To Guides

How to Write Test Cases for Dark Mode (With Examples)

Understanding Dark Mode Requirements

Dark mode is more than a color swap; it is a user‑experience feature that influences readability, contrast, and accessibility. Before writing test cases you must collect the functional and non‑functional expectations that the product team has defined for the dark variant of the UI. Typical sources include style guides, design tokens, accessibility WCAG criteria, and user‑story acceptance criteria.

Create a requirements matrix that maps each UI element (text, icon, button, image, background) to its dark‑mode specification:

ElementLight‑mode valueDark‑mode valueWCAG contrast ruleNotes
Primary text#212121#E0E0E0≥ 4.5:1 for normal textUse body font
Primary button bg#6200EE#BB86FC≥ 3:1 for UI componentsMust stay tappable
Icon (secondary)#757575#B0B0B0≥ 3:1Avoid pure white
Surface (card)#FFFFFF#121212N/AEnsure elevation shadows remain visible
Error text#B00020#CF6679≥ 4.5:1Keep semantic meaning

When the matrix is complete, each row becomes a source of test conditions. For example, the contrast rule for primary text yields a test that validates the measured luminance ratio against the 4.5:1 threshold.

Anatomy of a Dark Mode Test Case

A well‑structured test case contains the following fields:

Keep each step atomic and avoid bundling multiple verifications into a single step; this makes failure analysis easier.

Positive Test Cases

Positive cases verify that the implementation conforms to the specification when the system behaves as intended. Below is a representative set of positive dark‑mode tests for a typical mobile e‑commerce app.

IDPreconditionsStepsExpected Result
DM‑TC‑001Device set to system‑wide dark mode; app version 2.4.0; user logged out1. Launch app 2. Navigate to home screenHome screen background = #121212 (±3%); primary text = #E0E0E0 (±3%); contrast ratio ≥ 4.5:1
DM‑TC‑002Same as above; user logged in with default profile1. Open product detail page 2. Scroll to descriptionDescription text color = #E0E0E0; background = #121212; no text clipping
DM‑TC‑003Same as above; cart contains 2 items1. Tap cart icon 2. View cart summaryCart item names = #E0E0E0; price = #BB86FC; divider = #303030; all elements visible
DM‑TC‑004Same as above; no network connectivity1. Pull‑to‑refresh on home feed 2. Observe offline bannerOffline banner background = #121212; text = #E0E0E0; icon = #B0B0B0; banner fully visible
DM‑TC‑005Same as above; user navigates to settings1. Open Settings → Appearance 2. Toggle “Follow system theme” off 3. Select “Dark”App UI remains in dark mode regardless of system setting; all colors match dark‑mode matrix
DM‑TC‑006Same as above; user has large font size enabled (accessibility)1. Open any list screen 2. Verify text scalingText scales proportionally; contrast ratios remain ≥ 4.5:1 after scaling
DM‑TC‑007Same as above; app receives a push notification1. Notification appears 2. Expand notificationNotification background = #121212; title = #E0E0E0; body = #B0B0B0; action button = #BB86FC
DM‑TC‑008Same as above; user opens a modal dialog1. Tap “Add to wishlist” 2. Observe dialogDialog surface = #121212; title = #E0E0E0; body = #B0B0B0; primary button = #BB86FC; secondary button = #303030
DM‑TC‑009Same as above; user views an image with overlay text1. Open product gallery 2. Select image with captionOverlay background = rgba(0,0,0,0.4); text = #FFFFFF; contrast ≥ 4.5:1
DM‑TC‑010Same as above; user rotates device to landscape1. Rotate device 2. Observe home screenAll colors and contrast values remain unchanged; layout does not break dark‑mode tokens

These ten cases already cover core screens, user interactions, accessibility overrides, and system‑level theme changes.

Negative Test Cases

Negative cases confirm that the app does not incorrectly leak light‑mode colors or violate contrast when something goes wrong.

IDPreconditionsStepsExpected Result
DM‑TC‑011Device in light mode; app version 2.4.01. Launch app 2. Navigate to any screenNo dark‑mode token (#121212, #E0E0E0, etc.) appears; all colors match light‑mode palette
DM‑TC‑012Device in dark mode; app forced to light mode via developer override1. Enable “Force light theme” in developer options 2. Launch appApp UI renders in light mode; dark‑mode colors absent
DM‑TC‑013Device in dark mode; custom theme applied that lacks dark definitions1. Apply third‑party theme that only defines light colors 2. Launch appApp falls back to system dark colors where defined; any missing element shows a visible contrast warning (logged)
DM‑TC‑014Device in dark mode; network error returns malformed JSON with missing color field1. Trigger product list load with error payload 2. Observe error viewError view uses default dark‑mode error colors (#CF6679 text on #121212 bg); no crash
DM‑TC‑015Device in dark mode; user enables high‑contrast mode (Android)1. Activate “High contrast text” in accessibility 2. Open any screenText contrast ratio ≥ 7:1; colors may be shifted but remain legible; no missing UI elements
DM‑TC‑016Device in dark mode; app receives a remote config that forces a light‑mode banner1. Receive config payload with banner background = #FFFFFF 2. App renders bannerBanner overrides with light colors; test expects a visible warning in logs and a QA flag (design token violation)
DM‑TC‑017Device in dark mode; user opens a WebView loading an external site without dark‑mode CSS1. Navigate to help page (WebView) 2. Observe contentWebView retains site’s native colors; app does not force dark mode; no visual glitches around the WebView border
DM‑TC‑018Device in dark mode; low battery triggers system‑wide dimming1. Set battery level to 5% 2. Observe UIUI remains readable; contrast ratios stay ≥ 4.5:1; no sudden color inversion
DM‑TC‑019Device in dark mode; user enables color inversion (accessibility)1. Turn on “Color inversion” 2. Open appAll colors are inverted relative to dark mode; test expects that the inversion does not produce unreadable contrast (ratio ≥ 4.5:1 after inversion)
DM‑TC‑020Device in dark mode; app receives a push notification with a custom sound but no visual payload1. Send silent notification 2. Observe status barNo visual changes; app does not crash or display stray light‑mode artifacts

These negative cases guard against regression where a light‑mode token accidentally creeps into dark mode, or where external influences (themes, accessibility overrides) break the intended appearance.

Edge and Boundary Cases

Edge cases push the limits of the implementation: extreme values, rare device states, and combinations that rarely appear in manual testing but can surface in production.

IDPreconditionsStepsExpected Result
DM‑TC‑021Device in dark mode; font scale set to 200% (largest setting)1. Open any screen with long text 2. Verify line wrappingText wraps without overflow; contrast remains ≥ 4.5:1; no clipping
DM‑TC‑022Device in dark mode; system language set to right‑to‑left (Arabic)1. Change locale to ar‑EG 2. Launch appLayout mirrors correctly; dark‑mode colors stay intact; no overlapping
DM‑TC‑023Device in dark mode; screen zoom set to 300% (magnification gesture)1. Triple‑tap to zoom 2. Pan across screenZoomed region retains correct colors; edge of zoom does not reveal light‑mode artifacts
DM‑TC‑024Device in dark mode; app receives a dynamic color update from a server while UI is animating1. Start a page transition 2. Mid‑animation push new color token 3. Observe final stateUI settles to new dark token without flicker; no intermediate light‑mode flash
DM‑TC‑025Device in dark mode; user rapidly toggles system theme 10 times within 5 seconds1. Use ADB shell cmd to switch theme repeatedly 2. Observe app after each switchApp follows each switch within 200ms; no UI gets stuck in previous theme
DM‑TC‑026Device in dark mode; app opened in split‑screen mode with another app in light mode1. Launch app in left half 2. Open light‑mode app in right half 3. Interact with bothApp maintains dark mode; no color bleed from adjacent app; contrast unaffected
DM‑TC‑027Device in dark mode; battery saver enabled, which may restrict background services1. Enable battery saver 2. Perform background sync 3. Return to foregroundUI colors unchanged; any background‑driven theme updates are deferred until foreground
DM‑TC‑028Device in dark mode; user enables “Remove animations” (developer option)1. Disable animations 2. Navigate between screensTransition is instant; dark‑mode colors appear correctly without delay
DM‑TC‑029Device in dark mode; app receives a local notification with a custom icon that lacks a dark version1. Trigger notification 2. Expand viewNotification uses fallback monochrome icon; contrast with background ≥ 3:1; no missing icon
DM‑TC‑030Device in dark mode; app runs on an emulator with a custom skin that overrides system colors1. Launch emulator with skin “Midnight” 2. Start appApp respects skin’s dark palette where defined; otherwise uses app‑defined dark tokens; no hard‑coded light colors appear

These cases test the robustness of the theme engine under stress, localization, accessibility, and system‑level quirks.

Data Setup and Environment Configuration

Reliable dark‑mode testing requires repeatable control over the theme state. Below are the most common ways to prepare the environment for both manual and automated runs.

Manual Setup

Automated Setup (CLI / Script)

Android via ADB


# Set system UI mode to dark
adb shell settings put system ui_night_mode 2
# Verify
adb shell settings get system ui_night_mode   # should return 2

iOS via XCUITest


let app = XCUIApplication()
app.launchArguments += ["-AppleInterfaceStyle", "Dark"]
app.launch()

Web via Playwright


const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({
    colorScheme: 'dark'
  });
  const page = await context.newPage();
  await page.goto('https://example.com');
  // assertions …
  await browser.close();
})();

Appium (Android) for Dark Mode


DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("appPackage", "com.example.app");
caps.setCapability("appActivity", ".MainActivity");
caps.setCapability("autoGrantPermissions", true);
caps.setCapability("autoAcceptAlerts", true);
// Force dark mode via UI mode
caps.setCapability("androidDeviceShell", "settings put system ui_night_mode 2");
AndroidDriver driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), caps);

Data Preparation

Prioritization and Traceability Matrix

Not all test cases carry equal risk. Prioritize based on impact (user‑visible defects), likelihood (frequency of occurrence), and effort (complexity to automate).

IDPriority (P1‑P3)Risk AreaTrace to RequirementAutomatable?
DM‑TC‑001P1Core UI contrastREQ‑DM‑001 (Primary text contrast)Yes
DM‑TC‑002P1Text readabilityREQ‑DM‑002 (Body text contrast)Yes
DM‑TC‑003P2Actionable elementsREQ‑DM‑005 (Button colors)Yes
DM‑TC‑004P2Offline stateREQ‑DM‑008 (Offline banner)Yes
DM‑TC‑005P1Theme overrideREQ‑DM‑010 (Follow system theme)Yes
DM‑TC‑006P2Accessibility scalingREQ‑DM‑012 (Large font)Yes
DM‑TC‑007P1Notification UIREQ‑DM‑015 (Push notification)Yes
DM‑TC‑008P2DialogsREQ‑DM‑018 (Modal dialog)Yes
DM‑TC‑009P2Media overlaysREQ‑DM‑020 (Image caption)Yes
DM‑TC‑010P1Orientation changeREQ‑DM‑022 (Landscape)Yes
DM‑TC‑011P1Light‑mode leakREQ‑DM‑001 (Negative)Yes
DM‑TC‑012P1Forced light themeREQ‑DM‑010 (Override)Yes
DM‑TC‑013P2Missing theme definitionsREQ‑DM‑025 (Fallback)Partial
DM‑TC‑014P2Error view colorsREQ‑DM‑003 (Error states)Yes
DM‑TC‑015P2High‑contrast textREQ‑DM‑012 (Accessibility)Yes
DM‑TC‑016P3Remote config bannerREQ‑DM‑030 (Config‑driven UI)Yes
DM‑TC‑017P2WebView dark handlingREQ‑DM‑031 (WebView)Yes
DM‑TC‑018P2Low‑battery dimmingREQ‑DM‑033 (Power state)Yes
DM‑TC‑019P2Color inversionREQ‑DM‑012 (Invert)Yes
DM‑TC‑020P1Silent notificationREQ‑DM‑015 (Notification)Yes
DM‑TC‑021P2Extreme font scalingREQ‑DM‑012 (Font scale)Yes
DM‑TC‑022P2RTL layoutREQ‑DM‑034 (Localization)Yes
DM‑TC‑023P2Screen zoomREQ‑DM‑035 (Magnification)Yes
DM‑TC‑024P1Dynamic token mid‑animationREQ‑DM‑036 (Dynamic theming)Yes
DM‑TC‑025P1Rapid theme togglingREQ‑DM‑037 (Theme stability)Yes
DM‑TC‑026P2Split‑screenREQ‑DM‑038 (Multi‑window)Yes
DM‑TC‑027P2Battery saverREQ‑DM‑039 (Power management)Yes
DM‑TC‑028P2No animationsREQ‑DM‑040 (Animation disable)Yes
DM‑TC‑029P2Notification icon fallbackREQ‑DM‑041 (Asset fallback)Yes
DM‑TC‑030P2Emulator skin overrideREQ‑DM‑042 (Skin compatibility)Partial

Use this matrix to decide which tests to run on every commit (P1), which to run nightly (P2), and which to reserve for weekly exploratory runs (P3).

Manual vs Automated Execution Strategies

A balanced approach leverages the speed of automation for repeatable checks and the flexibility of manual testing for exploratory and visual validation.

Automation Foundations

  1. Unit‑level token tests – Verify that the theme manager returns the correct color for a given key (getColor(R.color.primary)).
  2. Component snapshot tests – Render UI components in isolation (using Jetpack Compose preview, SwiftUI Preview, or Storybook) under dark mode and compare PNG snapshots against a baseline.
  3. End‑to‑end UI flows – Use Espresso/XCUITest for Android/iOS, or Playwright/Cypress for web, to execute the test cases from the matrix.
  4. Contrast assertions – Integrate axe-core (web) or Android’s ContrastChecker into the test suite to fail automatically when a ratio falls below the threshold.

Sample Espresso test for DM‑TC‑001:


@Test
public void homeScreenDarkModeContrast() {
    // assume device already in dark mode via adb
    onView(withId(R.id.home_screen)).check(matches(isDisplayed()));
    onView(withId(R.id.primary_text)).check(matches(withTextColor(Color.parseColor("#E0E0E0"))));
    // contrast check using a custom matcher
    onView(withId(R.id.primary_text)).check(matches(hasContrastRatioAtLeast(4.5f)));
}

Sample Playwright test for a web header:


test('header has dark mode colors and sufficient contrast', async ({ page }) => {
  await page.goto('/');
  const header = page.locator('header');
  await expect(header).toHaveCSS('background-color', 'rgb(18, 18, 18)');
  await expect(header.locator('h1')).toHaveCSS('color', 'rgb(224, 224, 224)');
  // contrast assertion using axe
  await expect(await page.evaluate(() => axe.run())) .toPass({ rules: [{ id: 'color-contrast' }] });
});

Manual Exploratory Sessions

Combining Both

Run the automated suite on every pull request. If it passes, schedule a short manual exploratory session (15‑20 minutes) focused on the high‑risk areas identified in the priority matrix (P1 and P2). Document any new observations as additional test cases or as updates to existing ones.

Leveraging Autonomous Exploration (SUSA Mention)

Autonomous testing tools can complement scripted cases by exercising the app in ways that resemble real user behavior, uncovering issues that static test matrices might miss.

SUSA (SUSATest) is an autonomous QA platform that, given an APK or a web URL, explores the application using a variety of personas—curious, impatient, novice, power‑user, accessibility‑focused, and others. Each persona follows its own behavior model: taps, scrolls, text entry, handling of dialogs, and navigation through typical flows such as login or checkout.

When pointed at a build with dark mode enabled, SUSA will:

  1. Discover screens that are reachable only under dark‑mode tokens (e.g., a settings page that appears after a long‑press on a theme toggle).
  2. Detect contrast violations by analyzing rendered frames against WCAG thresholds, flagging any element that falls below 4.5:1 for normal text or 3:1 for UI components.
  3. Identify missing dark assets by checking resource qualifiers; if an icon lacks a -night variant, the platform logs a fallback usage.
  4. Capture ANRs or crashes triggered by rapid theme switching or by specific interaction sequences (e.g., opening a modal while a background sync is in progress).
  5. Generate regression scripts – after a run, SUSA can export Appium (Android) and Playwright (Web) scripts that reproduce the discovered paths, giving you a starting point for automated coverage.

In practice, you would:

Integrate SUSA runs into your CI pipeline as a nightly job. Treat its findings as supplemental test cases: any new defect discovered becomes a candidate for addition to the manual test matrix or for automation in the next sprint.

Checklist for Dark Mode Testing

Use this concise checklist before marking a release as ready for dark‑mode support.

Real‑World Production Gotchas

Even with thorough test cases, certain issues only manifest after the app reaches a broad user base. Below are common production‑only dark‑mode pitfalls and how to guard against them.

GotchaWhy It Appears in ProductionMitigation
Theme flash on cold startThe app launches before the system theme broker finishes delivering the dark value, causing a brief light‑mode splash.Use a splash screen that respects android:windowBackground set to a theme‑aware color, or launch the app with a window background that matches dark mode from the start.
Third‑party ad networks serving light‑mode creativesAds are rendered in a WebView that does not inherit the app’s theme.Request dark‑mode creatives from the network, or overlay a semi‑transparent dark tint on the ad container; monitor fill‑rate and impression metrics.
Dynamic fonts downloaded at runtimeSome font providers deliver only a regular weight; when the app applies bold via fontWeight, the rendered glyphs may appear lighter on OLED screens, reducing contrast.Pre‑bundle font weights or use the provider’s API to request the appropriate weight; test contrast after font download.
System‑level dark mode scheduler (e.g., Bedtime mode)The OS may automatically switch themes at a set time, which the app might miss if it only checks theme on launch.Register a listener for UiModeManager changes (onConfigurationChanged) and refresh UI accordingly.
Accessibility service overriding colorsServices like “Color correction” or “Dark reader” can remap colors after the app has rendered them, sometimes creating unexpected hues.Test with the most common accessibility services enabled; ensure that any remapping still yields sufficient contrast (use the service’s preview mode).
OLED pixel shift causing perceived color changeOn OLED panels, prolonged display of a static bright element can cause temporary pixel shift, making a dark element look slightly off‑gray.Avoid static bright UI elements for extended periods; use animations or periodic updates to mitigate burn‑in perception.
Remote configuration delivering light‑mode assetsA feature flag may unintentionally push a light‑mode image asset URL, breaking the visual contract.Validate asset URLs against a naming convention (*_night.png) in the CI pipeline; fail the build if a light asset is referenced in a dark‑mode build.
Gesture navigation bar color mismatchThe system navigation bar may remain light if the app does not explicitly set its color in dark mode.In styles.xml set android:navigationBarColor to a theme‑aware color, or handle it programmatically in onCreate.
Keyboard or IME background not adaptingSome custom keyboards ignore the app’s theme and show a light background, causing a jarring contrast when typing.Test with the default system keyboard and popular third‑party keyboards; if needed, provide a hint to the user to switch to a dark‑compatible keyboard.
Accessibility shortcut (triple‑tap) triggering magnification that reveals seamsWhen magnification is enabled, the UI may render at a higher scale, exposing seams between light and dark assets.Run the app with magnification gestures enabled and verify that no seams or misaligned assets appear.
Battery‑saver restricting background theme updatesSome OEMs aggressively throttle background services, delaying theme changes after a system toggle.Ensure that theme changes are handled in the foreground and that any background work that depends on theme does not block UI thread.

Document these gotchas in your team’s knowledge base and add regression tests where possible (e.g., a test that confirms the splash screen does not flash light).

Conclusion

Writing high‑signal test cases for dark mode requires a deliberate blend of specification‑driven design, systematic exploration, and automation that validates both functional correctness and non‑functional qualities like contrast and accessibility. Start by extracting concrete requirements into a token matrix, then build a comprehensive set of positive, negative, and edge test cases that cover every UI element, interaction, and system state. Prioritize those cases using a risk‑based matrix, automate the repeatable checks, and reserve manual exploratory sessions for the nuances that scripts cannot capture.

Leveraging autonomous exploration tools such as SUSA adds a valuable layer of real‑world simulation, surfacing issues that only appear under specific persona behaviors or device conditions. Pair those tests that are easy to miss in a traditional test plan.

Finally, treat dark mode not as a one‑time feature but as an ongoing concern: each platform update, new third‑party library, or accessibility enhancement can reintroduce regressions. Keep the test matrix living, update it whenever a new token or component is added, and run your combined manual‑automated suite on every release. By following the steps outlined here, you will deliver a dark‑mode experience that is consistently readable, accessible, and delightful for users across all lighting conditions.

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