How to Debug Missing Labels in Mobile Apps

How to Debug Missing Labels in Mobile Apps starts with recognizing that a missing label is more than a cosmetic glitch—it can break accessibility, cause test failures, and hide critical UI states. Whe

March 28, 2026 · 16 min read · Common Issues

How to Debug Missing Labels in Mobile Apps starts with recognizing that a missing label is more than a cosmetic glitch—it can break accessibility, cause test failures, and hide critical UI states. When a screen reader cannot announce a button, or an automated test cannot locate an element by its accessibility identifier, the user experience suffers and regression risk rises. This guide walks you through a repeatable process to find why labels disappear, how to reproduce the issue reliably, which tools give the fastest signal, and what fixes prevent the problem from returning. Each section includes concrete commands, code samples, and tables you can copy into your own workflow.

Understanding Missing Labels: Definition and Impact

A label in a mobile app is the text that accessibility services read aloud or that UI‑automation frameworks use to identify a view. On Android this is usually the contentDescription attribute; on iOS it is the accessibilityLabel. When the attribute is empty, missing, or inadvertently overridden, the view becomes invisible to TalkBack, VoiceOver, Espresso, XCUITest, and similar tools.

Why Missing Labels Matter

Quick Visual Check

Open the developer options on your device and enable “Show layout bounds” (Android) or “Show View Hierarchy” (iOS). If a button appears with a visible text label but the overlay shows no accessibility outline, the label is missing from the accessibility tree.

Common Root Causes of Missing Labels

Missing labels rarely appear at random; they trace back to a handful of repeatable mistakes in layout files, view creation code, or runtime behavior. Below we break each cause into sub‑categories, show a minimal reproducible example, and note the typical symptom you will see in logs or inspectors.

1. Layout XML or Storyboard Oversights

Developers sometimes forget to set android:contentDescription or accessibilityLabel when they copy‑paste a view. In other cases they set it to @string/empty or nil intentionally for a design reason, later forgetting to restore it.

Android XML example (missing):


<Button
    android:id="@+id/btnSubmit"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Submit"
    <!-- contentDescription missing -->
    />

iOS Storyboard example (missing):

In the Identity Inspector, the “Accessibility” checkbox is unchecked, leaving accessibilityLabel as nil.

2. Code‑Generated Views

When views are instantiated programmatically, the accessibility property is often omitted. This is common in custom cell factories, dynamically generated forms, or when using libraries that create views on your behalf.

Kotlin snippet (missing):


val button = Button(context).apply {
    text = "Save"
    // contentDescription not set
}

Swift snippet (missing):


let button = UIButton(type: .system)
button.setTitle("Save", for: .normal)
// accessibilityLabel not assigned

3. Localization and Resource Errors

A label may be present in the default values/strings.xml but absent in a locale‑specific file. When the app switches language at runtime, the missing string resolves to an empty value, wiping the accessibility name.

Example: values-es/strings.xml lacks Guardar. When the device locale is es_ES, the button’s contentDescription pulls an empty string.

4. Dynamic Content Overwrites

Some apps update a view’s label after inflation, e.g., to reflect a badge count. If the update clears the accessibility property or sets it to null, the earlier value is lost.

Android example (overwrite):


button.text = "${getString(R.string.cart)} ($count)"
button.contentDescription = null   // unintentional wipe

iOS example (overwrite):


button.setTitle("Cart (\(count))", for: .normal)
button.accessibilityLabel = nil   // clears previous label

5. Third‑Party Library or Theme Side Effects

Certain UI kits apply their own styling or accessibility defaults. A library might set contentDescription to @null to hide decorative elements, unintentionally affecting functional buttons that inherit the same style.

6. Accessibility State Changes

When the app responds to system accessibility events (e.g., user turns on “Reduce Motion” or “Increase Contrast”), some developers reset the view hierarchy and forget to re‑apply labels.

7. ProGuard / R8 Shrinking (Android)

If contentDescription values are referenced only in XML and not accessed via code, aggressive shrinking may remove the string resources, leaving the attribute empty at runtime.

8. Interface Builder Runtime Exceptions (iOS)

A mis‑connected outlet can cause the view controller to set accessibilityLabel on a nil object, silently failing and leaving the label unset.

Reproducing Missing Labels Reliably

A bug that appears only under specific conditions is hard to fix. The following matrix helps you provoke the issue consistently across devices, orientations, language settings, and font scales.

Test Matrix Table

