| name | asserting-node-state-and-text |
| description | Use this skill to verify a Compose semantics node's properties from a UI test using `assertExists`, `assertDoesNotExist`, `assertIsDisplayed`, `assertIsNotDisplayed`, `assertIsDeactivated`, `assertIsEnabled`, `assertIsOn`, `assertIsOff`, `assertIsSelected`, `assertIsFocused`, `assertTextEquals`, `assertTextContains`, `assertContentDescriptionEquals`, `assertValueEquals`, `assertRangeInfoEquals`, `assertHasClickAction`, plus the generic `assert(matcher)` escape hatch and the boolean `isDisplayed()` / `isNotDisplayed()` for `waitUntil` predicates. Covers collection variants `assertCountEquals`, `assertAny`, `assertAll`. Use when the developer wants to verify a Switch is on, a Button is enabled, a Text shows the expected string, a node is displayed vs merely composed, or asks about `assertIsDisplayed` vs `assertExists`. If the developer mentions any `assert*` API on `SemanticsNodeInteraction`, use this skill. |
| license | Apache-2.0. See LICENSE for complete terms. |
| metadata | {"author":"Jaewoong Eum (skydoves)","keywords":["jetpack-compose","ui-testing","assertIsDisplayed","assertIsEnabled","assertTextEquals","assertCountEquals","assertHasClickAction","assertExists","semantics-assertions","compose-test-assert"]} |
Asserting Node State and Text — Verify Through the Framework, Not Through fetchSemanticsNode
Once a finder resolves to a SemanticsNodeInteraction, the next step is asserting it. Compose ships a typed assertion for almost every semantic property; the generic assert(matcher) covers the rest. This skill picks the right assertion, distinguishes "exists" from "displayed", and shows when boolean predicates belong inside waitUntil.
When to use this skill
- The developer wants to verify a Switch is on, a Checkbox is unchecked, a Button is enabled, a Text equals an expected string.
- The developer asks about
assertIsDisplayed vs assertExists (one verifies on-screen, the other verifies presence in the tree).
- The developer needs a custom assertion via
assert(matcher) for a property without a typed extension.
- The developer wants to verify a collection: "exactly 3 items", "at least one is selected", "all are enabled".
- A test compares
node.config[...] directly instead of using a typed assertion.
When NOT to use this skill
- The right node cannot be located — see
../../finders/finding-nodes-by-tag-text-content/SKILL.md.
- The assertion is geometric (width, height, position) — see
./asserting-bounds-and-dimensions/SKILL.md.
- The check needs a custom
SemanticsMatcher — see ../../finders/composing-semantics-matchers/SKILL.md.
- The state changes asynchronously and the assertion is timing-sensitive — see
../../synchronization/synchronizing-with-idle/SKILL.md.
Prerequisites
- A working
ComposeTestRule / ComposeUiTest. See ../../setup/configuring-test-dependencies/SKILL.md.
- The node has been located through
onNode* / onAllNodes* / a navigator.
- For
assertIsDisplayed semantics, the node must be composed AND placed AND at least partially on-screen post-clip (Assertions.kt:30-39).
Workflow
Patterns
Pattern: assertIsDisplayed over assertExists for user-facing checks
@Test
fun submit_isVisibleAfterError() {
rule.setContent { CheckoutScreen(state = state) }
rule.onNodeWithTag(SubmitTag).assertExists()
}
@Test
fun submit_isVisibleAfterError() {
rule.setContent { CheckoutScreen(state = state) }
rule.onNodeWithTag(SubmitTag).assertIsDisplayed()
}
Pattern: typed assertIsOn over manual config reads
@Test
fun darkMode_switch_isOn() {
rule.setContent { SettingsScreen() }
val node = rule.onNodeWithTag(DarkModeSwitchTag).fetchSemanticsNode()
assertEquals(ToggleableState.On, node.config[SemanticsProperties.ToggleableState])
}
@Test
fun darkMode_switch_isOn() {
rule.setContent { SettingsScreen() }
rule.onNodeWithTag(DarkModeSwitchTag).assertIsOn()
}
assertIsOn is assert(isOn()) (Assertions.kt:74); isOn() is expectValue(SemanticsProperties.ToggleableState, ToggleableState.On) (Filters.kt:61-62). The error names the property and dumps the node automatically.
Pattern: assertTextEquals (vararg) handles merged Text + EditableText
TextField(value = "hello", onValueChange = {}, label = { Text("Name") },
modifier = Modifier.testTag(NameFieldTag))
rule.onNodeWithTag(NameFieldTag).assertTextEquals("Name", "hello")
assertTextEquals(vararg) matches the unordered set of SemanticsProperties.Text plus, by default, SemanticsProperties.EditableText (Assertions.kt:181-185 → Filters.kt:274-293). To exclude editable text from the comparison: assertTextEquals("Name", includeEditableText = false).
Pattern: assertCountEquals instead of onAllNodes(...).onFirst().assertExists()
rule.onAllNodesWithTag(ItemTag).onFirst().assertExists()
rule.onAllNodesWithTag(ItemTag).assertCountEquals(3)
Pattern: assert(matcher) for properties without a typed assertion
@Test
fun row_hasPriorityOne() {
rule.setContent { TaskList(tasks = tasks) }
rule.onNodeWithTag(TaskRowTag)
.assert(SemanticsMatcher.expectValue(PriorityKey, 1))
}
PriorityKey is a custom SemanticsPropertyKey<Int> set via Modifier.semantics { priority = … }. See ../../finders/composing-semantics-matchers/SKILL.md for matcher composition.
Pattern: assertAny (fails on empty) vs assertAll (passes on empty)
rule.onAllNodesWithTag(RowTag).assertAny(isSelected())
rule.onAllNodesWithTag(RowTag).assertAll(isEnabled())
assertAny throws AssertionError("Failed to assertAny … no node matched") on an empty collection (Assertions.kt:305-307). assertAll returns successfully on an empty collection (Assertions.kt:323-339). Pick deliberately.
Pattern: isDisplayed() inside waitUntil
rule.waitUntil { rule.onNodeWithTag(SnackbarTag).assertIsDisplayed(); true }
rule.waitUntil(timeoutMillis = 2_000) {
rule.onAllNodesWithTag(SnackbarTag).fetchSemanticsNodes().isNotEmpty() &&
rule.onNodeWithTag(SnackbarTag).isDisplayed()
}
rule.onNodeWithTag(SnackbarTag).assertTextContains("Saved")
isDisplayed() returns false when zero nodes match and only throws on multiple matches (Assertions.kt:343-351). Cross-reference: ../../synchronization/synchronizing-with-idle/SKILL.md for waitUntil vs mainClock.advanceTimeUntil.
Pattern: assertIsDeactivated for SubcomposeLayout retained children
rule.onNodeWithTag(RetainedSlotTag).assertIsDeactivated()
assertIsDeactivated fetches the node without skipping deactivated ones (SemanticsNodeInteraction.kt:137-148) and checks node.layoutInfo.isDeactivated.
Mandatory rules
- MUST use the typed assertion (
assertIsOn, assertIsEnabled, assertTextEquals, …) over fetchSemanticsNode().config[...]. The framework's error message includes the selector, the node dump, and the failed clause.
- MUST use
assertIsDisplayed() when the contract is "the user sees this"; MUST use assertExists() only when the contract is "this is in the semantics tree" (e.g. checking presence in unmerged tree without on-screen requirement).
- MUST use the boolean
isDisplayed() / isNotDisplayed() inside waitUntil { … } blocks; MUST NOT call throwing assertIs* from inside waitUntil.
- MUST use
assertCountEquals for collection cardinality; MUST NOT index [0] to imply "the only one".
- MUST NOT repeat the same assertion across chained calls when one composed matcher would do — see
../../finders/composing-semantics-matchers/SKILL.md.
- PREFERRED: prefer tag-anchored lookups before assertions. Skydoves hot take #1.
Verification
References