Fokusnavigation testen

Durch die Automatisierung von Tests zur Fokusnavigation können Sie dafür sorgen, dass Ihre App auf Hardwaretastaturen, Steuerkreuzen und Bedienungshilfen eine einheitliche und vorhersehbare Nutzererfahrung bietet. Compose bietet integrierte Test-APIs, um Tastatureingaben zu simulieren und den Fokusstatus zu prüfen.

Fokustests einrichten

Verwenden Sie zum Schreiben von Fokustests createComposeRule() aus der Compose-Testbibliothek:

@RunWith(AndroidJUnit4::class)
class FocusNavigationTest {
    @get:Rule
    val composeTestRule = createComposeRule()

Fokusziele überprüfen

Sie können prüfen, ob ein UI-Element den Fokus erhalten kann, indem Sie darauf klicken oder den Fokus anfordern und dann den Status mit assertIsFocused() und assertIsNotFocused() bestätigen.

@Test
fun interactiveElement_isFocusTarget() {
    composeTestRule.setContent {
        AppTheme {
            CardListScreen()
        }
    }

    val firstCard = composeTestRule.onNodeWithTag("card_1")
    val secondCard = composeTestRule.onNodeWithTag("card_2")

    // Focus the first element
    firstCard.performClick()
    firstCard.assertIsFocused()
    secondCard.assertIsNotFocused()
}

Eindimensionalen Fokuswechsel testen

Simulieren Sie das Drücken der Tabulatortaste und der Umschalttaste + Tabulatortaste mit performKeyInput, um zu prüfen, ob der Fokus in der Reihenfolge der Darstellung durch die Benutzeroberfläche wandert. Als Best Practice sollten Sie sowohl die Vorwärtsnavigation (Key.Tab) als auch die Rückwärtsnavigation (Key.Tab bei gedrückter Key.ShiftLeft-Taste) prüfen, um die Integrität der bidirektionalen Navigation zu gewährleisten:

@Test
fun tabKey_navigatesInAppearanceOrder() {
    composeTestRule.setContent {
        AppTheme {
            CardListScreen()
        }
    }

    val firstCard = composeTestRule.onNodeWithTag("card_1")
    val secondCard = composeTestRule.onNodeWithTag("card_2")
    val thirdCard = composeTestRule.onNodeWithTag("card_3")

    firstCard.performClick()
    firstCard.assertIsFocused()

    // Press Tab -> Moves to second card
    firstCard.performKeyInput {
        pressKey(Key.Tab)
    }
    secondCard.assertIsFocused()
    firstCard.assertIsNotFocused()

    // Press Tab -> Moves to third card
    secondCard.performKeyInput {
        pressKey(Key.Tab)
    }
    thirdCard.assertIsFocused()

    // Press Shift + Tab -> Moves backward to second card
    thirdCard.performKeyInput {
        withKeyDown(Key.ShiftLeft) {
            pressKey(Key.Tab)
        }
    }
    secondCard.assertIsFocused()
}

Zweidimensionale Richtungsnavigation testen

Simulieren Sie Pfeiltasten oder Steuerkreuz-Richtungstasten mit Key.DirectionDown, Key.DirectionUp, Key.DirectionRight und Key.DirectionLeft.

@Test
fun arrowKeys_moveFocusTwoDimensionallyWithoutWrap() {
    composeTestRule.setContent {
        AppTheme {
            GridLayoutScreen()
        }
    }

    val topLeftButton = composeTestRule.onNodeWithTag("btn_top_left")
    val bottomLeftButton = composeTestRule.onNodeWithTag("btn_bottom_left")

    topLeftButton.performClick()
    topLeftButton.assertIsFocused()

    // Down arrow moves focus downward to bottom-left button
    topLeftButton.performKeyInput {
        pressKey(Key.DirectionDown)
    }
    bottomLeftButton.assertIsFocused()

    // Directional keys do not wrap around: pressing Down on bottom element stays focused
    bottomLeftButton.performKeyInput {
        pressKey(Key.DirectionDown)
    }
    bottomLeftButton.assertIsFocused()
}

Fokusverhalten von Textfeldern testen

Prüfen Sie, ob der Fokus bei einzeiligen Textfeldern mit der Tabulatortaste weitergegeben wird, während er bei mehrzeiligen Textfeldern beibehalten wird:

@Test
fun textField_handlesTabAccordingToLineLimits() {
    composeTestRule.setContent {
        AppTheme {
            FormScreen()
        }
    }

    val singleLineNode = composeTestRule.onNodeWithTag("single_line_field")
    val multiLineNode = composeTestRule.onNodeWithTag("multi_line_field")
    val submitButtonNode = composeTestRule.onNodeWithTag("submit_button")

    // Single-line field advances focus on Tab
    singleLineNode.performClick()
    singleLineNode.assertIsFocused()
    singleLineNode.performKeyInput { pressKey(Key.Tab) }
    multiLineNode.assertIsFocused()

    // Multi-line field keeps focus and inserts '\t'
    multiLineNode.performKeyInput { pressKey(Key.Tab) }
    multiLineNode.assertIsFocused()
    submitButtonNode.assertIsNotFocused()

    // Shift + Tab escapes multi-line field to previous target
    multiLineNode.performKeyInput {
        withKeyDown(Key.ShiftLeft) { pressKey(Key.Tab) }
    }
    singleLineNode.assertIsFocused()
}