Automate taking screenshots of Android app with Jetpack Compose
Tags: android, automation, screenshots, jetpackcompose
Published: 2022-02-04 13:23:17

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`:

```Groovy
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```kotlin
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:

```bash
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:

```bash
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:

```bash
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:

```bash
# 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
```