Automate taking screenshots of Android app with Jetpack Compose
Piotr Chmielowski
Posted on February 4, 2022
In the article I'll show how to automate taking screenshots of the Android application written in Jetpack Compose.
1. Create test
Start with setting up instrumented test. This test will not check anything, it will only perform actions such clicking button and take screenshots of the application.
First, set up testing in app/build.gradle
:
android {
// other properties
defaultConfig {
// other properties
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // Add this line
}
}
// ...
dependencies {
// other dependencies
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" // Add this line
}
Now, let's suppose this is the Activity we want to take screenshots of:
package com.example
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Scaffold {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(16.dp),
) {
var greetingVisible by remember { mutableStateOf(false) }
if (greetingVisible) {
Text("Hello!")
}
Button(onClick = { greetingVisible = true }) {
Text("Show greeting")
}
}
}
}
}
}
Create the test in androidTest
directory:
package com.example
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ScreenshotTest {
@get:Rule
val rule = createAndroidComposeRule<MainActivity>()
@Test
fun makeScreenshot() {
// TODO: Take screenshot before clicking button
rule
.onNodeWithText("Show greeting")
.performClick()
// TODO: Take screenshot after clicking button
}
}
2. Take the screenshot
In order to take screenshot and save it as an image, use the following functions:
private fun ComposeContentTestRule.takeScreenshot(file: String) {
onRoot()
.captureToImage()
.asAndroidBitmap()
.save(file)
}
private fun Bitmap.save(file: String) {
val path = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.canonicalPath
FileOutputStream("$path/$file").use { out ->
compress(Bitmap.CompressFormat.PNG, 100, out)
}
}
Here is the full test which takes screenshots of the app:
package com.example
import android.graphics.Bitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.FileOutputStream
@RunWith(AndroidJUnit4::class)
class ScreenshotTest {
@get:Rule
val rule = createAndroidComposeRule<MainActivity>()
@Test
fun makeScreenshot() {
rule.takeScreenshot("before-click.png")
rule
.onNodeWithText("Show greeting")
.performClick()
rule.takeScreenshot("after-click.png")
}
}
private fun ComposeContentTestRule.takeScreenshot(file: String) {
onRoot()
.captureToImage()
.asAndroidBitmap()
.save(file)
}
private fun Bitmap.save(file: String) {
val path = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.canonicalPath
FileOutputStream("$path/$file").use { out ->
compress(Bitmap.CompressFormat.PNG, 100, out)
}
}
3. Save screenshots on the host PC
All screenshots taken by the code are stored in the emulator or phone.
To fetch them manually you can use Device File Explorer, which is build in Android Studio. Screenshots can be found in the following directory:
/data/data/$applicationId/files/
This process can be automated by ADB command:
adb root
adb pull /data/data/com.example/files/ screenshots
Extra information
Turn off animations
In some cases turning off animations is needed to run tests. To do it, use these commands:
adb shell settings put global window_animation_scale 0
adb shell settings put global animator_duration_scale 0
adb shell settings put global transition_animation_scale 0
Some users have reported that their devices need to be reboot to apply these settings. If that's your case, you can use the following command:
adb shell "su 0 am start -a android.intent.action.REBOOT"
Turn off keyboard
Soft keyboard can affect screenshots by cropping them. To disable it use the following commands:
# Show all keyboards (-a for all, -s for short summary).
adb shell ime list -s -a
# Disable keyboards:
adb shell ime disable <<keyboard id>>
# Example: adb shell ime disable com.android.inputmethod.latin
Posted on February 4, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.