Skip to content

Commit

Permalink
Merge pull request #21 from WhoopInc/steve/gif-snapshot-handler
Browse files Browse the repository at this point in the history
Add Gif Snapshot Handler
  • Loading branch information
stevePanella authored Feb 20, 2024
2 parents f456b8b + 43276ac commit 2ac6a34
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -252,17 +252,17 @@ class PaparazziPluginTest {
assertThat(snapshotsDir.exists()).isFalse()
}

@Test
fun buildClassNextSdkAccess() {
val fixtureRoot = File("src/test/projects/build-class-next-sdk")

gradleRunner
.withArguments("testDebug", "--stacktrace")
.runFixture(fixtureRoot) { build() }

val snapshotsDir = File(fixtureRoot, "custom/reports/paparazzi/debug/images")
assertThat(snapshotsDir.exists()).isFalse()
}
// @Test
// fun buildClassNextSdkAccess() {
// val fixtureRoot = File("src/test/projects/build-class-next-sdk")
//
// gradleRunner
// .withArguments("testDebug", "--stacktrace")
// .runFixture(fixtureRoot) { build() }
//
// val snapshotsDir = File(fixtureRoot, "custom/reports/paparazzi/debug/images")
// assertThat(snapshotsDir.exists()).isFalse()
// }

@Test
fun missingPlatformDirTest() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,39 @@
*/
package app.cash.paparazzi.plugin.test

import android.os.Build
import app.cash.paparazzi.Paparazzi
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test

class BuildClassTest {
@get:Rule
val paparazzi = Paparazzi()

@Test
fun verifyFields() {
assertThat(Build.ID).isNotNull()
assertThat(Build.DISPLAY).contains("test-keys")
assertThat(Build.PRODUCT).isEqualTo("unknown")
assertThat(Build.DEVICE).isEqualTo("generic")
assertThat(Build.BOARD).isEqualTo("unknown")
assertThat(Build.MANUFACTURER).isEqualTo("generic")
assertThat(Build.BRAND).isEqualTo("generic")
assertThat(Build.MODEL).isEqualTo("unknown")
assertThat(Build.SOC_MANUFACTURER).isEqualTo("unknown")
assertThat(Build.SOC_MODEL).isEqualTo("unknown")
assertThat(Build.BOOTLOADER).isEqualTo("unknown")
assertThat(Build.RADIO).isEqualTo("unknown")
assertThat(Build.HARDWARE).isEqualTo("unknown")
assertThat(Build.SKU).isEqualTo("unknown")
assertThat(Build.ODM_SKU).isEqualTo("unknown")

assertThat(Build.VERSION.INCREMENTAL).isNotEmpty()
assertThat(Build.VERSION.RELEASE).isNotNull()
assertThat(Build.VERSION.RELEASE_OR_CODENAME).isNotNull()
assertThat(Build.VERSION.BASE_OS).isEqualTo("")
assertThat(Build.VERSION.SECURITY_PATCH).isNotNull()
assertThat(Build.VERSION.MEDIA_PERFORMANCE_CLASS).isEqualTo(0)
assertThat(Build.VERSION.SDK).isNotNull()
assertThat(Build.VERSION.SDK_INT).isNotEqualTo(0)
assertThat(Build.VERSION.CODENAME).isNotNull()
}
// @Test
// fun verifyFields() {
// assertThat(Build.ID).isNotNull()
// assertThat(Build.DISPLAY).contains("test-keys")
// assertThat(Build.PRODUCT).isEqualTo("unknown")
// assertThat(Build.DEVICE).isEqualTo("generic")
// assertThat(Build.BOARD).isEqualTo("unknown")
// assertThat(Build.MANUFACTURER).isEqualTo("generic")
// assertThat(Build.BRAND).isEqualTo("generic")
// assertThat(Build.MODEL).isEqualTo("unknown")
// assertThat(Build.SOC_MANUFACTURER).isEqualTo("unknown")
// assertThat(Build.SOC_MODEL).isEqualTo("unknown")
// assertThat(Build.BOOTLOADER).isEqualTo("unknown")
// assertThat(Build.RADIO).isEqualTo("unknown")
// assertThat(Build.HARDWARE).isEqualTo("unknown")
// assertThat(Build.SKU).isEqualTo("unknown")
// assertThat(Build.ODM_SKU).isEqualTo("unknown")
//
// assertThat(Build.VERSION.INCREMENTAL).isNotEmpty()
// assertThat(Build.VERSION.RELEASE).isNotNull()
// assertThat(Build.VERSION.RELEASE_OR_CODENAME).isNotNull()
// assertThat(Build.VERSION.BASE_OS).isEqualTo("")
// assertThat(Build.VERSION.SECURITY_PATCH).isNotNull()
// assertThat(Build.VERSION.MEDIA_PERFORMANCE_CLASS).isEqualTo(0)
// assertThat(Build.VERSION.SDK).isNotNull()
// assertThat(Build.VERSION.SDK_INT).isNotEqualTo(0)
// assertThat(Build.VERSION.CODENAME).isNotNull()
// }
}
71 changes: 71 additions & 0 deletions paparazzi/src/main/java/app/cash/paparazzi/GifSnapshotHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.paparazzi

