Automate taking screenshots of Android app with Jetpack Compose

Automate taking screenshots of Android app with Jetpack Compose

Steve Author: Steve Date: 2022-08-11
Tags:
Automate taking screenshots of Android app with Jetpack Compose

    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
    }
    
    Enter fullscreen mode Exit fullscreen mode

    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")
                        }
                    }
                }
            }
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    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
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    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)
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    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)
        }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    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
    
    Enter fullscreen mode Exit fullscreen mode

    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
    
    Enter fullscreen mode Exit fullscreen mode

    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"
    
    Enter fullscreen mode Exit fullscreen mode

    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
    
    Enter fullscreen mode Exit fullscreen mode