| name | build_grid_screenshot_tests |
| description | A skill for building instrumented tests in Remote Compose using GridScreenshotUI. |
Building Instrumented Tests using GridScreenshotUI
[!IMPORTANT]
AI INSTRUCTION: Do not assume you should use GridScreenshotUI for all tests in this directory. If the user asks you to write a screenshot test, you MUST first explicitly ask them: "Would you like me to use the GridScreenshotUI utility for this test?" Proceed with using this skill only if they confirm.
This skill provides guidelines for building screenshot tests using GridScreenshotUI in the @compose/remote/remote-creation-compose project.
Purpose
GridScreenshotUI is a utility class designed to lay out multiple small remote UI components in a grid. This is particularly useful for screenshot testing as it allows you to capture many variations of a component (e.g., different alignments, modifiers, or arrangements) in a single screenshot, making tests more efficient and easier to compare visually.
How to use GridScreenshotUI
-
Test Class Setup:
Create a test class annotated with @MediumTest, @SdkSuppress(minSdkVersion = 35, maxSdkVersion = 35), and @RunWith(AndroidJUnit4::class).
-
Add the Screenshot Rule:
Define a RemoteScreenshotTestRule explicitly specifying the module directory and matcher.
@get:Rule
val composeTestRule: RemoteScreenshotTestRule by lazy {
RemoteScreenshotTestRule(
moduleDirectory = SCREENSHOT_GOLDEN_DIRECTORY,
context = ApplicationProvider.getApplicationContext(),
matcher = MSSIMMatcher(threshold = 0.999),
)
}
-
Instantiate GridScreenshotUI:
Create an instance of GridScreenshotUI in your test class.
private val gridScreenshotUI = GridScreenshotUI()
-
Define your UI variations:
Create a method or variable that provides a list of Pair<String, @RemoteComposable @Composable () -> Unit>. The string is the label for the variation, and the lambda is the actual Compose UI content.
You can use the .toInput() extension functions defined in GridScreenshotUI.Companion to easily convert a list of composables into the required pair format if you don't want to specify labels manually.
-
Write the Test:
Use composeTestRule.runScreenshotTest and call gridScreenshotUI.GridContent(...) with your list of variations.
@Test
fun exampleGridTest() =
composeTestRule.runScreenshotTest {
gridScreenshotUI.GridContent(getLayoutAlignmentUIs())
}
Best Practices
- Consolidate into
grid() when possible: Where feasible and as long as it does not negatively impact readability, group related visual configurations into a single @Test fun grid() using GridScreenshotUI instead of creating multiple individual @Test methods that each produce their own separate golden image.
- Grid Capacity & Splitting:
GridScreenshotUI arranges items in 3 columns by default (itemsPerRow = 3). On a standard emulator screen (e.g., Medium Phone), typically around 9 to 12 items (3 to 4 rows) fit comfortably without being cut off. If a test suite has more items, split them across multiple grid tests (e.g., grid(), grid2(), grid3(), etc.) or group them by logical sub-feature (e.g., grid_sizing(), grid_styling()).
- Extract composables to private functions: Extract each test variation into a standalone, private
@Composable @RemoteComposable fun ComponentVariant() function rather than writing large inline closures inside the tests list to keep the grid test clean and readable.
- Reuse Dimensions: Use
GridScreenshotUI.Companion.DefaultContainerSize to maintain consistent container dimensions across tests.
- RTL Testing: You can easily test RTL (Right-to-Left) layouts by passing
layoutDirection = LayoutDirection.Rtl to GridContent.
gridScreenshotUI.GridContent(
getLayoutAlignmentUIs(),
layoutDirection = LayoutDirection.Rtl,
)
- Helper methods: Create builder methods to generate the list of
Pair items if you have a combinatorial explosion of parameters (like trying out all combinations of Arrangements and Alignments through sequence).
private fun getLayoutAlignmentUIs(): List<Pair<String, @RemoteComposable @Composable () -> Unit>> =
sequence {
for (alignment alignments) {
(arrangement arrangements) {
yield(
to
{
RemoteRow(
modifier = RemoteModifier.size(DefaultContainerSize),
horizontalArrangement = arrangement,
verticalAlignment = alignment,
) {
}
}
)
}
}
}.toList()
Complete Example
@MediumTest
@SdkSuppress(minSdkVersion = 35, maxSdkVersion = 35)
@RunWith(AndroidJUnit4::class)
class RemoteCustomScreenshotTest {
@get:Rule
val composeTestRule =
RemoteScreenshotTestRule(
moduleDirectory = SCREENSHOT_GOLDEN_DIRECTORY,
context = ApplicationProvider.getApplicationContext(),
matcher = MSSIMMatcher(threshold = 0.999),
)
private val gridScreenshotUI = GridScreenshotUI()
@Test
fun grid() = composeTestRule.runScreenshotTest {
val tests =
listOf<Pair<String, @RemoteComposable @Composable () -> Unit>>(
"default" to @Composable @RemoteComposable { DefaultView() },
"styled" to @Composable @RemoteComposable { StyledView() },
)
gridScreenshotUI.GridContent(tests)
}
@Composable
@RemoteComposable
private fun DefaultView() {
}
@Composable
@RemoteComposable
private fun StyledView() {
}
}