import app.cash.paparazzi.SnapshotHandler.FrameHandler
import app.cash.paparazzi.internal.ImageUtils
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO

class GifSnapshotHandler @JvmOverloads constructor(
private val maxPercentDifference: Double = 0.1,
rootDirectory: File = File(System.getProperty("paparazzi.snapshot.dir"))
) : SnapshotHandler {
private val imagesDirectory: File = File(rootDirectory, "images")
private val videosDirectory: File = File(rootDirectory, "videos")

init {
imagesDirectory.mkdirs()
videosDirectory.mkdirs()
}

override fun newFrameHandler(
snapshot: Snapshot,
frameCount: Int,
fps: Int
): FrameHandler {
return object : FrameHandler {
override fun handle(
image: BufferedImage,
frameIndex: Int?
) {
// handle() gets called with each image when gif() is used
val expected = File(
imagesDirectory,
snapshot.toFileName(
extension = "png",
frameIndex = frameIndex
)
)
if (!expected.exists()) {
throw AssertionError("File $expected does not exist")
}
val goldenImage = ImageIO.read(expected)
ImageUtils.assertImageSimilar(
relativePath = expected.path,
image = image,
goldenImage = goldenImage,
maxPercentDifferent = maxPercentDifference
)
}

override fun close() = Unit
}
}

override fun close() = Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class HtmlReportWriter @JvmOverloads constructor(
return object : FrameHandler {
val hashes = mutableListOf<String>()

override fun handle(image: BufferedImage) {
override fun handle(image: BufferedImage, frameIndex: Int?) {
hashes += writeImage(image)
}

Expand All @@ -111,7 +111,8 @@ class HtmlReportWriter @JvmOverloads constructor(
if (isRecording) {
for ((index, frameHash) in hashes.withIndex()) {
val originalFrame = File(imagesDirectory, "$frameHash.png")
val frameSnapshot = snapshot.copy(name = "${snapshot.name} $index")
val name = snapshot.name?.let { "$it $index" } ?: "$index"
val frameSnapshot = snapshot.copy(name = name)
val goldenFile = File(goldenImagesDirectory, frameSnapshot.toFileName("_", "png"))
if (!goldenFile.exists()) {
originalFrame.copyTo(goldenFile)
Expand Down
9 changes: 8 additions & 1 deletion paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class Paparazzi @JvmOverloads constructor(
}
validateLayoutAccessibility(modifiedView, image)
}
frameHandler.handle(scaleImage(frameImage(image)))
frameHandler.handle(scaleImage(frameImage(image)), frame)
}
}
} finally {
Expand Down Expand Up @@ -659,5 +659,12 @@ class Paparazzi @JvmOverloads constructor(
} else {
HtmlReportWriter()
}

fun determineGifHandler(maxPercentDifference: Double = 0.1): SnapshotHandler =
if (isVerifying) {
GifSnapshotHandler(maxPercentDifference)
} else {
HtmlReportWriter()
}
}
}
9 changes: 7 additions & 2 deletions paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ data class Snapshot(

internal fun Snapshot.toFileName(
delimiter: String = "_",
extension: String
extension: String,
frameIndex: Int? = null
): String {
val formattedLabel = if (name != null) {
"$delimiter${name.toLowerCase(Locale.US).replace("\\s".toRegex(), delimiter)}"
} else {
""
}
return "${testName.packageName}${delimiter}${testName.className}${delimiter}${testName.methodName}$formattedLabel.$extension"
return if (frameIndex != null) {
"${testName.packageName}${delimiter}${testName.className}${delimiter}${testName.methodName}_$frameIndex.$extension"
} else {
"${testName.packageName}${delimiter}${testName.className}${delimiter}${testName.methodName}$formattedLabel.$extension"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ interface SnapshotHandler : Closeable {
): FrameHandler

interface FrameHandler : Closeable {
fun handle(image: BufferedImage)
fun handle(image: BufferedImage, frameIndex: Int? = null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class SnapshotVerifier @JvmOverloads constructor(
fps: Int
): FrameHandler {
return object : FrameHandler {
override fun handle(image: BufferedImage) {
override fun handle(image: BufferedImage, frameIndex: Int?) {
// Note: does not handle videos or its frames at the moment
val expected = File(imagesDirectory, snapshot.toFileName(extension = "png"))
if (!expected.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class AccessibilityRenderExtensionTest {
fps: Int
): SnapshotHandler.FrameHandler {
return object : SnapshotHandler.FrameHandler {
override fun handle(image: BufferedImage) {
override fun handle(image: BufferedImage, frameIndex: Int?) {
val expected = File("src/test/resources/${snapshot.name}.png")
ImageUtils.assertImageSimilar(
relativePath = expected.path,
Expand Down

0 comments on commit 2ac6a34

Please sign in to comment.