Search code examples
androidandroid-jetpack-compose

Allow custom TextInput wrapper to support UI testing `performTextInput`


I have this wrapper MyInput over compose's builtin TextField that does a series of UI operations and declares a style globally for all my inputs in the app.

I put all my components in a ui module and then I consume it from my different screens.

The problem is when I try to do UI testing and execute performTextInput("[email protected]") like so:

composeTestRule.onNodeWithTag("enter_email_field")
                .assertIsDisplayed()
                .performTextInput("[email protected]")

and I get the following result:

java.lang.AssertionError: Failed to perform text input.
Failed to assert the following: (SetText is defined)
Semantics of the node:
Node #20 at (l=84.0, t=1500.0, r=1356.0, b=1699.0)px, Tag: 'enter_email_field'
Has 1 child, 10 siblings
Selector used: (TestTag = 'enter_email_field')

Would it be possible to have my custom MyInput support these semantics? I tried reading the source for TextField but I fail to see where this is declared?

if i declare the semantics for setText myself i am asked for requestFocus and then insertTextAtCursor? Id like to just inherit these from the inner BasicTextField


Solution

  • If your custom TextInput wrapper has TextField as a child of some other composable you should first get the child TextField and then perform text input on it.

    composeTestRule.onNodeWithTag("enter_email_field")
                .onChildren()
                .filterToOne(hasSetTextAction())
                .performTextInput("[email protected]")
    
    

    You can create a helper function to avoid repetition.

    fun SemanticsNodeInteraction.editTextNode() = onChildren().filterToOne(hasSetTextAction())
    

    Previous Answer

    If you want your custom text field to support performTextInput; make sure you are supplying the test tag on the inner TextField's Modifier.

    This will work

    @Composable
    fun MyTextField(
        modifier: Modifier,
        testTag: String,
    ) {
        var text by remember { mutableStateOf("Initial") }
        Box(modifier) {
            TextField(text, { text = it }, Modifier.testTag(testTag))
        }
    }
    

    This won't

    @Composable
    fun MyTextField(
        modifier: Modifier,
        testTag: String,
    ) {
        var text by remember { mutableStateOf("Initial") }
        Box(modifier.testTag(testTag)) {
            TextField(text, { text = it }, Modifier)
        }
    }