4

I am new to Jetpack Compose testing and trying to figure out how to access the values of an OutlinedTextField to perform Instrumentation tests on them:

I can't figure out the syntax to access and check some of the values in the SemanticsNode of the EditFeild.

I am using the following Instrumentation Test:

@Test
fun NameTextField_LongInput_CompleteStatusAndLabelCorrect() {
    composeTestRule.setContent {
        ComposeTemplateTheme {
            NameTextInput(name = "RandomName123", onNameInfoValid = { isComplete = it })
        }
        assertEquals(isComplete, true)

        // This is accessing the label text 
        composeTestRule.onNodeWithText("Name").assertIsDisplayed()
        //How do I access the Editable text?
        //composeTestRule.onNodeWithEditableText("RandomName123") // How do I do something like this?!?!123
    }
}

I would like to figure out how to access various items in this tree:

printToLog:
 Printing with useUnmergedTree = 'false'
 Node #1 at (l=0.0, t=110.0, r=1080.0, b=350.0)px
  |-Node #2 at (l=48.0, t=158.0, r=1032.0, b=326.0)px
    ImeAction = 'Default'
    EditableText = 'RandomName123' // HOW DO I ACCESS THIS?!?! I want to confirm values of this?!?!
    TextSelectionRange = 'TextRange(0, 0)'
    Focused = 'false'
    Text = '[Name]'
    Actions = [GetTextLayoutResult, SetText, SetSelection, OnClick, OnLongClick, PasteText]
    MergeDescendants = 'true'

Here is the complete Composable I am trying to test:

@Composable
fun NameTextInput(name: String, onNameInfoValid: (Boolean) -> Unit) {
    // Name
    val nameState = remember { mutableStateOf(TextFieldValue(name)) }
    val nameString = stringResource(R.string.name)
    val nameLabelState = remember { mutableStateOf(nameString) }
    val isNameValid = if (nameState.value.text.length >= 5) {
        nameLabelState.value = nameString
        onNameInfoValid(true)
        true
    } else {
        nameLabelState.value = stringResource(R.string.name_error)
        onNameInfoValid(false)
        false
    }
    OutlinedTextField(
        shape = RoundedCornerShape(card_corner_radius),
        value = nameState.value,
        singleLine = true,
        onValueChange = { if (it.text.length <= 30) nameState.value = it },
        isError = !isNameValid,
        label = { Text(nameLabelState.value) },
        keyboardOptions = KeyboardOptions(
            keyboardType = KeyboardType.Text,
            capitalization = KeyboardCapitalization.Words
        ),
        colors = customTextColors(),
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizfull_verthalf)
    )
}
Booger
  • 18,579
  • 7
  • 55
  • 72

1 Answers1

11

Great question, you can access the textfield via the tag of its modifier.

OutlinedTextField(
    ...
    modifier = Modifier
        .fillMaxWidth()
        .padding(horizfull_verthalf)
        .testTag("field")
)

Then access the text field with above tag, you can assert the result as below:

val value = composeTestRule.onNodeWithTag("field")
value.assertTextEquals("RandomName123") // verify value of textfield
for ((key, value) in value.fetchSemanticsNode().config) {
    Log.d("AAA", "$key = $value") // access and print all config
    if (key.name == "EditableText"){
        assertEquals("RandomName123", value.toString())
    }
}

Try and test success on my side.

Liem Vo
  • 5,149
  • 2
  • 18
  • 16
  • If I am not mistaken, the OP was how to test the semantics of a given node. Test tags just add new semantics. – Abhimanyu Nov 06 '21 at 08:40
  • @Abhimanyu Maybe I misunderstand the question too. BTW, with `testTag`, it help to verify the value of TextField. – Liem Vo Nov 06 '21 at 08:54
  • Yes, I am aware of the test tag. In react we can do exactly what the OP wants. I am trying to find the alternative in Compose. – Abhimanyu Nov 06 '21 at 08:56
  • @Abhimanyu Great. Just update with the key and value, we can compare `key.name` with expected test and assert the value. – Liem Vo Nov 06 '21 at 09:38
  • @liemvo - thanks this does answer my original question - but can you enhance your answer to show how to get just the `Editable Text` value out of the Semantics (it is not a normal tree and I couldn't figure out how to just get that value to make my assert). Can you enhance your answer to show how to get *just* the value of `Editable Text` out of the Semantics and do the assert? (I think that will make this question more valuable in the future). I will reward the bounty either way, but this extra info would help. – Booger Nov 06 '21 at 13:52
  • @Booger update the answer with get value of EditableText and verify it – Liem Vo Nov 06 '21 at 18:42
  • Perfect, that should help the next person...I can't award the bounty yet (based on time) but I will. Thanks. I can't figure out how to access that exact key, without iterating through the list. But that is outside the scope of my original question (and your clarification does make the answer complete.). – Booger Nov 06 '21 at 20:38
  • Did you check this [hasTextExactly](https://developer.android.com/reference/kotlin/androidx/compose/ui/test/package-summary#hasTextExactly(kotlin.Array,kotlin.Boolean))? You can set `includeEditableText` to true. – jonatbergn Jan 09 '22 at 18:54
  • 2
    To potentially improve on this answer a bit, instead of doing the for loop with the `key.name == "EditableText"` check, you could instead do, `value.fetchSemanticsNode().config[SemanticsProperties.EditableText][0].text`. – T. Colligan Aug 03 '22 at 20:43
  • @LiemVo is there any way to fetch semanticNodes inside our code? (i mean not inside test code). I'm trying to create a custom semantic property and access it inside my composable function – Saman Sattari Jun 08 '23 at 08:54