포커스 탐색 테스트

포커스 탐색 테스트를 자동화하면 앱이 하드웨어 키보드, D패드, 접근성 도구에서 일관되고 예측 가능한 사용자 환경을 제공할 수 있습니다. Compose는 키 입력을 시뮬레이션하고 포커스 상태를 확인하는 기본 제공 테스트 API를 제공합니다.

포커스 테스트 설정

포커스 테스트를 작성하려면 Compose 테스트 라이브러리에서 createComposeRule()를 사용하세요.

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

포커스 타겟 확인

클릭을 실행하거나 포커스를 요청한 다음 assertIsFocused() 및 assertIsNotFocused()를 사용하여 상태를 어설션하여 UI 요소가 포커스를 받을 수 있는지 확인할 수 있습니다.

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

일차원 포커스 순회 테스트

performKeyInput를 사용하여 Tab 및 Shift+Tab 키 입력을 시뮬레이션하여 포커스가 외관 순서대로 UI를 통해 이동하는지 확인합니다. 권장사항에 따라 양방향 순회 무결성을 보장하기 위해 순방향 탐색 (Key.Tab)과 역방향 탐색(Key.ShiftLeft을 누른 상태에서 Key.Tab)을 모두 확인하세요.

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

2차원 방향 탐색 테스트

Key.DirectionDown, Key.DirectionUp, Key.DirectionRight, Key.DirectionLeft를 사용하여 화살표 키 또는 D패드 방향 키를 시뮬레이션합니다.

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

텍스트 필드 포커스 동작 테스트

단일 줄 텍스트 필드는 Tab에서 포커스를 이동하고 다중 줄 텍스트 필드는 포커스를 유지하는지 확인합니다.

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