Testing Different Navigation Options with Compose
Testing Different Navigation Options with Compose ź“ė Ø
One part of creating accessible Android apps is to provide alternative navigation options. Some examples include touch (or pointer) input, keyboard navigation, switch navigation, and screen reader navigation. But how can you write tests for these different ways of navigation?
In this blog post, Iāll share some examples of how to do that. Iām using an old demo project about making graphs more accessible and demonstrating how to write tests for the different elements Iāve explained with that demo project.
About the Code Weāre Using
As mentioned, Iām using an old demo project as the basis for the tests. In short, it contains a graph displaying data and is navigable with touch input, keyboard, switch device, and screen reader. The additional buttons for changing the highlighted sections in the chart also work for someone who has, for example, tremors in their hands or reduced dexterity.
If you want to learn more about how I built the UI and the reasons behind the decisions, Iāve added links to all the blog posts in theĀ Related Blog Posts (proandroiddev
)Ā section.
Alright, letās get to writing tests!
Setting Up The Tests
Letās first set up the tests by creating a test class in theĀ androidTest
-package, definingĀ composeTestRule
, and adding a setup function that runs before each test:
class GraphScreenTest {
@get:Rule
val composeTestRule = createComposeRule()
@Before
fun setupTests() {
composeTestRule.setContent {
GraphExampleTheme {
GraphScreen()
}
}
}
}
Another part of the setup phase is deciding how we will retrieve the elements we use for testing. In this case, I decided to use test tags for simplicity, and Iāve defined aĀ TestTags
-object for sharing between the UI and tests. This solution is straightforward and might not be your choice in a production app, but as this is a demo, it uses the most explicit option.
You can findĀ all the changes from this blog post in this commit (eevajonnapanula/graph-accessibility-example
).
Touch Navigation
The first tests weāre going to write are about touch interaction. First, hereās a short video of how things work in the UI when a user uses their finger to drag over the chart:
So, when a user moves their pointer input around in the graph, a box with values appears in the bottom right corner of the UI, displaying the values for the selected year.
Letās first get the elements weāre going to use in the test:
@Test
fun touchInteractionsWorkCorrectly() {
val labels =
composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
val chart =
composeTestRule.onNode(hasTestTag(TestTags.chartTestTag))
}
Why these two? First, theĀ labels
Ā variable is the one weāre using to check if things work correctly. It contains the information that changes, so by checking the year, we can ensure that navigation works correctly. Second, the chart is the one weāre interacting with.
The actual tests look like this:
@Test
fun touchInteractionsWorkCorrectly() {
// ...
labels.assertIsNotDisplayed()
// Navigate forward
chart.performTouchInput {
swipeRight(startX = 0f, endX = 30f)
}
labels.assertIsDisplayed()
labels.onChildren().assertAny(hasText("2015"))
// Navigate forward
chart.performTouchInput {
swipeRight(startX = 30f, endX = 70f)
}
labels.onChildren().assertAny(hasText("2016"))
}
We first assert that the labels are not visible because thatās how the UI is before navigation actions. After that, we perform touch input by swiping right, asserting that the correct year (in this case, ā2015ā) is displayed in the labels component.
The numbers we use forĀ swipeRight
Ā are based on the code, and the 35-pixel swipe is still inside the area used in the code for deciding what year is shown. In the same way, the second swipe from 30 to 70 moves from the first year to the second year.
Keyboard Navigation
Alright, weāve written a test for touch/pointer input navigation. Next, we want to write tests for keyboard navigation.
The following video demonstrates what the keyboard navigation looks like on the graph when a user presses the ānextā button (right arrow in this case) to navigate forward in the graph:
To test the keyboard navigation, weāll need a similar setup as for the touch/pointer interactions:
@Test
fun keyboardNavigationWorksCorrectly() {
val labels =
composeTestRule.onNode(
hasTestTag(TestTags.labelsTestTag)
)
val chart =
composeTestRule.onNode(
hasTestTag(TestTags.chartTestTag)
)
labels.assertIsNotDisplayed()
}
For this test, we define the same variables (Ā labels
Ā andĀ chart
) and then assert that the labels component is not displayed.
Next, weāll need to perform some keyboard input actions. We can do that withĀ performKeyInputĀ andĀ pressKey
:
@Test
fun keyboardNavigationWorksCorrectly() {
// ...
// Navigate forward
chart.performKeyInput {
pressKey(Key.DirectionRight)
pressKey(Key.DirectionRight)
pressKey(Key.DirectionRight)
pressKey(Key.DirectionRight)
pressKey(Key.DirectionRight)
pressKey(Key.DirectionRight)
}
labels.onChildren().assertAny(hasText("2020"))
// Navigate back
chart.performKeyInput {
pressKey(Key.DirectionLeft)
pressKey(Key.DirectionLeft)
pressKey(Key.DirectionLeft)
}
labels.onChildren().assertAny(hasText("2017"))
}
These lines assert that the keyboard navigation works correctly. The last thing to test in the context of this blog post is the on-screen button navigation.
Navigation Using On-Screen Buttons
The previous videos didnāt display the on-screen buttons because they were recorded before adding them. Hereās a video with the buttons and how the navigation works:
So, to test, again, weāll have similar ā but not exactly the same! ā setup:
@Test
fun buttonNavigationWorksCorrectly() {
val labels =
composeTestRule.onNode(hasTestTag(TestTags.labelsTestTag))
val leftButton =
composeTestRule.onNode(hasTestTag(TestTags.leftButtonTestTag))
val rightButton =
composeTestRule.onNode(hasTestTag(TestTags.rightButtonTestTag))
labels.assertIsNotDisplayed()
}
This time, besides getting the labels, we donāt need the chart component at all. Instead, weāll get a reference to both buttons on the screen.
Next, we want to navigate forward by clicking the button and asserting that the year on the label is correct. After that, we do some forward and backward navigation to ensure both buttons work correctly:
@Test
fun buttonNavigationWorksCorrectly() {
// ...
// Navigate forward
rightButton.performClick()
labels.assertIsDisplayed()
labels.onChildren().assertAny(hasText("2015"))
// Navigate forward
rightButton.performClick()
rightButton.performClick()
rightButton.performClick()
rightButton.performClick()
// Navigate back
leftButton.performClick()
leftButton.performClick()
labels.onChildren().assertAny(hasText("2017"))
}
And this is how we can test the on-screen button navigation in the graph.
Wrapping Up
In this blog post, weāve written tests for three different navigation styles: Touch/pointer input, keyboard navigation, and on-screen button navigation. This way, weāve tested that users using different navigation methods and tools can use the app.
Do you test for these interactions and navigation alternatives? If so, do you have any tips on testing them?
Links in the Blog Post
Related Blog Posts
Info
This article is previously published on proandroiddev