DEV Community

Evgeniy Bobrovnichiy
Evgeniy Bobrovnichiy

Posted on

The Last Error Is Not Always the Root Cause: Diagnosing Android Test Failures

An Android UI test times out. Maestro reports that an element is missing. A screenshot shows a blank or half-rendered screen.

It is tempting to fix the selector, increase the timeout, and rerun the test.

But those are often secondary symptoms. The application may have crashed several events earlier.

This article uses a synthetic UK weather forecast application to show how I separate the first actionable failure from the noise that follows it.

The failure

Assume the test artifact contains this stack trace:

Fatal Exception: java.lang.IllegalStateException:
No binding for class
dev.flakylens.samples.weatheruk.forecast.ForecastDetailsViewModel provided.
See documentation for ViewModelProviderFactory

at dev.flakylens.samples.weatheruk.di.AssistedViewModelFactoryRegistry.factoryFor(
    AssistedViewModelFactoryRegistry.kt:37)
at dev.flakylens.samples.weatheruk.forecast.ForecastDetailsComponent(
    ForecastDetailsComponent.kt:51)
at dev.flakylens.samples.weatheruk.WeatherForecastActivity.Content(
    WeatherForecastActivity.kt:59)
Enter fullscreen mode Exit fullscreen mode

A UI-test report generated later might contain:

Element not found: "Tomorrow's forecast"
Enter fullscreen mode Exit fullscreen mode

Both messages are real, but they are not equally useful.

Reconstruct the failure chain

Read the evidence in chronological and causal order:

  1. WeatherForecastActivity renders the forecast details component.
  2. The component requests ForecastDetailsViewModel.
  3. AssistedViewModelFactoryRegistry.factoryFor() cannot find a compatible binding.
  4. The application throws IllegalStateException.
  5. The activity stops rendering.
  6. The UI-test runner eventually reports a missing element or timeout.

The missing element is therefore an effect of the crash. Changing the selector cannot restore a ViewModel binding.

Separate evidence from inference

Good diagnostics should make this boundary explicit.

Direct evidence

  • Exception: java.lang.IllegalStateException
  • Message: no binding exists for ForecastDetailsViewModel
  • First relevant application frame: AssistedViewModelFactoryRegistry.kt:37
  • ViewModel request site: ForecastDetailsComponent.kt:51
  • Host activity: WeatherForecastActivity.kt:59

Likely inference

The host activity probably supplies a generic or incompatible ViewModelProvider.Factory for a ViewModel that requires assisted arguments.

This is not proven by the stack trace alone. Repository context is needed to verify which factory the activity uses and whether another working activity already demonstrates the correct pattern.

That distinction matters. A diagnostic tool should not present a plausible guess as a verified fact.

Find the repair pattern in the repository

Search the codebase for:

  • the missing ViewModel class;
  • its assisted factory or generated binding;
  • the activity from the stack trace;
  • another activity that creates a similar ViewModel successfully;
  • overrides of defaultViewModelProviderFactory.

The project-specific API may differ, but the repair often resembles this pattern:

@Inject
lateinit var assistedFactoryProvider: AssistedViewModelFactoryProvider

override val defaultViewModelProviderFactory: ViewModelProvider.Factory
    get() = assistedFactoryProvider.create(this, intent.extras)
Enter fullscreen mode Exit fullscreen mode

The important part is not copying this snippet blindly. Confirm that:

  1. the missing ViewModel is registered with the assisted provider;
  2. the host supplies the required owner and arguments;
  3. the returned factory is used by the component requesting the ViewModel;
  4. the implementation matches an established pattern in the same repository.

Verify the fix

After changing the factory:

  1. Run the smallest test that opens the affected screen.
  2. Capture Logcat from before the navigation begins.
  3. Confirm that the original IllegalStateException is gone.
  4. Confirm that ForecastDetailsViewModel is created successfully.
  5. Only then investigate any remaining selector or timeout failure.
  6. Add a regression test that opens the activity with the required arguments.

This order prevents a secondary UI error from hiding a still-active application crash.

A reusable triage checklist

When an Android UI test produces several failures, ask:

  • What is the earliest fatal exception?
  • What is the first application-owned stack frame?
  • Which later failures require the application to have remained alive?
  • Is the proposed cause directly supported or inferred?
  • Can repository code confirm the suspected wiring problem?
  • Is there a working implementation of the same pattern nearby?

This approach applies beyond ViewModel factories. It is also useful for dependency-injection failures, malformed API responses, missing navigation arguments, startup crashes, and mock-server mismatches.

Why I built FlakyLens

I kept repeating this reconstruction manually across Logcat, Maestro, JUnit, CI output, and source code. I built FlakyLens to make the process faster and more explicit.

It accepts pasted Logcat text and .txt, .log, .xml, .json, .yaml, .yml, and .zip artifacts. The report identifies the likely primary failure, preserves supporting evidence, separates inference from facts, and can use an optional read-only GitHub connection to produce repository-aware recommendations.

Raw uploaded artifacts are processed in the browser. A demo is available without signing in.

Try the FlakyLens demo

The Founding Beta is free for the first 500 developers through January 29, 2027. No card is required and there is no automatic paid conversion.

Disclosure: I built FlakyLens, and the weather application and stack trace in this article are synthetic examples created for demonstration.

What Android failure class should I cover next: dependency injection, mock-server mismatches, Compose crashes, or CI-only timeouts?

Top comments (1)

Collapse
 
hayrullahkar profile image
Hayrullah Kar

The framing on treating UI timeouts as downstream symptoms of early crashes is spot on. One flag on coroutine-heavy android apps though: stack traces often lose the original cause chain across asyn boundaires, making early triaga trickier.