Testing and release evidence

UI State Tests for On-Device AI Features

Cover missing model, download, verify, load, stream, cancel, fail, repair, and ready states explicitly.

Review status: Fact and editorial QA complete; owner review pending · 2026-10-06 · Fact IDs: COVE-LIFECYCLE-01, ARCH-01, COVE-INFERENCE-CONTRACT-2026-10

Direct answer

A UI state test checks the user-facing contract between an app state and what a person can see or do. It asks whether the screen explains the current situation, exposes only actions that make sense, and reacts to the action through the expected state owner. It is not a test that the model ran, a test-double test of Flow timing, or an accessibility audit. Keep those evidence boundaries separate so a passing screenshot or Compose interaction is not mistaken for a completed inference task. Begin with the state type and the actual states the application can reach. The shared ModelState enum declares NOT_DOWNLOADED, DOWNLOADING, DOWNLOADED, LOADING, READY, and ERROR. Current ModelManager paths assign several of these states, but the implementation should be checked before asserting every enum value is reachable: download errors and caught load exceptions currently return to NOT_DOWNLOADED, while ERROR appears in a UI branch. Treat an unreachable state as a contract discrepancy to investigate, not as proof that the manager emits it. Product screens may also derive separate UI states from permissions, selected input, or result validation; name those explicitly instead of collapsing them into one loading boolean. Create a transition table before writing Compose tests. For every UI state, record the heading/body, progress or result region, enabled actions, and event that should change state. A missing model may offer a setup/download action; an active download can show progress and avoid presenting a duplicate start action; a ready state can enable a task only when its other prerequisites are met; a failure state needs a retry or repair path only if the app implements one. Do not invent cancel, retry, permission settings, or background completion controls because another application has them. Base expectations on actual route wiring and product behavior. Test the stateful screen without loading LiteRT-LM. Compose UI testing can install content with a supplied state, find nodes through their text or semantics, assert visibility or enabled state, and perform user actions. When a screen receives a ViewModel or provider, use a deterministic fake upstream to cause the next state. Assert both sides of the interaction: the intended callback was invoked once, and the resulting UI state reflects the response. A click test with a static state only proves the clickable node exists; it does not show the state transition happened. For each transition, test the most important negative action as well. A loading state should not start another load if the product prevents duplicates; a missing artifact should not enable inference; an error or unavailable state should not show success; and a rapid state update should not leave stale output visible. Verify that progress labels, errors, and result buttons match the state source. If StateFlow rapidly changes through intermediate values, tests should assert the current state and meaningful user result instead of requiring a late collector to observe every transient value. Keep model ownership outside the Composable. A preview or Compose test should receive state and callbacks, not create a native engine as a side effect of composition. Use a fake repository or ViewModel state only where an actual injectable boundary exists; for current ModelManager, which is Context-backed and owns concrete dependencies, do not claim it can be swapped for a fake through a constructor it does not have. A screen test may use a fake state holder while a separate test exercises ModelManager integration. Accessibility deserves a separate pass. Compose testing and semantics are related, but this guide does not measure TalkBack announcement cadence, switch access, or focus behavior. If a test uses semantics matchers, that is a way to locate and interact with UI nodes; it does not make the test an accessibility audit. Likewise, a visually correct state does not prove a private request stayed on device or that output is valid. Attach only the assertion that the fixture actually demonstrates. Use a stable content fixture and isolate each state. Set the screen to one state, assert the expected copy and visible controls, perform a meaningful action, then update the supplied state and assert the new contract. Avoid sleeping for asynchronous work; drive fake events or wait for Compose synchronization. Cover process or navigation behavior only when the screen contract owns it. Do not use screenshots as the sole assertion for whether a button is enabled or a task completed. The resulting record should include screen, state fixture, event, callback count, before/after state, rendered message, enabled controls, and what was not exercised. A passing Compose test proves that the tested screen represents the supplied state and wires the tested action for that build. It does not prove that the repository downloaded bytes, the runtime loaded a model, the generated result is correct, or the workflow is accessible to every user.

The screen shows model ready while inference is not yet available

A Compose home screen derives its primary action from ModelState. A test fixture reports DOWNLOADED, while the runtime has not yet reached READY. The team needs to verify the user can understand the difference and that tapping does not claim a task has already run.