DimensionValues to TestPurpose
OS versionAndroid 9, 10, 11, 12, 13; iOS 15, 16, 17Catch OS‑specific accessibility bugs
Device form factorPhone (small), tablet, foldableVerify layout‑inflation paths differ
OrientationPortrait, LandscapeSome configs load alternate layout files
Language/localeen_US, es_ES, ja_JP, ar_SA, fr_FR (right‑to‑left)Detect missing localized strings
Font scale100%, 120%, 150%, 200% (Android) / Dynamic Type sizesSpot clipping or layout‑pass that clears labels
Accessibility modeTalkBack / VoiceOver ON, Switch Control ONEnsure labels are read when assistive tech is active
ThemeLight, Dark, High ContrastThemes may override accessibility attributes
Installation stateClean install, update from previous version, downgradeReveal migration‑script or data‑loss issues

How to automate the matrix (Android):


#!/usr/bin/env bash
# param: apk path
APK=$1
for lang in en es ja ar fr; do
  for orient in portrait landscape; do
    for scale in 100 120 150 200; do
      adb shell am set-device-density $scale
      adb shell am set-orientation $orient
      adb shell am set-locale $lang
      adb install -r "$APK"
      adb shell monkey -p com.example.app -c android.intent.category.LAUNCHER 1
      # run your UI test suite here
    done
  done
done

iOS equivalent using xcrun simctl:


for lang in en es ja fr ar; do
  for orient in portrait landscape; do
    for scale in 1.0 1.2 1.5 2.0; do
      xcrun simctl boot "iPhone 14"
      xcrun simctl spawn booted defaults write -g AppleLanguages -array "$lang"
      xcrun simctl spawn booted defaults write -g AppleDisplayScaleFactor -float $scale
      xcrun simctl spawn booted uiinterfaceorientation $orient
      xcrun simctl install booted MyApp.app
      xcrun simctl launch booted com.example.myapp
      # run XCUITest
    done
  done
done

Running the full matrix on a CI agent (or a local script) will surface missing labels that only appear, for example, in right‑to‑left locales with a large font scale.

Tools and Signals for Diagnosis

When a missing label is suspected, gather evidence from multiple sources before diving into code. The following tools give complementary signals: low‑level logs, runtime introspection, and automated test output.

Android Signals

Tool / CommandWhat It ShowsTypical Use
logcat -s AccessibilityManagerAccessibility service events, hints when contentDescription is nullSpot TalkBack complaints
adb shell uiautomator dump /tmp/view.xmlCurrent view hierarchy with contentDescription attributesGrep for empty strings
Accessibility Scanner (Play Store)Highlights views lacking labels in a screenshotQuick visual triage on device
Espresso onView(withContentDescription(""))Fails when a view has empty descriptionUnit‑test level detection
Android Studio Layout InspectorLive hierarchy with ability to edit attributesEdit contentDescription on‑the‑fly to verify fix
SUSATest autonomous explorationGenerates a session report flagging “missing label” for each screenGives a baseline across many user personas without writing tests

iOS Signals

Tool / CommandWhat It ShowsTypical Use
Xcode Console (accessibilityDebug)Logs when VoiceOver attempts to read a nil labelEnable via UIAccessibility.post(notification: .announcement, argument: nil)
Accessibility Inspector (Xcode)Shows accessibilityLabel, value, traits for selected viewImmediate inspection
xcrun simctl spawn booted uiaccessibilitySimulates VoiceOver navigation and outputs spoken textDetect missing spoken cues
XCUITest await element.labelReturns empty string if label missingAssert in test
SUSATest (web/agent mode)Crawls the app, records each screen’s accessibility labelsProduces a CSV of screens with empty labels

Cross‑Platform Signals

Step‑by‑Step Diagnosis Workflow

Follow this iterative process to go from symptom to root cause. Each step narrows the scope and tells you which tool to reach for next.

1. Capture the Symptom

2. Verify the Attribute Is Empty

If the attribute is present but contains a placeholder like @string/empty or nil, move to step 3.

3. Determine If the Issue Is Static or Dynamic

4. Check Localization and Resources

If the key is absent in the test locale, the label will be empty at runtime.

5. Examine Code Paths That Mutate the Property

6. Inspect Themes and Styles

7. Validate With a Fix and Re‑run the Matrix

8. Add a Regression Guard

9. Document the Finding

Fixes for Each Common Cause

Below are concrete remediations for the categories identified earlier. Each includes a before/after code snippet and a brief explanation of why it works.

Fix 1: Add Missing Static Label

Android XML


<Button
    android:id="@+id/btnSubmit"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Submit"
+   android:contentDescription="@string/save_desc"
    />

iOS Storyboard

Fix 2: Initialize Label in Code‑Generated Views

Kotlin


val button = Button(context).apply {
    text = "Save"
    contentDescription = getString(R.string.save_desc)   // explicit init
}

Swift


