Skip to content

Commit b722d73

Browse files
Merge pull request #421 from airbnb/allen--animation-snapshot-support
Enable screenshots of animated components
2 parents 0fbde2f + bf79d79 commit b722d73

File tree

188 files changed

+579
-48
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

188 files changed

+579
-48
lines changed

build.gradle

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ buildscript {
1515
'gradle' : '8.9.1',
1616
'junit' : '4.13.2',
1717
'junitImplementation' : '1.1.2',
18-
'kotlinCompileTesting' : '0.7.0',
18+
'kotlinCompileTesting' : '0.8.0',
1919
'kotlinPoet' : '1.12.0',
2020
'ksp' : "$KSP_VERSION",
2121
'ktx' : '1.1.0',
2222
'lifecycle' : '2.6.2',
23-
'paparazzi' : '1.2.0',
23+
'paparazzi' : '2.0.0-alpha02',
2424
'picasso' : '2.8',
2525
'appcompat' : '1.6.1',
2626
'testRunner' : '1.4.0',
@@ -90,7 +90,6 @@ buildscript {
9090
google()
9191
mavenCentral()
9292
gradlePluginPortal()
93-
jcenter()
9493
}
9594
dependencies {
9695
classpath "com.android.tools.build:gradle:${versions.gradle}"
@@ -114,9 +113,8 @@ allprojects {
114113
repositories {
115114
google()
116115
mavenCentral()
117-
jcenter()
118116
}
119117
dependencies {
120118
detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:${versions.detekt}"
121119
}
122-
}
120+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.airbnb.android.showkase.annotation
2+
3+
/**
4+
* Configuration for how screenshots of the annotated Composable should be captured.
5+
*/
6+
sealed interface ScreenshotConfig {
7+
/**
8+
* A single screenshot will be captured of the initial composition.
9+
*/
10+
object SingleStaticImage : ScreenshotConfig
11+
12+
/**
13+
* An animated PNG will be captured of the Composable, using the values provided for
14+
* [ShowkaseComposable.captureDurationMillis] and [ShowkaseComposable.captureFramerate].
15+
*/
16+
data class SingleAnimatedImage(
17+
val durationMillis: Int = 1000,
18+
val framerate: Int = 30,
19+
) : ScreenshotConfig
20+
21+
/**
22+
* Multiple static screenshots will be taken of the Composable, with the animation advanced to the
23+
* time offsets provided in [ShowkaseComposable.captureOffsetsMillis].
24+
*/
25+
data class MultipleImagesAtOffsets(
26+
val offsetMillis: List<Int>,
27+
) : ScreenshotConfig
28+
}

showkase-annotation/src/main/java/com/airbnb/android/showkase/annotation/ShowkaseComposable.kt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ package com.airbnb.android.showkase.annotation
5858
* but are still available in the generated `ShowkaseBrowserComponent` object. This may be useful when
5959
* extra data is needed for attributing components during other processes (e.g. static analysis,
6060
* displaying attributions in a custom component browser).
61+
* @param screenshotCaptureConfig Configures how screenshot tests should capture the Composable content.
6162
*/
6263
@MustBeDocumented
6364
@Retention(AnnotationRetention.SOURCE)
@@ -74,4 +75,68 @@ annotation class ShowkaseComposable(
7475
val defaultStyle: Boolean = false,
7576
val tags: Array<String> = [],
7677
val extraMetadata: Array<String> = [],
78+
val screenshotCaptureConfig: ScreenshotCaptureConfig = ScreenshotCaptureConfig(
79+
// Need to specify default values here or else KAPT throws an error
80+
type = ScreenshotCaptureType.SingleStaticImage,
81+
durationMillis = 1000,
82+
framerate = 10,
83+
offsetsMillis = [0, 200, 400, 600, 800, 1000],
84+
),
7785
)
86+
87+
/**
88+
* Wrapper annotation class for grouping values related to screenshot capture configuration.
89+
* This will be converted to a [ScreenshotConfig] instance for downstream processing.
90+
*/
91+
@Retention(AnnotationRetention.SOURCE)
92+
annotation class ScreenshotCaptureConfig(
93+
/**
94+
* Used by Paparazzi snapshot testing to determine if the component has any animation, and how to capture
95+
* the screenshot.
96+
*/
97+
val type: ScreenshotCaptureType = ScreenshotCaptureType.SingleStaticImage,
98+
/**
99+
* Used by Paparazzi screenshot testing when [type] is set to
100+
* [ScreenshotCaptureType.SingleAnimatedImage]. Determines the duration the animation
101+
* will be played in milliseconds.
102+
*/
103+
val durationMillis: Int = 1000,
104+
/**
105+
* Used by Paparazzi screenshot testing when [type] is set to
106+
* [ScreenshotCaptureType.SingleAnimatedImage]. Determines how many frames
107+
* will be captured per second. 10 fps is chosen as a default to balance fidelity, file size, test
108+
* execution time, and flakiness.
109+
*/
110+
val framerate: Int = 10,
111+
/**
112+
* Used by Paparazzi screenshot testing when [type] is set to
113+
* [ScreenshotCaptureType.MultipleImagesAtOffsets]. One separate screenshot will be taken
114+
* at each of the time offsets provided here.
115+
*/
116+
val offsetsMillis: IntArray = [0, 200, 400, 600, 800, 1000],
117+
)
118+
119+
/**
120+
* Indicates how screenshots should be captured for the given Composable during testing.
121+
* Maps to the [ScreenshotConfig] type, which we cannot use as a value in an annotation.
122+
*/
123+
enum class ScreenshotCaptureType {
124+
/**
125+
* A single screenshot will be captured of the initial composition.
126+
*/
127+
SingleStaticImage,
128+
129+
/**
130+
* An animated PNG will be captured of the Composable, using the values provided for
131+
* [ShowkaseComposable.captureDurationMillis] and [ShowkaseComposable.captureFramerate].
132+
*/
133+
SingleAnimatedImage,
134+
135+
/**
136+
* Multiple static screenshots will be taken of the Composable, with the animation advanced to the
137+
* time offsets provided in [ShowkaseComposable.captureOffsetsMillis].
138+
*
139+
* NOTE: This isn't working currently in Paparazzi, see https://github.com/cashapp/paparazzi/pull/1645.
140+
*/
141+
MultipleImagesAtOffsets
142+
}

showkase-processor-testing/src/test/java/com/airbnb/android/showkase_processor_testing/ShowkaseProcessorTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,4 +619,9 @@ class ShowkaseProcessorTest : BaseProcessorTest() {
619619
options["requireShowkaseComposableAnnotation"] = "true"
620620
compileInputsAndVerifyOutputs(options = options)
621621
}
622+
623+
@Test
624+
fun `composable function with showkase annotation with capturetype compiles ok`() {
625+
compileInputsAndVerifyOutputs()
626+
}
622627
}

showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/MyScreenshotTest_PaparazziShowkaseTest.kt renamed to showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/MyScreenshotTestImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import org.junit.Test
1919
import org.junit.runner.RunWith
2020

2121
@RunWith(TestParameterInjector::class)
22-
public class MyScreenshotTest_PaparazziShowkaseTest : MyScreenshotTest() {
22+
public class MyScreenshotTestImpl : MyScreenshotTest() {
2323
@get:Rule
2424
public val paparazzi: Paparazzi = providePaparazzi()
2525

@@ -35,7 +35,7 @@ public class MyScreenshotTest_PaparazziShowkaseTest : MyScreenshotTest() {
3535
uiMode: PaparazziShowkaseUIMode,
3636
) {
3737
paparazzi.unsafeUpdateConfig(config.deviceConfig.copy(softButtons = false))
38-
takePaparazziSnapshot(paparazzi, elementPreview, direction, uiMode)
38+
takePaparazziSnapshot(paparazzi, elementPreview, direction, uiMode, elementPreview.captureType)
3939
}
4040

4141
@Suppress("DEPRECATION")

showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// This is an auto-generated file. Please do not edit/modify this file.
22
import androidx.compose.runtime.Composable
3+
import com.airbnb.android.showkase.`annotation`.ScreenshotConfig
34
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
45

56
public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowserComponent(
@@ -8,5 +9,6 @@ public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowse
89
componentKDoc = "",
910
componentKey = """_TestComposable1_null_group1_name1_0_null""",
1011
isDefaultStyle = false,
12+
screenshotConfig = ScreenshotConfig.SingleStaticImage,
1113
component = @Composable { TestComposable1() }
1214
)

showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_paparazzi_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// This is an auto-generated file. Please do not edit/modify this file.
22
import androidx.compose.runtime.Composable
3+
import com.airbnb.android.showkase.`annotation`.ScreenshotConfig
34
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
45

56
public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowserComponent(
@@ -8,5 +9,6 @@ public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowse
89
componentKDoc = "",
910
componentKey = """_TestComposable2_null_group2_name2_0_null""",
1011
isDefaultStyle = false,
12+
screenshotConfig = ScreenshotConfig.SingleStaticImage,
1113
component = @Composable { TestComposable2() }
1214
)

showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable1group1name1.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package com.airbnb.android.showkase_processor_testing
33

44
import androidx.compose.runtime.Composable
5+
import com.airbnb.android.showkase.`annotation`.ScreenshotConfig
56
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
67

78
public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowserComponent(
@@ -11,5 +12,6 @@ public val TestComposable1group1name1: ShowkaseBrowserComponent = ShowkaseBrowse
1112
componentKey =
1213
"""com.airbnb.android.showkase_processor_testing_TestComposable1_null_group1_name1_0_null""",
1314
isDefaultStyle = false,
15+
screenshotConfig = ScreenshotConfig.SingleStaticImage,
1416
component = @Composable { TestComposable1() }
1517
)

showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_generates_screenshot_test_for_all_UI_elements/output/TestComposable2group2name2.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package com.airbnb.android.showkase_processor_testing
33

44
import androidx.compose.runtime.Composable
5+
import com.airbnb.android.showkase.`annotation`.ScreenshotConfig
56
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
67

78
public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowserComponent(
@@ -11,5 +12,6 @@ public val TestComposable2group2name2: ShowkaseBrowserComponent = ShowkaseBrowse
1112
componentKey =
1213
"""com.airbnb.android.showkase_processor_testing_TestComposable2_null_group2_name2_0_null""",
1314
isDefaultStyle = false,
15+
screenshotConfig = ScreenshotConfig.SingleStaticImage,
1416
component = @Composable { TestComposable2() }
1517
)

showkase-processor-testing/src/test/resources/ShowkaseProcessorTest/class_with_@ScreenshotTest_only_generates_screenshot_test_for_only_non_preview_parameter_composable/output/TestComposable1WrapperClassTestComposable1.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package com.airbnb.android.showkase_processor_testing
33

44
import androidx.compose.runtime.Composable
5+
import com.airbnb.android.showkase.`annotation`.ScreenshotConfig
56
import com.airbnb.android.showkase.models.ShowkaseBrowserComponent
67

78
public val TestComposable1WrapperClassTestComposable1: ShowkaseBrowserComponent =
@@ -12,6 +13,7 @@ public val TestComposable1WrapperClassTestComposable1: ShowkaseBrowserComponent
1213
componentKey =
1314
"""com.airbnb.android.showkase_processor_testing.WrapperClass_TestComposable1_com.airbnb.android.showkase_processor_testing.WrapperClass_WrapperClass_TestComposable1_0_null""",
1415
isDefaultStyle = false,
16+
screenshotConfig = ScreenshotConfig.SingleStaticImage,
1517
component = @Composable {
1618
WrapperClass().TestComposable1()
1719
}

0 commit comments

Comments
 (0)