Relax is a lightweight wrapper around Android's UI Automator library. It helps write clear and concise UI tests:
Relax("com.example.myapp") {
pressHome()
launch()
inputText("[email protected]", "id/email")
inputText("v3ry_s3cur3", "id/password")
click("Login")
assertExists {
textStartsWith("Welcome")
}
}
Rather than:
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.pressHome()
device.wait(Until.hasObject(By.pkg(device.launcherPackageName).depth(0)), 5_000)
val context = ApplicationProvider.getApplicationContext<Context>()
val packageName = "com.example.myapp"
val intent = context.packageManager.getLaunchIntentForPackage(packageName)!!
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
device.wait(Until.hasObject(By.pkg(packageName).depth(0)), 5_000)
device.findObject(UiSelector().resourceId("$packageName:id/email")).text = "[email protected]"
device.findObject(UiSelector().resourceId("$packageName:id/password")).text = "v3ry_s3cur3"
device.findObject(UiSelector().text("Login")).click()
device.waitForIdle()
assertTrue(device.findObject(UiSelector().textStartsWith("Welcome")).exists())
In your project's build.gradle
:
dependencies {
androidTestImplementation("com.emergetools.test:relax:0.1.0")
// or use implementation if in test module
}
Relax allows you to build UI test flows as a sequence of actions:
Relax("<your app's package name>") {
// Arbitrary actions
}
Launch the application:
launch()
Using a deeplink:
launchWithLink("custom://…")
Force-stop the application (requires com.android.test
module):
forceStop()
Clear all data associated with the application package (requires com.android.test
module):
clearData()
By exact text:
click("Text")
By id:
click("id/my_view_id")
By class:
click(Button::class.java)
Comprehensive selector:
click {
textContains("Milk")
checkable(true)
// …
}
On specific coordinates:
click(x, y)
To long click simply use longClick
instead of click
.
inputText("Ob-la-di", "Input Label")
inputText("ob-la-da", "id/input")
inputText("life goes on", EditText::class.java)
inputText("brah") {
// Arbitrary selectors
}
scrollForward("Text")
scrollBackward("id/list")
scrollToBeginning {
// Arbitrary selectors
}
scrollToEnd(maxSwipes = 2, "id/list")
swipeUp("Text")
swipeDown("id/carousel")
swipeLeft(CarouselView::class.java)
swipeRight {
// Arbitrary selectors
}
pressHome()
pressBack()
The same selectors as UiSelector
are available:
click { // Or any other action that accepts selectors
checkable(true)
checked(true)
child {
// Arbitrary child selectors
}
className(MyView::class.java)
className("com.example.myapp.MyView")
classNameMatches("regex")
description("description")
descriptionContains("cript")
descriptionMatches("regex")
descriptionStartsWith("desc")
enabled(true)
focusable(true)
focused(true)
index(0)
instance(0)
longClickable(true)
packageName("com.example.myapp")
packageNameMatches("regex")
parent {
// Arbitrary parent selectors
}
resId("my_id")
resIdMatches("regex")
scrollable(true)
selected(true)
text("Text")
textContains("x")
textMatches("regex")
textStartsWith("Te")
}
assertExists("Text")
assertExists("id/my_view")
assertNotExists(MyView::class.java)
assertNotExists {
// Arbitrary selectors
}
By default if an object cannot be found or an action cannot be completed the test will fail. This behavior can be easily modified.
Making some actions optional:
optional {
inputText("SAVE10", "Coupon")
}
click("Purchase")
Ignoring all failures by default:
val config = FlowConfig(errorHandler = NoOpFlowErrorHandler)
Relax("id", config) {
// …
}
Custom error handler:
val errorHandler = object : FlowErrorHandler {
override fun onError(error: Throwable) {
// …
}
}
val config = FlowConfig(errorHandler = errorHandler)
Relax("id", config) {
// …
}
Relax is a lightweight wrapper around UI Automator, therefore it's easy to leverage the full feature set of UI Automator. For example:
Relax(/* … */) {
if (!device.isScreenOn) device.wakeUp()
launch()
click("Next")
device.takeScreenshot(storePath)
// …
}