Blog Infos
Author
Published
Topics
,
Author
Published
Topics
,
Posted by: Partyk Kosieradzki

See the original article on my website:
https://patrykkosieradzki.com/easy-ui-and-screenshot-testing-on-android/

Ways of testing on Android

Every good Android application should be well tested to minimize the risk of error after releasing it to the world. The most basic tests for any application are Unit Tests. You must write them to ensure that a particular part of the code is working.

On Android we have also Instrumentation Tests — tests that run on physical devices and emulators, and they can take advantage of the Android framework APIs and supporting APIs, such as AndroidX Test. One of many ways of using this mechanism is to test the UI and take screenshots of every screen in our app. I really like this, because if you run this UI/Screenshot Test every time before creating a new pull request, you can easily check if you didn’t change something in the UI by mistake.

How to capture device screenshots?

I’ve worked on a few projects that used screenshot testing and these test were always dependent on external libraries like Karumi Shot or Facebook Screenshot Tests For Android. These libraries were honestly a pain in the ass, completely impossible to set up and use in a long term. One guy from my project spent a few days to manually fix some code inside the library to make it work on Windows 10. I decided that this was too much for a simple operation like taking a device screenshot, so I began to research for a better solution.

What did I find?

Well, first of all, Android 4.3 is a minimum system version we need to have in our project, because we are gonna be using UI Automator under the hood. The solution I found is simply… using Android Support Test Library. This library contains a Screenshot class and capture() method that we’re gonna need.

This is great, because all of the screenshots can be taken, even if, for example, the Activity/Fragment is not currently displayed on the screen or a system dialog is in the foreground.

As I said, we’re gonna need the Screenshot.capture() method:

Creates a ScreenCapture that contains a Bitmap of the visible screen content for Build.VERSION_CODES.JELLY_BEAN_MR2 and above.

Note: Only use this method if all your tests run on API versions Build.VERSION_CODES.JELLY_BEAN_MR2 or above. If you need to take screenshots on lower API levels, you need to use capture(Activity) or capture(View) for those versions.

Processing the screenshot

In order to process the screenshot, we have to use two things: ScreenCapture and ScreenCaptureProcessor.

There is a default implementation called BasicScreenCaptureProcessor, which does the following:

A basic ScreenCaptureProcessor for processing a ScreenCapture.

This will perform basic processing on the given ScreenCapture such as saving to the public Pictures directory, given by android.os.Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES), with a simple name that includes a few characteristics about the device it was saved on followed by a UUID.

This API is currently in beta.

Taking a simple screenshot

At this point, if you were to take a simple screenshot, you’d just have to use a function like this:

private const val TAG = "ScreenshotsUtils"
fun takeScreenshot(screenShotName: String) {
Log.d(TAG, "Taking screenshot of '$screenShotName'")
val screenCapture = Screenshot.capture()
try {
screenCapture.apply {
name = screenShotName
process()
}
Log.d(TAG, "Screenshot taken")
} catch (ex: IOException) {
Log.e(TAG, "Could not take a screenshot", ex)
}
}

WARNING

Make sure you have WRITE_EXTERNAL_STORAGE permission added to the manifest (adding it only for debug build is fine). If you run on API 23+ (Marshmallow), you will also need to have those permissions granted before running the test.

You can “hack” this, by using installOptions in AGP (Android Gradle Plugin). Just put this in your build.gradle file:

android {
// The rest of you build.gradle
adbOptions {
installOptions '-g', '-r'
}
}
view raw build.gradle hosted with ❤ by GitHub
Custom ScreenCaptureProcessor

If you want to set a custom name for your screenshots and save to a specific location, rather than default “screenshots” folder under “Picture” on the device. Since you could use other apps on the same device, it’ll be better to set up a specific folder on the device. I wanted to make it event better, so I made an implementation that also takes into account flavors and build types:

class MyScreenCaptureProcessor : BasicScreenCaptureProcessor() {
init {
this.mDefaultScreenshotPath = File(
File(
getExternalStoragePublicDirectory(DIRECTORY_PICTURES),
"${BuildConfig.APPLICATION_ID}/${BuildConfig.BUILD_TYPE}"
).absolutePath,
SCREENSHOTS_FOLDER_NAME
)
}
override fun getFilename(prefix: String): String = prefix
companion object {
const val SCREENSHOTS_FOLDER_NAME = "screenshots"
}
}

Now, in order to take screenshots we have to update the takeScreenshot(…) function a little:

private const val TAG = "ScreenshotsUtils"
fun takeScreenshot(screenShotName: String) {
Log.d(TAG, "Taking screenshot of '$screenShotName'")
val screenCapture = Screenshot.capture()
val processors = setOf(MyScreenCaptureProcessor())
try {
screenCapture.apply {
name = screenShotName
process(processors)
}
Log.d(TAG, "Screenshot taken")
} catch (ex: IOException) {
Log.e(TAG, "Could not take a screenshot", ex)
}
}

