'How to enter text in Jetpack compose TextField through UI tests?

In Jetpack compose I have a TextField and I'm trying to write Espresso UI tests.
I didn't find how I can enter text in the TextField, any ideas, please?

TextField(
    value = textState.value,
    modifier = Modifier.fillMaxWidth(),
    onValueChange = {
        textState.value = it
        apiServiceCall(textState.value.text)
    },
    keyboardOptions = KeyboardOptions(
        capitalization = KeyboardCapitalization.Sentences)
    ),
)

@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()

@Test
fun enterTextAndMakeServiceCall() {
    ActivityScenario.launch(MainActivity::class.java)

    // TODO: Enter text inside the TextField
    composeTestRule.onNode(hasText(getString(R.string.result)))
}


Solution 1:[1]

I first set the testTag modifier on the composable I want to test:

const val MY_TEXTFIELD_TAG = "myTextFieldTag"

TextField(
    value = textState.value,
    modifier = Modifier.fillMaxWidth().testTag(MY_TEXTFIELD_TAG),
    onValueChange = {
        textState.value = it
    },
    keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
)

And then from your test you can set and check the value like this:

@Test
fun setAndCheckTheTextFieldValue() {
    ActivityScenario.launch(MainActivity::class.java)
    val resultText = "result"

    // Sets the TextField value
    composeTestRule.onNodeWithTag(MY_TEXTFIELD_TAG).performTextInput(resultText)

    // Asserts the TextField has the corresponding value
    composeTestRule.onNodeWithTag(MY_TEXTFIELD_TAG).assert(hasText(resultText))
}

UPDATE:

Another way I use lately is to use the contentDescription instead.

Let's say you have a TextField with content description like this one (not using state hoisting for simplicity on this sample):

@Composable
fun MyTextField() {
    val textState = remember { mutableStateOf(TextFieldValue()) }
    val textFieldContentDescription = stringResource(id = R.string.text_field_content_description)
    TextField(
        value = textState.value,
        modifier = Modifier
            .fillMaxWidth()
            .semantics { contentDescription = textFieldContentDescription },
        onValueChange = {
            textState.value = it
        },
    )
}

The test could be something like:

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun setAndCheckTheTextFieldValue() {
    lateinit var textFieldContentDescription: String
    composeTestRule.setContent {
        textFieldContentDescription = stringResource(id = R.string.text_field_content_description)
        MaterialTheme {
            MyTextField()
        }
    }
    val resultText = "result"

    // Sets the TextField value
    composeTestRule.onNodeWithContentDescription(textFieldContentDescription).performTextInput(resultText)

    // Asserts the TextField has the corresponding value
    composeTestRule.onNodeWithContentDescription(textFieldContentDescription).assert(hasText(resultText, ignoreCase = true))
}

and this way the app is more accessible as well by having content descriptions.

Solution 2:[2]

Yo just can find by nodeText you need to search the string in label property

composeTestRule.onNodeWithText("user").performTextInput("userCred")

That will find that Textfield and will enter the text "userCred".

This solution only works if you don't have any other text that matches that string.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Daniel Carracedo