Remove Roborazzi, use Paparazzi for Compound (#7735)

* Remove Roborazzi, use Paparazzi for Compound

The new screenshots will be recorded with the rest of the Paparazzi ones. Note the names changed, since Paparazzi enforces a `$packagename.$testname.$name.$extension` format

* Set a max width value for color previews

* Merge the `verifyPaparazziDebug` CI steps

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa
2026-09-21 12:27:56 +02:00
committed by GitHub
parent 95d734efc9
commit c4529b87b4
63 changed files with 249 additions and 245 deletions
@@ -57,12 +57,6 @@ echo "Deleting previous screenshots"
echo "Record screenshots"
./gradlew recordPaparazziDebug --stacktrace $GRADLE_ARGS
echo "Deleting previous screenshots"
./gradlew removeOldScreenshots --stacktrace --warn $GRADLE_ARGS
echo "Record screenshots (Compound)"
./gradlew :libraries:compound:recordRoborazziDebug --stacktrace -PpreDexEnable=false --max-workers 4 --warn $GRADLE_ARGS
echo "Committing changes"
git config http.sslVerify false
+1 -5
View File
@@ -76,10 +76,7 @@ jobs:
run: ./gradlew :app:testGplayDebugUnitTest testDebugUnitTest -x :tests:konsist:testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES
- name: ⚙️ Run screenshot tests
run: ./gradlew :tests:uitests:verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
- name: ⚙️ Run Compound screenshot tests
run: ./gradlew :libraries:compound:verifyRoborazziDebug $CI_GRADLE_ARG_PROPERTIES
run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES
- name: ⚙️ Generate kover coverage report
run: ./gradlew :koverXmlReportMerged :koverHtmlReportMerged :koverVerifyAll -x testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES
@@ -103,7 +100,6 @@ jobs:
name: tests-and-screenshot-tests-results
path: |
**/build/paparazzi/failures/
**/build/roborazzi/failures/
**/build/reports/tests/*UnitTest/
- name: 🚫 Modify summary on error
+4 -23
View File
@@ -18,7 +18,6 @@ plugins {
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.dependencycheck) apply false
alias(libs.plugins.roborazzi) apply false
alias(libs.plugins.dependencyanalysis)
alias(libs.plugins.detekt)
alias(libs.plugins.ktlint)
@@ -153,7 +152,6 @@ allprojects {
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
val isScreenshotTest = project.gradle.startParameter.taskNames.any { it.contains("paparazzi", ignoreCase = true) }
val isRoborazziTest = project.gradle.startParameter.taskNames.any { it.contains("roborazzi", ignoreCase = true) }
if (isScreenshotTest) {
// Paparazzi tests benefit from parallelisation, so we can use half the available cores to run them in parallel.
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
@@ -178,12 +176,10 @@ allprojects {
// Disable screenshot tests by default
exclude("ui/*.class")
exclude("translations/*.class")
if (isRoborazziTest.not()) {
// Roborazzi screenshot tests (:libraries:compound) live in a `screenshot` package, which
// the two patterns above do not match, so they used to run on every plain unit test run.
// They are verified by the dedicated `verifyRoborazziDebug` task instead.
exclude("**/screenshot/**")
}
// The Compound screenshot tests (:libraries:compound) live in a `screenshot` package, which
// the two patterns above do not match, so they would otherwise run on every plain unit test run.
// They are verified by the dedicated `verifyPaparazziDebug` task instead.
exclude("**/screenshot/**")
}
}
}
@@ -230,21 +226,6 @@ subprojects {
tasks.findByName("recordPaparazziRelease")?.dependsOn(removeOldScreenshotsTask)
}
// Make sure to delete old snapshot before recording new ones
subprojects {
val screenshotsDir = File("${project.projectDir}/screenshots")
val removeOldScreenshotsTask = tasks.register("removeOldScreenshots") {
onlyIf { screenshotsDir.exists() }
doFirst {
println("Delete previous screenshots located at $screenshotsDir\n")
screenshotsDir.deleteRecursively()
}
}
tasks.findByName("recordRoborazzi")?.dependsOn(removeOldScreenshotsTask)
tasks.findByName("recordRoborazziDebug")?.dependsOn(removeOldScreenshotsTask)
tasks.findByName("recordRoborazziRelease")?.dependsOn(removeOldScreenshotsTask)
}
subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions {
-4
View File
@@ -59,10 +59,6 @@ ksp.allow.all.target.configuration=false
# Used to prevent detekt from reusing invalid cached rules
detekt.use.worker.api=true
# Let test include roborazzi verification
# https://github.com/takahirom/roborazzi?tab=readme-ov-file#roborazzitest
roborazzi.test.verify=true
# Needed after enabling proguard on AGP 8.13.1
android.r8.gradual.support=true
-7
View File
@@ -32,7 +32,6 @@ accompanist = "0.37.3"
# Test
test_core = "1.7.0"
roborazzi = "1.74.0"
# Jetbrain
datetime = "0.8.0"
@@ -247,11 +246,6 @@ google_autoservice_annotations = { module = "com.google.auto.service:auto-servic
# Miscellaneous
androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
# Test
test_roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" }
test_roborazzi_compose = { module = "io.github.takahirom.roborazzi:roborazzi-compose", version.ref = "roborazzi" }
test_roborazzi_junit = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" }
[bundles]
[plugins]
@@ -269,7 +263,6 @@ dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
dependencycheck = "org.owasp.dependencycheck:13.0.0"
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
paparazzi = "app.cash.paparazzi:2.0.0-alpha05"
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" }
sonarqube = "org.sonarqube:7.5.0.8588"
+25 -4
View File
@@ -10,7 +10,7 @@ import extension.testCommonDependencies
plugins {
id("io.element.android-compose-library")
alias(libs.plugins.roborazzi)
alias(libs.plugins.paparazzi)
}
android {
@@ -21,9 +21,30 @@ android {
}
}
// Paparazzi's layoutlib runtime conflicts with Robolectric, so during Paparazzi record/verify runs
// only execute the screenshot tests (which use Paparazzi) and leave the Robolectric-based unit tests
// out. On plain unit test runs the screenshot package is excluded in the root build.gradle.kts.
tasks.withType(Test::class).configureEach {
val isScreenshotTest = gradle.startParameter.taskNames.any { it.contains("paparazzi", ignoreCase = true) }
if (isScreenshotTest) {
include("**/screenshot/**")
}
}
dependencies {
testCommonDependencies(libs)
testImplementation(libs.test.roborazzi)
testImplementation(libs.test.roborazzi.compose)
testImplementation(libs.test.roborazzi.junit)
// Paparazzi 1.3.2 workaround (see https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md#132---2024-01-13)
constraints.add("testImplementation", "com.google.guava:guava") {
attributes {
attribute(
TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
objects.named(TargetJvmEnvironment::class.java, TargetJvmEnvironment.STANDARD_JVM)
)
}
because(
"LayoutLib and sdk-common depend on Guava's -jre published variant." +
"See https://github.com/cashapp/paparazzi/issues/906."
)
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,7 @@ package io.element.android.compound.previews
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -27,7 +28,7 @@ import kotlinx.collections.immutable.persistentMapOf
@Preview(heightDp = 2000)
@Composable
internal fun CompoundSemanticColorsLight() = ElementTheme {
Surface {
Surface(Modifier.width(300.dp)) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
@@ -48,7 +49,7 @@ internal fun CompoundSemanticColorsLight() = ElementTheme {
internal fun CompoundSemanticColorsLightHc() = ElementTheme(
compoundDark = compoundColorsHcDark,
) {
Surface {
Surface(Modifier.width(300.dp)) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
@@ -67,7 +68,7 @@ internal fun CompoundSemanticColorsLightHc() = ElementTheme(
@Preview(heightDp = 2000)
@Composable
internal fun CompoundSemanticColorsDark() = ElementTheme(theme = Theme.Dark) {
Surface {
Surface(Modifier.width(300.dp)) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
@@ -89,7 +90,7 @@ internal fun CompoundSemanticColorsDarkHc() = ElementTheme(
theme = Theme.Dark,
compoundDark = compoundColorsHcDark,
) {
Surface {
Surface(Modifier.width(300.dp)) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
@@ -8,24 +8,22 @@
package io.element.android.compound.screenshot
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.compound.screenshot.utils.createPaparazziRule
import io.element.android.compound.theme.AvatarColorsPreviewDark
import io.element.android.compound.theme.AvatarColorsPreviewLight
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class AvatarColorsTest : RobolectricTest() {
class AvatarColorsTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
@Config(sdk = [35], qualifiers = "xxhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("Avatar Colors - Light.png")) {
paparazzi.snapshot(name = "Avatar Colors - Light") {
AvatarColorsPreviewLight()
}
captureRoboImage(file = screenshotFile("Avatar Colors - Dark.png")) {
paparazzi.snapshot(name = "Avatar Colors - Dark") {
AvatarColorsPreviewDark()
}
}
@@ -11,36 +11,34 @@ package io.element.android.compound.screenshot
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.previews.IconsCompoundPreviewDark
import io.element.android.compound.previews.IconsCompoundPreviewLight
import io.element.android.compound.previews.IconsCompoundPreviewRtl
import io.element.android.compound.previews.IconsPreview
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.compound.screenshot.utils.createPaparazziRule
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.theme.Theme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.tests.testutils.robolectric.RobolectricTest
import kotlinx.collections.immutable.toImmutableList
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class CompoundIconTest : RobolectricTest() {
class CompoundIconTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
@Config(sdk = [35], qualifiers = "w1024dp-h2048dp")
fun screenshots() {
captureRoboImage(file = screenshotFile("Compound Icons - Light.png")) {
paparazzi.snapshot(name = "Compound Icons - Light") {
IconsCompoundPreviewLight()
}
captureRoboImage(file = screenshotFile("Compound Icons - Rtl.png")) {
paparazzi.snapshot(name = "Compound Icons - Rtl") {
IconsCompoundPreviewRtl()
}
captureRoboImage(file = screenshotFile("Compound Icons - Dark.png")) {
paparazzi.snapshot(name = "Compound Icons - Dark") {
IconsCompoundPreviewDark()
}
captureRoboImage(file = screenshotFile("Compound Vector Icons - Light.png")) {
paparazzi.snapshot(name = "Compound Vector Icons - Light") {
val content: List<@Composable ColumnScope.() -> Unit> = CompoundIcons.all.map {
@Composable { Icon(imageVector = it, contentDescription = null) }
}
@@ -51,7 +49,7 @@ class CompoundIconTest : RobolectricTest() {
)
}
}
captureRoboImage(file = screenshotFile("Compound Vector Icons - Dark.png")) {
paparazzi.snapshot(name = "Compound Vector Icons - Dark") {
val content: List<@Composable ColumnScope.() -> Unit> = CompoundIcons.all.map {
@Composable { Icon(imageVector = it, contentDescription = null) }
}
@@ -8,20 +8,18 @@
package io.element.android.compound.screenshot
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.previews.CompoundTypographyPreview
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.compound.screenshot.utils.createPaparazziRule
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class CompoundTypographyTest : RobolectricTest() {
class CompoundTypographyTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
@Config(sdk = [35], qualifiers = "h2048dp-xxhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("Compound Typography.png")) {
paparazzi.snapshot(name = "Compound Typography") {
CompoundTypographyPreview()
}
}
@@ -18,22 +18,21 @@ import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.colors.SemanticColorsLightDark
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.compound.screenshot.utils.createPaparazziRule
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.theme.ForcedDarkElementTheme
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class ForcedDarkElementThemeTest : RobolectricTest() {
class ForcedDarkElementThemeTest {
// The preview uses `fillMaxSize` to fill the inner Surface, so it needs a bounded frame.
@get:Rule
val paparazzi = createPaparazziRule(widthDp = 420, heightDp = 640)
@Test
@Config(sdk = [35], qualifiers = "xxhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("ForcedDarkElementTheme.png")) {
paparazzi.snapshot(name = "ForcedDarkElementTheme") {
ElementTheme {
Surface {
Column(
@@ -12,32 +12,31 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.previews.ColorPreview
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.compound.screenshot.utils.createPaparazziRule
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.theme.LinkColor
import io.element.android.compound.theme.SnackBarLabelColorDark
import io.element.android.compound.theme.SnackBarLabelColorLight
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class LegacyColorsTest : RobolectricTest() {
class LegacyColorsTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
@Config(sdk = [35], qualifiers = "xxhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("Legacy Colors.png")) {
paparazzi.snapshot(name = "Legacy Colors") {
ElementTheme {
Surface {
Surface(Modifier.width(300.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Legacy Colors")
Spacer(modifier = Modifier.height(10.dp))
@@ -12,32 +12,31 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.compound.screenshot.utils.createPaparazziRule
import io.element.android.compound.theme.ColorsSchemeDarkHcPreview
import io.element.android.compound.theme.ColorsSchemeDarkPreview
import io.element.android.compound.theme.ColorsSchemeLightHcPreview
import io.element.android.compound.theme.ColorsSchemeLightPreview
import io.element.android.compound.theme.ElementTheme
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class MaterialColorSchemeTest : RobolectricTest() {
class MaterialColorSchemeTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
@Config(sdk = [35], qualifiers = "h2048dp-xhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("Material3 Colors - Light.png")) {
paparazzi.snapshot(name = "Material3 Colors - Light") {
ElementTheme {
Surface {
Surface(Modifier.width(300.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "M3 Light colors",
@@ -49,9 +48,9 @@ class MaterialColorSchemeTest : RobolectricTest() {
}
}
}
captureRoboImage(file = screenshotFile("Material3 Colors - Light HC.png")) {
paparazzi.snapshot(name = "Material3 Colors - Light HC") {
ElementTheme {
Surface {
Surface(Modifier.width(300.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "M3 Light HC colors",
@@ -63,9 +62,9 @@ class MaterialColorSchemeTest : RobolectricTest() {
}
}
}
captureRoboImage(file = screenshotFile("Material3 Colors - Dark.png")) {
paparazzi.snapshot(name = "Material3 Colors - Dark") {
ElementTheme {
Surface {
Surface(Modifier.width(300.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "M3 Dark colors",
@@ -77,9 +76,9 @@ class MaterialColorSchemeTest : RobolectricTest() {
}
}
}
captureRoboImage(file = screenshotFile("Material3 Colors - Dark HC.png")) {
paparazzi.snapshot(name = "Material3 Colors - Dark HC") {
ElementTheme {
Surface {
Surface(Modifier.width(300.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "M3 Dark HC colors",
@@ -8,20 +8,19 @@
package io.element.android.compound.screenshot
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.compound.screenshot.utils.createPaparazziRule
import io.element.android.compound.theme.MaterialTextPreview
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class MaterialTextTest : RobolectricTest() {
class MaterialTextTest {
// MaterialTextPreview relies on `weight`/`fillMaxSize`, so it needs a bounded frame to lay out.
@get:Rule
val paparazzi = createPaparazziRule(widthDp = 420, heightDp = 1200)
@Test
@Config(sdk = [35], qualifiers = "w480dp-h1200dp-xxhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("MaterialText Colors.png")) {
paparazzi.snapshot(name = "MaterialText Colors") {
MaterialTextPreview()
}
}
@@ -8,20 +8,18 @@
package io.element.android.compound.screenshot
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.previews.TypographyPreview
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.compound.screenshot.utils.createPaparazziRule
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class MaterialTypographyTest : RobolectricTest() {
class MaterialTypographyTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
@Config(sdk = [35], qualifiers = "h2048dp-xxhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("Material Typography.png")) {
paparazzi.snapshot(name = "Material Typography") {
TypographyPreview()
}
}
@@ -19,22 +19,20 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.previews.ColorsSchemePreview
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.compound.screenshot.utils.createPaparazziRule
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.theme.Theme
import io.element.android.tests.testutils.robolectric.RobolectricTest
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class MaterialYouThemeTest : RobolectricTest() {
class MaterialYouThemeTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
@Config(sdk = [35], qualifiers = "h2048dp-xhdpi")
fun screenshots() {
captureRoboImage(file = screenshotFile("MaterialYou Theme - Light.png")) {
paparazzi.snapshot(name = "MaterialYou Theme - Light") {
ElementTheme(dynamicColor = true) {
Surface {
Column(
@@ -49,7 +47,7 @@ class MaterialYouThemeTest : RobolectricTest() {
}
}
}
captureRoboImage(file = screenshotFile("MaterialYou Theme - Dark.png")) {
paparazzi.snapshot(name = "MaterialYou Theme - Dark") {
ElementTheme(dynamicColor = true, theme = Theme.Dark) {
Surface {
Column(
@@ -8,35 +8,33 @@
package io.element.android.compound.screenshot
import com.github.takahirom.roborazzi.captureRoboImage
import io.element.android.compound.previews.CompoundSemanticColorsDark
import io.element.android.compound.previews.CompoundSemanticColorsDarkHc
import io.element.android.compound.previews.CompoundSemanticColorsLight
import io.element.android.compound.previews.CompoundSemanticColorsLightHc
import io.element.android.compound.screenshot.utils.screenshotFile
import io.element.android.tests.testutils.robolectric.RobolectricTest
import io.element.android.compound.screenshot.utils.createPaparazziRule
import org.junit.Rule
import org.junit.Test
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
class SemanticColorsTest : RobolectricTest() {
@Config(sdk = [35], qualifiers = "h2000dp-xhdpi")
class SemanticColorsTest {
@get:Rule
val paparazzi = createPaparazziRule()
@Test
fun screenshots() {
captureRoboImage(file = screenshotFile("Compound Semantic Colors - Light.png")) {
paparazzi.snapshot(name = "Compound Semantic Colors - Light") {
CompoundSemanticColorsLight()
}
captureRoboImage(file = screenshotFile("Compound Semantic Colors - Light HC.png")) {
paparazzi.snapshot(name = "Compound Semantic Colors - Light HC") {
CompoundSemanticColorsLightHc()
}
captureRoboImage(file = screenshotFile("Compound Semantic Colors - Dark.png")) {
paparazzi.snapshot(name = "Compound Semantic Colors - Dark") {
CompoundSemanticColorsDark()
}
captureRoboImage(file = screenshotFile("Compound Semantic Colors - Dark HC.png")) {
paparazzi.snapshot(name = "Compound Semantic Colors - Dark HC") {
CompoundSemanticColorsDarkHc()
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.compound.screenshot.utils
import app.cash.paparazzi.DeviceConfig
import app.cash.paparazzi.Paparazzi
import com.android.ide.common.rendering.api.SessionParams.RenderingMode
import com.android.resources.Density
// Screenshots are captured at xxhdpi so the icon grids stay sharp. This is the density scale factor
// (480dpi / 160dpi), used to convert the dp bounds below into the pixel values [DeviceConfig] expects.
private const val DENSITY_SCALE = 2
// Upper bounds for self-sizing screenshots, in dp. Large enough to fit the tallest and widest
// Compound preview; `RenderingMode.SHRINK` then crops each image down to its actual content.
private const val MAX_WIDTH_DP = 2048
private const val MAX_HEIGHT_DP = 4800
/**
* Creates a [Paparazzi] rule for the Compound screenshot tests.
*
* By default each screenshot sizes itself to its content ([RenderingMode.SHRINK]) instead of being
* rendered at a fixed device frame size, and the [DeviceConfig] only acts as an upper bound.
*
* Pass [widthDp] and [heightDp] for previews that rely on `fillMaxSize`/`weight` and therefore need
* a bounded frame to lay out correctly: they are then rendered at that fixed size instead.
*
* [useDeviceResolution] is enabled so screenshots are captured at full device resolution. Without
* it, Paparazzi downscales every image to a 1000px max dimension, which makes large previews (such
* as the icon grids) look pixelated.
*/
fun createPaparazziRule(
widthDp: Int? = null,
heightDp: Int? = null,
): Paparazzi {
val selfSizing = widthDp == null || heightDp == null
return Paparazzi(
deviceConfig = DeviceConfig(
screenWidth = (widthDp ?: MAX_WIDTH_DP) * DENSITY_SCALE,
screenHeight = (heightDp ?: MAX_HEIGHT_DP) * DENSITY_SCALE,
xdpi = 480,
ydpi = 480,
density = Density.XXHIGH,
softButtons = false,
),
renderingMode = if (selfSizing) RenderingMode.SHRINK else RenderingMode.NORMAL,
supportsRtl = true,
useDeviceResolution = true,
maxPercentDifference = 0.01,
)
}
@@ -1,19 +0,0 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.compound.screenshot.utils
import java.io.File
/**
* Returns a [File] object for a screenshot with the given [filename].
* This is to ensure we have a consistent location for all screenshots.
*/
fun screenshotFile(filename: String): File {
return File("screenshots", filename)
}