let button = UIButton(type: .system)
button.setTitle("Save", for: .normal)
button.accessibilityLabel = NSLocalizedString("save_desc", comment: "Save button")

Fix 3: Supply Missing Localized Strings

Create the missing entry in every values-/strings.xml (Android) or Localizable.strings (iOS).

Android (values-es/strings.xml)


<string name="save_desc">Guardar</string>

iOS (Localizable.strings)


"save_desc" = "Guardar";

If you use a code‑generated string file (e.g., via strings.xmlR.string), ensure the generation step runs for all locales.

Fix 4: Preserve Label When Updating Dynamic Content

Instead of nulling the property, update only the visible text and keep the accessibility label intact, or concatenate the new info to the existing label.

Android (preserve + append)


button.text = "${getString(R.string.cart)} ($count)"
val baseDesc = getString(R.string.cart_desc)
button.contentDescription = "$baseDesc ($count)"

iOS (preserve + append)


button.setTitle("Cart (\(count))", for: .normal)
if let base = button.accessibilityLabel {
    button.accessibilityLabel = "\(base) (\(count))"
} else {
    button.accessibilityLabel = "Cart (\(count))"
}

Fix 5: Audit Third‑Party Styles

Locate the style/theme that nulls the description and either:

Android (override in layout)


<Button
    style="@style/Widget.MyApp.Button"
    android:contentDescription="@string/save_desc"
    ... />

Fix 6: Re‑apply Labels After Accessibility State Changes

In the callback where you respond to system settings (e.g., onConfigurationChanged), re‑set any labels that might have been cleared.

Android


override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    // Re‑apply labels that depend on configuration
    findViewById<Button>(R.id.btnSubmit).contentDescription =
        getString(R.string.save_desc)
}

iOS


override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    if traitCollection != previousTraitCollection {
        saveButton.accessibilityLabel = NSLocalizedString("save_desc", comment: "Save button")
    }
}

Fix 7: Prevent Shrink‑Removed Resources

Add a keep rule in proguard-rules.pro (Android) or ensure the resource is referenced in code.


-keepclassmembers class * {
    @android:string *;
}

Or simply reference the string in a dummy method:


fun keepStrings() {
    val _ = R.string.save_desc   // forces keeper
}

Fix 8: Fix Broken Outlet Connection (iOS)

Open the storyboard, select the view controller, and verify the outlet is connected. If not, drag from the circle to the UI element. Alternatively, instantiate the view programmatically and set the label there.

Prevention Strategies

Finding missing labels after they have reached users is costly. Embedding guardrails early reduces the chance they ever appear.

1. Lint Rules


android {
    lintOptions {
        enable 'MissingContentDescription'
    }
}

disabled_rules: -line_length
opt_in_rules:
  - accessibility_label

2. Automated UI Test Assertions

Add a base test class that iterates over all visible views on a screen and asserts each has a non‑empty label.

Android (Espresso + Kotlin)


fun AssertAccessibilityLabels() {
    onRoot().matchers {
        isDisplayed()
    }.forEach { matcher ->
        onAllViews(withMatcher(matcher)).check {
            val desc = it.contentDescription?.toString() ?: ""
            assertFalse(desc.isEmpty(), "View ${it::class.java.simpleName} missing contentDescription")
        }
    }
}

iOS (XCUITest + Swift)


func assertAllLabelsPresent() {
    let app = XCUIApplication()
    let all = app.descendants(matching: .any)
    for element in all.boundByIndex(0..<all.count) {
        XCTAssertFalse(element.label.isEmpty, "\(element) missing accessibilityLabel")
    }
}

Run this after each navigation step in your test suite.

3. Design System Enforcement

If your team uses a component library (e.g., Material Components, SwiftUI), make the label a required parameter of the component’s initializer.

Kotlin (Material Button wrapper)


@Composable
fun MyButton(
    text: String,
    contentDescription: String, // required
    onClick: () -> Unit
) {
    Button(
        onClick = onClick,
        contentDescription = contentDescription
    ) {
        Text(text)
    }
}

Swift (SwiftUI wrapper)


func MyButton(title: String, accessibilityLabel: String, action: @escaping () -> Void) -> some View {
    Button(title, action: action)
        .accessibilityLabel(accessibilityLabel)
}

4. Code Review Checklist

Add a short item to your pull‑request template:


- [ ] All newly added interactive views have a non‑null contentDescription / accessibilityLabel set (either in XML/storyboard or in code).
- [ ] Localized strings for accessibility labels exist for all supported locales.
- [ ] No theme or style overrides set contentDescription/accessibilityLabel to @null or nil unless the view is purely decorative.

5. Continuous Integration Step for Localization

