androidandroid-espressoandroid-jetpack-composeandroid-testingandroid-jetpack-compose-testing

How to read the Semantic values of a Jetpack Compose TextField through UI tests?


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)
    )
}

Solution

  • 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.