Decision

Render each state from a supplied fixture, assert the correct copy and enabled action, and make the follow-up event drive a separate loading or task state rather than a fake success.

Expected outcome

The UI communicates the exact tested state and exposes a valid next step without claiming that a model file is already loaded or a user task completed.

Evidence to collect

The claim becomes reviewable only when the following evidence is attached to the same artifact and test run:

  • State transition table
  • UI fixtures
  • Accessibility and recovery assertions

A release claim is valid only for the recorded configuration and acceptance set. Generalization requires additional records.

Implementation workflow

  1. Inventory reachable and declared states. Read the state owner and UI mapping. Separate enum values that current code assigns from declared but unreachable branches, and record any derived states for permission, input, output validation, or navigation.
  2. Write the state-to-action table. For each fixture, list visible explanation, progress or result, enabled controls, and the event each control sends. Include forbidden actions such as inference without a ready model or duplicate download starts if the app policy blocks them.
  3. Render with supplied state. Set Compose content using the screen's state and callbacks, or an existing injectable state holder. Find nodes, assert visible text and enabled status, and avoid constructing a native engine inside composition.
  4. Perform action and drive next state. Click a meaningful action, assert its callback or provider was invoked once, then supply the resulting state and verify the next screen contract. A static click assertion alone is not a transition test.
  5. Cover negative and stale states. Verify missing/failed/loading states do not present false success, disallowed actions are unavailable, and stale result text is cleared or labeled when a new task begins.
  6. Separate adjacent evidence. Use semantics as test selectors without claiming an accessibility audit. Run ModelManager, fake Flow behavior, native inference, privacy traffic, and result correctness as their own evidence paths.

Keep each transition observable. A failure should identify the stage, artifact, runtime, and recovery action without logging private user content.

Acceptance criteria

  • Every tested UI state has an explicit message, visible controls, enabled/disabled rules, and expected event mapping.
  • Meaningful actions invoke the intended callback once and show the supplied next state; a static button test is not reported as a successful task.
  • The report distinguishes declared-but-unreachable states and does not claim model execution, accessibility certification, privacy, or output correctness from Compose assertions.

Decision table

DimensionChoice or evidenceHow to interpret it
State-to-view testRender a supplied state and assert text/control contractProves presentation for that state fixture, not that the real state owner emitted it.
Interaction transition testClick, observe callback, then supply next stateProves the tested user action is wired and its represented transition is coherent.
End-to-end model taskRun downloader/runtime and validate a real resultTests integration behavior; it requires separate artifact, device, and task evidence.

Failure patterns to prevent

  • Using one loading boolean
  • Skipping invalid transitions

Also prevent silent fallback, unpinned artifacts, missing cancellation, and conclusions that combine unlike configurations. Store unsuccessful runs alongside successful ones.

Minimum reproducibility record

LayerRecord
DeviceManufacturer, model, chipset, RAM class, operating-system build
SoftwareApplication version and git commit
ModelFamily, variant, revision, format, file length, hash, quantization
RuntimeName, revision, requested backend, observed backend evidence
WorkloadFixture revision, input hash, prompt hash, output policy
OutcomeCompleted, failed, cancelled, fallback, and privacy-safe diagnostics

Release checklist

  • ☐ The primary query is answered without an unsupported number.
  • ☐ Every artifact and runtime is pinned.
  • ☐ The representative task and failure policy are explicit.
  • ☐ Lifecycle, cancellation, cleanup, and fallback are tested.
  • ☐ User-content and network boundaries are documented.
  • ☐ Result wording applies only to the recorded configuration.
  • ☐ The page links to raw method or evidence when results are added.

Sources and related evidence

Chinese deployment and troubleshooting content is organized in the .奇连 AI 端侧专题.

Frequently asked questions

Does a Compose test prove the model loaded?

No. It proves what the screen rendered for its supplied state and the tested callbacks. Model loading requires evidence from ModelManager or a real runtime path.

Should a UI state test assert every declared enum value is reachable?

Only after checking the state owner. A declared value may not currently be emitted. Record unreachable branches as a discrepancy instead of manufacturing a production transition.

Do semantics matchers make this an accessibility test?

Not by themselves. Semantics can help tests find controls, but TalkBack behavior, focus order, announcements, and assistive navigation need their own review.