Interview QuestionPractical QuestionFollow-up Questions

Semantics and UI Testing in Jetpack Compose

skydovesJaewoong Eum (skydoves)||8 min read

Semantics and UI Testing in Jetpack Compose

Jetpack Compose uses a semantics system to define the meaning of UI elements beyond their visual appearance. This system serves both accessibility services and the testing framework, providing a structured way to locate, interact with, and assert on UI nodes without depending on implementation details. By the end of this lesson, you will be able to:

  • Explain what the semantics tree is and how it relates to the composition tree.
  • Describe how the test framework uses semantic properties to find and interact with UI nodes.
  • Apply Modifier.semantics to expose meaning for custom components.
  • Distinguish between merged and unmerged semantics trees and when to use each.
  • Write Compose UI tests that query nodes by semantic properties rather than internal structure.

The Semantics Tree

Every composable that carries meaning to the user has a corresponding node in the semantics tree. This tree runs parallel to the composition tree and stores metadata such as text content, role, toggle state, content descriptions, and custom accessibility actions. Standard components like Text, Button, and Switch populate their semantic properties automatically.

The test framework and accessibility services both read from this tree. When a test locates a node by its text content or role, it is querying the semantics tree, not the composition tree or the rendered pixels. This separation is deliberate: the semantics tree represents what the UI means to the user, not how it is implemented internally.

Not every composable has a semantics node. Layout containers like Box, Column, and Row do not appear in the tree unless they have explicit semantic annotations. Only composables that carry user-visible meaning or interactive behavior generate semantic nodes.

Finding and Interacting With Nodes

The Compose test framework uses SemanticsMatcher to locate nodes by their semantic properties. You can match on text, content description, role, toggle state, or any custom semantic property.

val mySwitch = SemanticsMatcher.expectValue(
    SemanticsProperties.Role, Role.Switch
)
composeTestRule.onNode(mySwitch)
    .performClick()
    .assertIsOff()

This test finds a node with the Switch role, clicks it, and asserts that its toggle state is off. The test does not reference any composable function name or layout structure. It operates entirely at the semantic level, which makes it resilient to refactoring of the composable hierarchy.

Adding Semantics to Custom Components

Built-in components handle semantics automatically, but custom components drawn manually or composed from low level primitives need explicit semantic annotations. Without them, the test framework and accessibility tools cannot interact with the component meaningfully.

This interview continues for subscribers

Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.

Become a Sponsor