Job Offers

Job Offers


    Android Developer

    Yoti Ltd
    Anywhere
    • Full Time
    apply now

    Senior Android Engineer – Big Release Team

    Zalando SE
    Berlin
    • Full Time
    apply now

    Delivery Lead / Scrum Master (m/w/d)

    Deutsche Post IT Services (Berlin) GmbH
    Berlin
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

, ,

Demystifying the UX of Compose Guidance

Guidance is one of the key factors for developers to learn and onboard to using Jetpack Compose. In this talk, we will share the exclusive design work Android Developer UX (ADUX) team has been leading to make the documentation and guidance materials for Compose more usable and approachable.
READ MORE

Jobs

Android Test Orchestrator

The next thing we have to do is to protect our UI tests against test crashes. Normally if you run a lot of tests and one of them crashes it will stop the rest of the remaining tests. If you want to avoid it then you have to use Android Test Orchestrator in your project.

A simple configuration for Android Test Orchestrator looks like this:

android {
defaultConfig {
...
testInstrumentationRunnerArguments clearPackageData: 'true'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
testOptions {
execution 'ANDROID_TEST_ORCHESTRATOR'
}
}
dependencies {
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestUtil 'com.android.support.test:orchestrator:1.0.1'
}
view raw build.gradle hosted with ❤ by GitHub
Turning off the animations

Another wise thing to do is to turn off the animations during tests. This setting could prevent unwanted lags and can speed up the tests. To do that, you just have to add animationsDisabled property to the build.gradle:

android {
testOptions {
...
animationsDisabled = true
}
}
view raw build.gradle hosted with ❤ by GitHub
Retrieve screenshots from the device

If we run our tests now then we should see screenshots saved on the device. Great! But how do we pull them from the device to a specific directory on our PC?

To solve this we have to create a few Gradle Tasks

  • First one for creating screenshots directory
  • Second one for fetching screenshots
  • And the last one for clearing screenshots from the device after fetching

Fetching your screenshots will be done automatically, right after connectedDebugAndroidTest Gradle Task. To make this work, add this to your build.gradle:

def appId = "com.patrykkosieradzki.moviebox"
android {
...
defaultConfig {
...
applicationId appId
}
}
def projectScreenshotsDirectory = "$projectDir/screenshots"
def deviceScreenshotsDirectory = '/sdcard/Pictures/' + appId + '/debug/screenshots'
def clearScreenshotsTask = task('clearScreenshots', type: Exec) {
println deviceScreenshotsDirectory
executable "${android.getAdbExe().toString()}"
args 'shell', 'rm', '-r', deviceScreenshotsDirectory
}
def createScreenshotDirectoryTask = task('createScreenshotDirectory', type: Exec, group: 'reporting') {
executable "${android.getAdbExe().toString()}"
args 'shell', 'mkdir', '-p', deviceScreenshotsDirectory
}
def fetchScreenshotsTask = task('fetchScreenshots', type: Exec, group: 'reporting') {
executable "${android.getAdbExe().toString()}"
args 'pull', deviceScreenshotsDirectory + '/.', projectScreenshotsDirectory
finalizedBy {
clearScreenshotsTask
}
dependsOn {
createScreenshotDirectoryTask
}
doFirst {
new File(projectScreenshotsDirectory).mkdirs()
}
}
tasks.whenTaskAdded { task ->
if (task.name == 'connectedDebugAndroidTest') {
task.finalizedBy {
fetchScreenshotsTask
}
}
}
view raw build.gradle hosted with ❤ by GitHub

Now, to run tests and fetch screenshots, just execute the following in you project’s directory:

./gradlew connectedDebugAndroidTest

You should see a new folder created right after, called “screenshots”.

If you want to learn more about ADB commands, you learn a lot here: https://developer.android.com/studio/command-line/adb

Show me the Github Repository

To sum it up I’ve created a simple movie app on Github that you can find here:

k0siara/AndroidMovieBox

Simple Android App written in Kotlin, using MVI and Clean Architecture to manage movie info from…

github.com

 

That’s all folks. Hope this helps you with building a good, well tested Android app. As always, if you have any questions you can either write a comment or message me directly. Bye and happy testing! 😀

Tags: Android Testing, Screenshot Testing, Android, Kotlin, Mobile Development

 

View original article at:


Originally published: May 01, 2021

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
I recently found a bug that would cause a crash in all the apps…
READ MORE
blog
Mobile device concept is one step ahead of the trend in technology, bringing an…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu