Add UI automation tests to Trail-Sense Android app using AutomationLibrary...
Create UI automation tests for Trail Sense tools using AutomationLibrary and ToolTestBase.
package com.kylecorry.trail_sense.tools.<toolname>
import com.kylecorry.trail_sense.R
import com.kylecorry.trail_sense.test_utils.AutomationLibrary.*
import com.kylecorry.trail_sense.test_utils.TestUtils
import com.kylecorry.trail_sense.test_utils.TestUtils.back
import com.kylecorry.trail_sense.test_utils.TestUtils.clickListItemMenu
import com.kylecorry.trail_sense.test_utils.ToolTestBase
import com.kylecorry.trail_sense.test_utils.views.*
import com.kylecorry.trail_sense.tools.tools.infrastructure.Tools
import org.junit.Test
class Tool<Name>Test : ToolTestBase(Tools.<TOOL_ID>) {
@Test
fun verifyBasicFunctionality() {
hasText(R.id.title, string(R.string.tool_title))
canCreateItem()
canEditItem()
canDeleteItem()
verifyQuickAction()
}
private fun canCreateItem() { /* ... */ }
private fun canEditItem() { /* ... */ }
private fun canDeleteItem() { /* ... */ }
private fun verifyQuickAction() { /* ... */ }
}
Location: app/src/androidTest/java/com/kylecorry/trail_sense/tools/<toolname>/Tool<Name>Test.kt
Tool*Test.kt files. This step is complete when the tool id, package, primary screen title, and existing test patterns are known.Prefer text-based selection for most interactions. Use IDs only when necessary.
// Clicking tabs, buttons, menu items, options
click(string(R.string.distance))
click(string(R.string.delete))
click("High")
click("Test Group")
// Verifying text anywhere on screen
hasText(string(R.string.no_paths))
hasText("Test Path")
// Dialog inputs (by label/hint)
input(string(R.string.name), "My Item")
input(string(R.string.distance), "1.0")
// Checkbox state by label
isChecked(string(R.string.tide_clock))
// Title bars (for verification)
hasText(R.id.paths_title, string(R.string.paths))
hasText(R.id.tide_title, "Tide 1")
// Add/play buttons (no text label)
click(R.id.add_btn)
click(R.id.play_btn)
// Specific input fields
input(R.id.searchbox, "query")
input(R.id.tide_name, "Tide 1")
input(R.id.utm, "42, -72")
// Result/data views
hasText(R.id.result, "3.2808 ft")
hasText(R.id.total_percent_packed, "50% packed")
// Charts and special views
isVisible(R.id.chart)
scrollToEnd(R.id.scroll_view)
// Toolbar buttons
click(toolbarButton(R.id.paths_title, Side.Right))
List items use Andromeda library IDs:
click(com.kylecorry.andromeda.views.R.id.title)
hasText(com.kylecorry.andromeda.views.R.id.title, "Item Name")
hasText(com.kylecorry.andromeda.views.R.id.description, "Details")
click(com.kylecorry.andromeda.views.R.id.checkbox)
click(com.kylecorry.andromeda.views.R.id.trailing_icon_btn)
click(com.kylecorry.andromeda.views.R.id.menu_btn)
private fun canCreateItem() {
click(R.id.add_btn)
click(string(R.string.new_item))
input(string(R.string.name), "Test Item")
clickOk()
hasText("Test Item")
}
private fun canEditItem() {
clickListItemMenu(string(R.string.edit))
input("Test Item", "Test Item 2")
clickOk()
hasText("Test Item 2")
}
private fun canDeleteItem() {
clickListItemMenu(string(R.string.delete))
clickOk()
not { hasText("Test Item 2", waitForTime = 0) }
}
click(toolbarButton(R.id.title, Side.Right))
click(string(R.string.export))
private fun verifyQuickAction() {
TestUtils.openQuickActions()
click(quickAction(Tools.QUICK_ACTION_ID))
// Verify action occurred
TestUtils.closeQuickActions()
}
optional {
hasText(string(R.string.disclaimer))
clickOk()
}
backUntil { isVisible(R.id.paths_title, waitForTime = 1000) }
| Function | Usage |
|---|---|
click(text) |
Click by text (contains match) |
click(text, exact = true) |
Click by exact text |
click(R.id.x) |
Click by ID |
hasText(text) |
Verify text on screen |
hasText(R.id.x, text) |
Verify text in view |
hasText(R.id.x, Regex(...)) |
Verify regex pattern |
input(R.id.x, text) |
Enter text by ID |
input(label, text) |
Enter text by label |
clickOk() |
Click OK in dialogs |
clickListItemMenu(label) |
Click list item overflow menu |
optional { } |
Ignore failures |
not { } |
Assert action fails |
string(R.string.x) |
Get string resource |
scrollUntil { } |
Scroll until condition |
backUntil { } |
Press back until condition |
Emulator sensors and the clock are not predictable, so regex-only assertions (\d+ ft) can't catch wrong values. Prefer known answers:
ToolTestBase(Tools.X, Coordinate(42.03, -71.97))seedData(), using TestData (addBeacon, addPathFromGpx, addPressureTrend, ...). Guard with assumeFalse(isStagingBuild) since staging builds can't be seeded.relaunchTool { TestData.add...() } (the tool is closed while seeding, so background updates can't race it).TestUtils.pickDate) instead of "today".Helpers for checking values: hasDataPoint(value, label) (a stat tile) and hasTextsInOrder(id, texts) (list ordering). hasText(String) can't match across a leading newline, use a Regex for multi-line values.
Canvas content (the map) can't be found by UiAutomator. Convert a coordinate to a screen pixel with the view's projection inside scenario.onActivity and tap that point (see ToolMapTest).
./scripts/run-emulator-integration-tests.sh
./scripts/run-emulator-integration-tests.sh com.kylecorry.trail_sense.tools.notes.ToolNotesTest 180
The filter only supports a single class or method (a comma separated list runs just the first class), so run each class separately. The script defaults to a 1800 second timeout. Most individual integration tests should finish in 60 to 180 seconds, so pass a shorter timeout for focused runs when practical.
For complete API details and additional functions, read:
app/src/androidTest/java/com/kylecorry/trail_sense/test_utils/AutomationLibrary.ktapp/src/androidTest/java/com/kylecorry/trail_sense/test_utils/TestUtils.ktapp/src/androidTest/java/com/kylecorry/trail_sense/test_utils/views/For example tests, see existing tool tests in:
app/src/androidTest/java/com/kylecorry/trail_sense/tools/*/Tool*Test.kt