Capture & publish UI snapshots
Invoke the SDK from instrumented tests
Uploading to your UI gallery is straightforward. On your Bitmap
snapshot, call our Kotlin extension function uploadToAppViewer()
, passing an initialized ScreenshotRecorder
, a unique state id and a descriptive subtitle that appears in your UI gallery.
onView(isRoot()).captureToBitmap().uploadToAppViewer(avRecorder, "state-id", "My subtitle")
We capture the screenshot here with the captureToBitmap()
function of Espresso's core
artifact, but you could do it any way you like.
Your test class might look like the following:
@RunWith(AndroidJUnit4::class)
class MyLoginUiTest {
@get:Rule
val activityRule = activityScenarioRule<LoginActivity>()
companion object {
@JvmStatic
private lateinit var recorder: ScreenshotRecorder
@BeforeClass
@JvmStatic
fun start() {
recorder = new ScreenshotRecorder();
recorder.init("MY-API-KEY", "MY-ACCOUNT-ID", "MY-APP-ID", BuildConfig.VERSION_CODE, BuildConfig.VERSION_NAME, "login-screen", "Login Screen");
}
@AfterClass
@JvmStatic
fun finishing() {
recorder.finishAndSubmit()
}
}
@Test
fun testingReturning() {
val activityScenario = activityRule.scenario
activityScenario.onActivity { activity: LoginActivity ->
// mocking UI state
activity.mViewModel.setUiModel(
LoginUiModel(returningUser = true, lastUsername = "AcmeStudi0", passwordHidden = true, enableButton = false, error = null)
)
}
onView(isRoot()).captureToBitmap().uploadToAppViewer(recorder, "known-user", "Known user coming back")
}
//...more tests
}
The API key, account id and app id can be found when signed in to Appviewer.io (click the 🔑 icon in the top nav bar).
Common best practices to avoid flakiness should be considered. Espresso for example suggests turning off system animations on the virtual or physical devices used for testing. To do this, go to Settings > Developer options, and disable
- Window animation scale
- Transition animation scale
- Animator duration scale
Inject recorded test data
To avoid having to manually code the test cases (i.e. your mocked UI states), you can record them with our Live Event Monitor feature and query them in your UI tests.
Record test data
Recording your test cases can be done the same way that you'd log any events or data for the Live Event Monitor (read here how). The Live Event Monitor acts as a web-based log viewer that allows everyone using a test version to verify and debug background behavior. And when you log data that you want to incl. in your tests (e.g. UI state in a LiveData exposed from a ViewModel), you can select relevant cases as test variants and download them later during your test runs. You simply save each relevant test variant under a common group id.
Download test data
In your instrumented tests use our SDK to query the test cases by the group id defined above:
val variants: List<TestDataFetcher.TestVariant> = recorder.fetchTestVariants("login-ui-model")
Deserialize test data
You might note that the TestVariant
holds the test data in its' variant
attribute as a single String. While a simple String might be sufficient in some cases, most of the time this will be a serialized object (that you serialized before logging for the Live Event Monitor). In those cases you will need to deserialize the data here first before using it in your test.
You can do this whatever way you prefer. With Kotlin the simplest way is to use the kotlinx.serialization library. Check the Live Event Monitor) for a serialization example. Deserialization would work like this:
val testCase: MyTestObject = Json.decodeFromString(testVariant.variant)
Inject test data with JUnitParams
It is up to you how you want to integrate the test data. But one suggestion is to use the JUnitParams library that allows to dynamically set test cases at run time. Note that you'd have to integrate this dependency yourself.
@RunWith(JUnitParamsRunner::class)
class MyLoginUiTest {
... // check above
@Test
@Parameters(method = "parametersToTestAdd") // test will run for each test case in the array returned by the method parametersToTestAdd
fun testUiModel(model: String, id: String, label: String, metaFields: Map<String, Any>?) {
val uiModel: LoginUiModel = Json.decodeFromString(model)
val activityScenario = activityRule.scenario
activityScenario.onActivity { activity: LoginActivity ->
activity.mViewModel.setUiModel(uiModel)
}
onView(isRoot()).captureToBitmap().uploadToAppViewer(recorder, id, label, metaFields)
}
private fun parametersToTestAdd(): Array<Any> {
val variants: List<TestDataFetcher.TestVariant> = recorder.fetchTestVariants("login-ui-model")
return variants.map { testVariant -> arrayOf(testVariant.variant, testVariant.id, testVariant.label, testVariant.meta) }.toTypedArray()
}
}
The testVariant.meta
data allows Appviewer to link the recorded test data with your captured UI scenario.