# Instrumentation tests usage

These examples use [pedrovgs/Shot](https://github.com/pedrovgs/Shot). It also works with any other on-device screenshot testing library that supports *ActivityScenarios*, like&#x20;

* Facebook [screenshot-tests-for-android](https://github.com/facebook/screenshot-tests-for-android)
* Dropbox [Dropshots](https://github.com/dropbox/dropshots)&#x20;
* Other custom screenshot testing solution based on *ActivityScenarios/FragmentScenarios*.&#x20;

{% hint style="info" %}
For Android-Testify, which does not support *ActivityScenario* but the deprecated *ActivityTestRule*, have a look at the[ Activity-Testify usage](/androiduitestingutils/usage/instrumentation-tests-usage/android-testify-usage.md) section.
{% endhint %}

You can find more complete examples with Shot, Dropshots, Roborazzi and Android-Testify in the [Android screenshot testing playground](https://github.com/sergio-sastre/Android-screenshot-testing-playground) repo.

## Activity

The simplest way is to use the **ActivityScenarioForActivityRule**, to avoid the need for closing the *ActivityScenario*.

```kotlin
@get:Rule
val screenshotRule =
    activityScenarioForActivityRule<MyActivity>(
        config = ActivityConfigItem(
            orientation = Orientation.LANDSCAPE,
            uiMode = UiMode.NIGHT,
            fontSize = FontSize.LARGEST,
            systemLocale = "en",
            displaySize = DisplaySize.LARGEST,
            fontWeight = FontWeight.BOLD,
        )
    )

@Test
fun snapActivityTest() {
    compareScreenshot(
        activity = screenshotRule.activity,
        name = "your_unique_screenshot_name",
    )
}
```

In case you don't want to/cannot use the rule, you can use  **ActivityScenarioConfigurator.ForActivity()** directly in the test.&#x20;

Apart from that, this would be equivalent

```kotlin
// Sets the Locale of the Android system
@get:Rule(order = 0)
val systemLocale = SystemLocaleTestRule("en")

@get:Rule(order = 1)
val fontWeight = FontWeightTestRule(FontWeight.BOLD)

@get:Rule(order = 2)
val fontSize = FontSizeTestRule(FontSizeScale.Value(1.75f))

@get:Rule(order = 3)
val displaySize = DisplaySizeTestRule(DisplaySize.LARGEST)

@get:Rule(order = 4)
val uiMode = UiModeTestRule(UiMode.NIGHT)

@Test
fun snapActivityTest() {
    // AppLocale, SystemLocale, FontSize & DisplaySize are only supported via TestRules for Activities
    val activityScenario = ActivityScenarioConfigurator.ForActivity()
        .setOrientation(Orientation.LANDSCAPE)
        .launch(MyActivity::class.java)

    val activity = activityScenario.waitForActivity()

    compareScreenshot(
        activity = activity,
        name = "your_unique_screenshot_name"
    )

    activityScenario.close()
}
```

## Fragment

The simplest way is to use the **fragmentScenarioConfiguratorRule**

```kotlin
@get:Rule
val screenshotRule = fragmentScenarioConfiguratorRule<MyFragment>(
        fragmentArgs = bundleOf("arg_key" to "arg_value"),
        config = FragmentConfigItem(
            orientation = Orientation.LANDSCAPE,
            uiMode = UiMode.DAY,
            locale = "de",
            theme = R.style.Custom_Theme,
            fontSize = FontSize.SMALL,
            displaySize = DisplaySize.SMALL,
            fontWeight = FontWeight.BOLD,
        ),
    )

@Test
fun snapFragment() {
    compareScreenshot(
        fragment = screenshotRule.fragment,
        name = "your_unique_screenshot_name",
    )
}
```

In case you don't want to/cannot use the rule, you can use the plain **FragmentScenarioConfigurator**. This would be its equivalent:

```kotlin
@Test
fun snapFragment() {
    val screenshotRule =
        FragmentScenarioConfigurator
            .setInitialOrientation(Orientation.LANDSCAPE)
            .setUiMode(UiMode.DAY)
            .setLocale("de")
            .setTheme(R.style.Custom_Theme)
            .setFontSize(FontSize.SMALL)
            .setDisplaySize(DisplaySize.LARGE)
            .setFontWeight(FontWeight.BOLD)
            .launchInContainer<MyFragment>(
                fragmentArgs = bundleOf("arg_key" to "arg_value"),
            )

    compareScreenshot(
        fragment = screeenshotRule.waitForFragment(),
        name = "your_unique_screenshot_name",
    )

    screenshotRule.close()
}
```

## Android View

The simplest way is to use the **ActivityScenarioForViewRule**, to avoid the need for closing the ActivityScenario.

```kotlin
@get:Rule
val rule =
    ActivityScenarioForViewRule(
        config = ViewConfigItem(
            fontSize = FontSize.NORMAL,
            locale = "en",
            orientation = Orientation.PORTRAIT,
            uiMode = UiMode.DAY,
            theme = R.style.Custom_Theme,
            displaySize = DisplaySize.SMALL,
            fontWeight = FontWeight.BOLD,
        ),
        backgroundColor = TRANSPARENT,
    )

@Test
fun snapViewHolderTest() {
    // IMPORTANT: The rule inflates a layout inside the activity with its context to inherit the configuration 
    val layout = rule.inflateAndWaitForIdle(R.layout.your_view_holder_layout)

    // wait asynchronously for layout inflation 
    val viewHolder = waitForMeasuredViewHolder {
        YourViewHolder(layout).apply {
            // bind data to ViewHolder here
            ...
        }
    }

    compareScreenshot(
        holder = viewHolder,
        heightInPx = viewHolder.itemView.height,
        name = "your_unique_screenshot_name",
    )
}
```

In case you don't want to/cannot use the rule, you can use **ActivityScenarioConfigurator.ForView()**. This would be its equivalent:

```kotlin
// example for ViewHolder
@Test
fun snapViewHolderTest() {
    val activityScenario =
        ActivityScenarioConfigurator.ForView()
            .setLocale("en")
            .setInitialOrientation(Orientation.PORTRAIT)
            .setUiMode(UiMode.DAY)
            .setTheme(R.style.Custom_Theme)
            .setFontSize(FontSize.NORMAL)
            .setDisplaySize(DisplaySize.SMALL)
            .setFontWeight(FontWeight.BOLD)
            .launchConfiguredActivity(TRANSPARENT)

    val activity = activityScenario.waitForActivity()

    // IMPORTANT: To inherit the configuration, inflate layout inside the activity with its context 
    val layout = activity.inflateAndWaitForIdle(R.layout.your_view_holder_layout)

    // wait asynchronously for layout inflation 
    val viewHolder = waitForMeasuredViewHolder {
        YourViewHolder(layout).apply {
            // bind data to ViewHolder here
            ...
        }
    }

    compareScreenshot(
        holder = viewHolder,
        heightInPx = viewHolder.itemView.height,
        name = "your_unique_screenshot_name",
    )

    activityScenario.close()
}
```

## Jetpack Compose

The simplest way is to use the **ActivityScenarioForComposableRule**, to avoid the need for:

1. calling createEmptyComposeRule()
2. closing the ActivityScenario.

```kotlin
@get:Rule
val screenshotRule = ActivityScenarioForComposableRule(
        config = ComposableConfigItem(
            orientation = Orientation.PORTRAIT,
            locale = "de",
            uiMode = UiMode.DAY,
            fontSize = FontSize.SMALL,
            displaySize = DisplaySize.LARGE,
            fontWeight = FontWeight.BOLD,
        ),
        backgroundColor = TRANSPARENT,
    )

@Test
fun snapComposableTest() {
    screenshotRule.activityScenario
        .onActivity {
            it.setContent {
                AppTheme { // this theme must use isSystemInDarkTheme() internally
                    yourComposable()
                }
            }
        }

    compareScreenshot(
        rule = rule.composeRule,
        name = "your_unique_screenshot_name",
    )
}
```

In case you don't want to/cannot use the rule, you can use **ActivityScenarioConfigurator.ForComposable()** together with **createEmptyComposeRule()**. This would be its equivalent:

```kotlin
// needs an EmptyComposeRule to be compatible with ActivityScenario
@get:Rule
val composeTestRule = createEmptyComposeRule()

@Test
fun snapComposableTest() {
    val activityScenario = ActivityScenarioConfigurator.ForComposable()
        .setFontSize(FontSize.SMALL)
        .setLocale("de")
        .setInitialOrientation(Orientation.PORTRAIT)
        .setUiMode(UiMode.DAY)
        .setDisplaySize(DisplaySize.LARGE)
        .setFontWeight(FontWeight.BOLD)
        .launchConfiguredActivity(TRANSPARENT)
        .onActivity {
            it.setContent {
                AppTheme { // this theme must use isSystemInDarkTheme() internally
                    yourComposable()
                }
            }
        }

    activityScenario.waitForActivity()

    compareScreenshot(rule = composeTestRule, name = "your_unique_screenshot_name")

    activityScenario.close()
}
```

### With Dropshots

If you are using a screenshot library that cannot take a *ComposeTestRule* as argument (e.g. Dropshots), you can still screenshot the Composable as follows:

```kotlin
// with ActivityScenarioForComposableRule
dropshots.assertSnapshot(
    view = activityScenarioForComposableRule.activity.waitForComposeView(),
    name = "your_unique_screenshot_name",
)
```

or

```kotlin
// with ActivityScenarioConfigurator.ForComposable()
val activityScenario =
    ActivityScenarioConfigurator.ForComposable()
    ...
    .launchConfiguredActivity()
    .onActivity {
        ...
    }

dropshots.assertSnapshot(
    view = activityScenario.waitForActivity().waitForComposeView(),
    name = "your_unique_screenshot_name",
)
```

## Rendering elevation

Most screenshot testing libraries use `Canvas` under the hood with `Bitmap.Config.ARGB_8888` as default for generating bitmaps (i.e. the screenshots) from the Activities/Fragments/ViewHolders/Views/Dialogs/Composables...&#x20;

That's because Canvas is supported in all Android versions.\
Nevertheless, such bitmaps generated using `Canvas` have some limitations, e.g. UI elements are rendered without considering elevation (e.g. without shadows). So, how to render screenshots with elevation?

{% hint style="info" %}
Elevation can be manifested in many ways: e.g. a UI layer on top of another or a shadow in a CardView.
{% endhint %}

### Bitmap

Fortunately, most libraries let you pass a bitmap of the UI as an argument in their record/verify methods. In doing so, we can draw the views with elevation to a bitmap by using`PixelCopy`.

{% hint style="warning" %}
Robolectric 4.10+ cannot render shadows or elevation with RNG even with `PixelCopy`, as stated in [this issue](https://github.com/robolectric/robolectric/issues/8081)
{% endhint %}

*AndroidUiTestingUtils* provides methods to easily generate bitmaps from the Activities/Fragments/ViewHolders/Views/Dialogs/Composables:

1. `drawToBitmap(config = Bitmap.Config.ARGB_8888)` -> uses `Canvas` under the hood
2. `drawToBitmapWithElevation(config = Bitmap.Config.ARGB_8888)` -> uses `PixelCopy` under the hood

and one extra to fully screenshot a scrollable Android View:&#x20;

3. `drawFullScrollableToBitmap(config = Bitmap.Config.ARGB_8888)` -> uses `Canvas` under the hood

Differences between Bitmaps generated via `Canvas` and `Pixel Copy` might be specially noticeable on API 31:

<div align="center"><img src="https://user-images.githubusercontent.com/6097181/211920600-6cfcdde3-1fd6-4b23-84d1-3eae587c811d.png" alt="Differences between bitmaps generated with Canvas &#x26; Pixel Copy on API 31" width="350"></div>

{% hint style="info" %}
If using `PixelCopy` with ViewHolders/Views/Dialogs/Composables, consider launching the container Activity with transparent background for a more realistic screenshot of the UI component.

```kotlin
ActivityScenarioConfigurator.ForView() // or .ForComposable()
      ...
      .launchConfiguredActivity(backgroundColor = Color.TRANSPARENT)
```

or

```kotlin
ActivityScenarioForViewRule( // or ActivityScenarioForComposableRule()
      viewConfig = ...,
      backgroundColor = Color.TRANSPARENT,
)
```

Otherwise it uses the default Dark/Light Theme background colors (e.g. white and dark grey).
{% endhint %}

Using `PixelCopy` instead of `Canvas` comes with its own drawbacks though. In general, don't use `PixelCopy` to draw views that don't fit on the screen.

| Canvas                                                                   |                                                    PixelCopy                                                    |
| ------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------: |
| <p>✅ Can render elements beyond the screen,<br>e.g. long ScrollViews</p> | <p>❌ Cannot render elements beyond the screen, resizing them to fit in the window </p><p>if that's the case</p> |
| ❌ Ignores elevation of UI elements                                       |                                        ✅ Renders elevation of UI elements                                       |

And using `PixelCopy` in your screenshot tests is as simple as this:

```kotlin
// for UI Components like Activities/Fragments/ViewHolders/Views/Dialogs
compareScreenshot(
    bitmap = uiComponent.drawToBitmapWithElevation(),
    name = "your_unique_screenshot_name",
)
```

```kotlin
// for Composables
compareScreenshot(
    bitmap = activity.waitForComposableView().drawToBitmapWithElevation(),
    name = "your_unique_screenshot_name",
)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sergio-sastre.gitbook.io/androiduitestingutils/usage/instrumentation-tests-usage.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
