AutoShot provides a set of tools to automate the generation and execution of screenshot tests for Android Compose applications. It consists of:
- A compiler-agnostic CLI Processor that generates JUnit test wrappers from Composable previews.
- A KSP Processor that runs during compilation to discover annotations and output metadata.
- A Gradle Plugin that wires up the generation and execution tasks seamlessly.
AutoShot supports two modes of execution for generating test files:
- How it works: KSP scans code for
@Previewannotations during compilation and outputs a lightweight metadata file (autoshot_metadata.txt). Thegenerate<VariantName>ScreenshotWrapperstask then reads this metadata to generate the final screenshot test classes. - Benefit: 100% accurate symbol resolution (including wildcard imports and custom annotations declared in external libraries).
- How it works: Bypasses the KSP compiler plugin entirely. The
generate<VariantName>ScreenshotWrapperstask scans your raw Kotlin source code files directly using a fast built-in tokenizer/parser. - Benefit: Rapid execution, zero compiler/KSP overhead, and does not require compile-time AGP dependencies.
To use the tools in your project, publish the binaries locally:
make publish-allTo consume the published artifacts, configure the GitHub Packages Maven repository in your settings.gradle.kts. Note that GitHub Packages requires authentication (username + Personal Access Token) to resolve dependencies even for public packages:
pluginManagement {
repositories {
maven {
url = uri("https://maven.pkg.github.com/vojtech/autoshot")
credentials {
username = "your-github-username"
password = "your-github-personal-access-token" // PAT with read:packages scope
}
}
mavenLocal() // For local development
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/vojtech/autoshot")
credentials {
username = "your-github-username"
password = "your-github-personal-access-token"
}
}
mavenLocal() // For local development
}
}Add the Android Screenshot Testing flag:
android.experimental.enableScreenshotTest=trueAdd the compose-screenshot plugin and AutoShot binaries:
[versions]
screenshot = "0.0.1-alpha13"
[libraries]
screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot" }
[plugins]
screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot" }
fediim-autoshot = { id = "com.fediim.plugin.autoshot", version = "1.0.0-alpha01" }Root build.gradle.kts:
plugins {
alias(libs.plugins.screenshot) apply false
}Module-level build.gradle.kts:
plugins {
alias(libs.plugins.fediim.autoshot)
}
autoshot {
// Set to false to use Standalone mode (default is true if KSP is on classpath)
useKsp.set(true)
// Optional: Fully qualified class names of custom preview annotations to scan for
customAnnotations.set(listOf("com.example.feature.MyCustomPreview"))
}Regardless of which mode you choose, the plugin registers the following tasks:
./gradlew generateDebugScreenshotWrappersThis task is always responsible for generating the Kotlin wrapper files (under build/generated/autoshot/<variant>/kotlin) from either the KSP metadata file (KSP mode) or by parsing sources directly (Standalone mode).
./gradlew copyDebugScreenshotTestsCopies the generated test wrappers to src/screenshotTest/kotlin so the Android screenshot testing runner can compile and run them.
# Update reference baselines (goldens)
./gradlew :<module>:updateDebugScreenshotTest
# Validate screenshots against reference baselines
./gradlew :<module>:validateDebugScreenshotTestIf you prefer to bypass the Gradle plugin completely, you can install and use the standalone CLI runner.
You can install the CLI tool using Homebrew:
brew install vojtech/autoshot/autoshotOr tap the repository first:
brew tap vojtech/autoshot
brew install autoshotAlternatively, download the standalone JAR directly from the releases page and execute it using Java.
Using Homebrew command:
autoshot \
--sources "src/main/kotlin,src/debug/kotlin" \
--output "build/generated/autoshot/debug/kotlin" \
--custom-annotations "com.example.MyCustomPreview" \
--visibility-report "build/generated/autoshot/visibility_report.txt"Using raw JAR directly:
java -cp autoshot-processor.jar com.fediim.autoshot.processor.CliMain \
--sources "src/main/kotlin,src/debug/kotlin" \
--output "build/generated/autoshot/debug/kotlin" \
--custom-annotations "com.example.MyCustomPreview" \
--visibility-report "build/generated/autoshot/visibility_report.txt"Using Homebrew command:
autoshot \
--metadata "build/generated/ksp/debug/resources/autoshot_metadata.txt" \
--output "build/generated/autoshot/debug/kotlin" \
--visibility-report "build/generated/autoshot/visibility_report.txt"Using raw JAR directly:
java -cp autoshot-processor.jar com.fediim.autoshot.processor.CliMain \
--metadata "build/generated/ksp/debug/resources/autoshot_metadata.txt" \
--output "build/generated/autoshot/debug/kotlin" \
--visibility-report "build/generated/autoshot/visibility_report.txt"The generator searches for methods annotated with @Preview or any meta-annotation (like @PreviewLightDark).
- Visibility: Preview functions must not be
privateso they can be referenced from the generated test wrappers. - Excluding Previews: To skip generating a test for a preview, either make the preview function
privateor annotate it with@IgnorePreview.
@Composable
@PreviewLightDark
fun MyWidgetPreview() {
MyWidget()
}
// Ignored because it has @IgnorePreview
@Composable
@Preview
@IgnorePreview
fun MyWidgetIgnoredPreview() {
MyWidget()
}
// Ignored because it is private
@Composable
@Preview
private fun MyWidgetPrivatePreview() {
MyWidget()
}If you have private previews that you want to include in screenshot testing, run:
./gradlew updatePreviewVisibilityThis task reads the visibility report and automatically changes the visibility of your private preview functions to internal in your source files.