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 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.
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.
Alright, we've written a test for touch/pointer input navigation. Next, we want to write tests for keyboard navigation.
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
:
chart.performKeyInput {
pressKey(Key.DirectionRight)
}
Key.DirectionRight
is the key for the right-pointing arrow. For the test, we want to first navigate forward some years, assert that the position is correct, and then navigate back and assert the year:
@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
Links in the Blog Post
Related Blog Posts
- Accessibility Tests in Compose - Name, Role, Value
- More Accessible Graphs with Jetpack Compose Part 1: Adding Content Description
- More Accessible Graphs with Jetpack Compose Part 2: Adding Keyboard Interaction
- More Accessible Graphs with Jetpack Compose Part 3: Differentiating without Color
- More Accessible Graphs with Jetpack Compose Part 4: On-Screen Control Buttons
Top comments (0)