Run a script that builds the APK/IPA for each locale and checks that every contentDescription / accessibilityLabel resolves to a non‑empty string.

Example (Android Gradle task)


task checkAccessibilityLabels {
    doLast {
        def apk = file("build/outputs/apk/debug/app-debug.apk")
        def xml = "aapt dump xmltree $apk AndroidManifest.xml"
        // pseudo‑logic: extract all string references used for contentDescription and verify they exist in each values‑*/strings.xml
    }
}

If the task fails, the build is red, preventing the missing label from shipping.

How Autonomous Exploration Surfaces Missing Labels Early

Traditional testing relies on predefined scripts, which often miss edge‑case label loss that only appears under a specific persona or interaction pattern. Autonomous QA platforms like SUSATest explore the app without scripts, exercising a variety of user behaviors and automatically checking accessibility properties.

What SUSATest Does

  1. Screen Discovery – Starting from a launch activity or URL, the agent taps, scrolls, types, and handles dialogs, building a graph of reachable screens.
  2. Persona Simulation – For each visited screen, the agent replays the interaction using profiles such as “elderly” (long press durations, larger tap targets), “impatient” (rapid taps), “accessibility” (talkback/voiceover enabled), and “adversarial” (inputs designed to trigger error states).
  3. Label Validation – After each action, the agent queries the accessibility tree:

If the value is empty or whitespace, the agent logs a “missing label” event, captures a screenshot, and records the exact interaction sequence that led to the state.

  1. Cross‑Session Learning – The agent remembers which screens have been explored and which actions produced dead ends. Subsequent runs focus on unexplored branches, increasing label coverage over time.
  2. Report Generation – At the end of a session, SUSATest emits a JSON (or HTML) report listing:

Integrating Into Your Pipeline

Add the SUSATest agent as a step in your CI after the build artifact is produced:


# Install the CLI (once per runner)
pip install susatest-agent

# Run exploration on the freshly built APK
susatest explore \
    --apk app/build/outputs/apk/debug/app-debug.apk \
    --personas curious impatient accessibility \
    --output-dir ./susatreport \
    --max-depth 6

The step fails the build if any missing‑label event exceeds a configurable threshold (e.g., more than 2 per screen). Because the agent explores without test scripts, it catches regressions introduced by refactors that inadvertently removed a label from a dynamically generated view—a scenario that often slips past unit tests.

Real‑World Example

A finance app added a new “Quick Transfer” button inside a recycler view adapter. The button was instantiated programmatically, and the developer set only the text. During a manual QA pass, the button appeared correctly labeled in English, but when the tester switched the device to Spanish, TalkBack announced nothing. The root cause was a missing strings-es.xml entry for the button’s description.

When the same build was run through SUSATest with the “accessibility” persona enabled, the agent:

The developer fixed the localization file, reran the SUSATest step, and the missing‑label count dropped to zero for that screen.

Checklist for Rapid Triage

Use this table when you first notice a silent element. Match the observed symptom to the most likely cause, then follow the associated verification steps and fix.

Symptom (what you see)Likely CauseQuick VerificationTypical Fix
Element visible, TalkBack/VoiceOver says nothingMissing contentDescription / accessibilityLabel in layout or codeDump view hierarchy; attribute empty or @null/nilAdd the attribute in XML/storyboard or initialize in code
Label appears in default language, blank in specific localeMissing localized string for contentDescription / accessibilityLabelCheck values-/strings.xml or Localizable.strings for the keyAdd the missing translation
Label present after inflation, cleared after user interactionCode overwrites property to null/nil or empty stringSet breakpoint/log after view creation and after each UI eventPreserve existing label or rebuild it from base + new data
Label missing only when TalkBack/VoiceOver is ONTheme or style sets @null/nil for accessibility when enabledSearch styles for android:contentDescription="@null"Remove the override or scope it to decorative views only
Label missing after font‑size change or layout‑rotationAlternate layout resource (layout-land, values-sw600dp) lacks attributeInspect the specific qualifier folder’s XMLCopy the attribute to the alternate layout or use include
Label missing only in a specific build variant (e.g., release)Shrinker removed string resourceCompare release vs debug APK string tablesAdd a keep rule or reference the string in code
Label missing after system accessibility setting changeApp clears labels in onConfigurationChanged / traitCollectionDidChangeAdd log in those callbacks; see if label gets resetRe‑apply label in the callback

Closing Takeaways

By following the workflow, applying the fixes, and institutionalizing the checks outlined here, you can shift missing‑label detection from a reactive firefight to a proactive, repeatable part of your quality process. The result is an app that speaks clearly to every user, passes accessibility audits reliably, and yields stable UI tests—all without the overhead of writing and maintaining endless test scripts.

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