Skip to content

Commit

Permalink
Add roborazzi.compare.output.dir gradle.properties (#592)
Browse files Browse the repository at this point in the history
## Behavior Changes to `roborazzi.outputDir.set(file("somedir"))` in `build.gradle`

Previously, when modifying `roborazzi.outputDir`, such as setting it to `src/screenshots`, this option also affected the paths for comparison images, like `foo_compare.png`. This behavior often caused issues, such as unintentionally saving comparison images to version control systems (e.g., Git). To address this, we have discontinued this behavior. Comparison images are now saved in `build/outputs/roborazzi` by default, while the behavior for reference images remains unchanged.

For users who wish to customize the path for comparison images, we have introduced a new option: `roborazzi.compare.outputDir`.

While I don't believe there are strong use cases for this, if you want to save the comparison images in a custom directory as you did before, you can specify `roborazzi.compare.outputDir` as follows:

```kotlin
roborazzi {
  outputDir.set(file("src/screenshots"))
  compare {
    outputDir.set(file("src/screenshots"))
  }
}
```

I believe this adjustment will be highly beneficial for most use cases involving changes to `outputDir`.

> [!NOTE]
> By default, when you use `captureRoboImage("image.png")`, the image will be saved as `module/image.png`.  
> You can customize the file path strategy for the recorded image. The default strategy is `relativePathFromCurrentDirectory`. If you select `relativePathFromRoborazziContextOutputDirectory`, the file will be saved in the output directory specified by `roborazzi.outputDir`.  
> This can be configured in your `gradle.properties` file:  
>
> ```properties
> roborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory
> ```
  • Loading branch information
takahirom authored Dec 9, 2024
2 parents eba7cb6 + d1d9515 commit 0c2a00b
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ data class RoborazziOptions(
}

data class CompareOptions(
val outputDirectoryPath: String = roborazziSystemPropertyOutputDirectory(),
val outputDirectoryPath: String = roborazziSystemPropertyCompareOutputDirectory(),
val imageComparator: ImageComparator = DefaultImageComparator,
val comparisonStyle: ComparisonStyle = ComparisonStyle.Grid(),
val aiAssertionOptions: AiAssertionOptions? = null,
Expand All @@ -127,7 +127,7 @@ data class RoborazziOptions(
imageComparator: ImageComparator = DefaultImageComparator,
resultValidator: (result: ImageComparator.ComparisonResult) -> Boolean = DefaultResultValidator,
): this(
outputDirectoryPath = roborazziSystemPropertyOutputDirectory(),
outputDirectoryPath = roborazziSystemPropertyCompareOutputDirectory(),
imageComparator = imageComparator,
resultValidator = resultValidator,
)
Expand All @@ -145,7 +145,7 @@ data class RoborazziOptions(
}

constructor(
outputDirectoryPath: String = roborazziSystemPropertyOutputDirectory(),
outputDirectoryPath: String = roborazziSystemPropertyCompareOutputDirectory(),
/**
* This value determines the threshold of pixel change at which the diff image is output or not.
* The value should be between 0 and 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ fun roborazziSystemPropertyOutputDirectory(): String {
return getSystemProperty("roborazzi.output.dir", DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH)
}

@ExperimentalRoborazziApi
fun roborazziSystemPropertyCompareOutputDirectory(): String {
return getSystemProperty("roborazzi.compare.output.dir", DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH)
}

@ExperimentalRoborazziApi
fun roborazziSystemPropertyImageExtension(): String {
return getSystemProperty("roborazzi.record.image.extension", "png")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class AppModule(val rootProject: RoborazziGradleRootProject, val testProjectDir:
private val PATH = "app/build.gradle.kts"
var removeOutputDirBeforeTestTypeTask = false
var customOutputDirPath: String? = null
var customCompareOutputDirPath: String? = null

init {
addIncludeBuild()
Expand Down Expand Up @@ -312,6 +313,19 @@ dependencies {
roborazzi {
outputDir.set(file("$customOutputDirPath"))
}
""".trimIndent()
)
}
if (customCompareOutputDirPath != null) {
buildFile.appendText(
"""
roborazzi {
compare {
outputDir.set(file("$customCompareOutputDirPath"))
}
}
""".trimIndent()
)
}
Expand Down Expand Up @@ -468,6 +482,13 @@ class MainActivity : ComponentActivity() {
file.appendText("\nroborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory")
}

fun removeCompareOutputDir() {
if(!testProjectDir.root.resolve("app/build/custom_compare_outputDirectoryPath")
.deleteRecursively()){
throw IllegalStateException("Failed to delete custom_compare_outputDirectoryPath")
}
}

fun addRuleTest() {
val file =
testProjectDir.root.resolve("app/src/test/java/com/github/takahirom/integration_test_project/RoborazziTest.kt")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.rules.TestWatcher
import org.junit.runner.Description

/**
* Run this test with `cd include-build` and `./gradlew roborazzi-gradle-plugin:check`
Expand All @@ -20,6 +21,11 @@ class RoborazziGradleProjectTest {
override fun starting(description: org.junit.runner.Description?) {
println("RoborazziGradleProjectTest.${description?.methodName} started")
}

override fun finished(description: Description?) {
super.finished(description)
println("RoborazziGradleProjectTest.${description?.methodName} finished")
}
}

private val className = "com.github.takahirom.integration_test_project.RoborazziTest"
Expand Down Expand Up @@ -146,15 +152,89 @@ class RoborazziGradleProjectTest {
RoborazziGradleRootProject(testProjectDir).appModule.apply {
val customDirFromGradle = "src/screenshots/roborazzi_customdir_from_gradle"
buildGradle.customOutputDirPath = customDirFromGradle
removeRoborazziOutputDir()
val output1 = record().output
assertNotSkipped(output1)
val output2 = record().output
assertSkipped(output2)

checkResultsSummaryFileExists()
checkRecordedFileExists("app/$customDirFromGradle/$className.testCapture.png")
checkRecordedFileNotExists("$$screenshotAndName.testCapture_compare.png")
checkRecordedFileNotExists("$$screenshotAndName.testCapture_actual.png")
checkRecordedFileNotExists("$screenshotAndName.testCapture.png")
checkRecordedFileNotExists("$screenshotAndName.testCapture_compare.png")
checkRecordedFileNotExists("$screenshotAndName.testCapture_actual.png")
checkRecordedFileNotExists("app/$customDirFromGradle/$className.testCapture_compare.png")

// We should be able to use the cache
changeScreen()
removeRoborazziOutputDir()
compare()
checkRecordedFileExists("$screenshotAndName.testCapture_compare.png")
removeRoborazziOutputDir()
compare()

checkResultsSummaryFileExists()
checkRecordedFileExists("app/$customDirFromGradle/$className.testCapture.png")
checkRecordedFileExists("$screenshotAndName.testCapture_compare.png")
checkRecordedFileExists("$screenshotAndName.testCapture_actual.png")
checkRecordedFileNotExists("app/$customDirFromGradle/$className.testCapture_compare.png")
}
}

@Test
fun compareWithCompareOutputDirPathItShouldSavedInTheDirectory() {
RoborazziGradleRootProject(testProjectDir).appModule.apply {
buildGradle.customCompareOutputDirPath = "build/custom_compare_outputDirectoryPath"

record()
changeScreen()
compare()

checkResultsSummaryFileExists()
checkRecordedFileExists("$screenshotAndName.testCapture.png")
checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png")
checkRecordedFileNotExists("$screenshotAndName.testCapture_compare.png")

// We should be able to use the cache
removeRoborazziOutputDir()
compare()
removeRoborazziOutputDir()
val buildResult = compare()
assertSkipped(buildResult.output)

checkResultsSummaryFileExists()
checkRecordedFileExists("$screenshotAndName.testCapture.png")
checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png")
checkRecordedFileNotExists("$screenshotAndName.testCapture_compare.png")
}
}

@Test
fun compareWithBothOutputDirPathItShouldSavedInTheDirectory() {
RoborazziGradleRootProject(testProjectDir).appModule.apply {
buildGradle.customCompareOutputDirPath = "build/custom_compare_outputDirectoryPath"
val customDirFromGradle = "build/custom_compare_outputDirectoryPath"
buildGradle.customOutputDirPath = customDirFromGradle

record()
changeScreen()
compare()

checkResultsSummaryFileExists()
checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture.png")
checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png")
checkRecordedFileNotExists("app/build/outputs/roborazzi/$className.testCapture_compare.png")

// We should be able to use the cache
removeCompareOutputDir()
compare()
removeCompareOutputDir()
compare()

checkResultsSummaryFileExists()
checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture.png")
checkRecordedFileExists("app/build/custom_compare_outputDirectoryPath/$className.testCapture_compare.png")
checkRecordedFileNotExists("app/build/outputs/roborazzi/$className.testCapture_compare.png")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFile
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
Expand Down Expand Up @@ -49,6 +51,15 @@ private const val DEFAULT_TEMP_DIR = "intermediates/roborazzi"
open class RoborazziExtension @Inject constructor(objects: ObjectFactory) {
val outputDir: DirectoryProperty = objects.directoryProperty()

@ExperimentalRoborazziApi
val compare: RoborazziCompareExtension =
objects.newInstance(RoborazziCompareExtension::class.java)

@ExperimentalRoborazziApi
fun compare(action: Action<RoborazziCompareExtension>) {
action.execute(compare)
}

@ExperimentalRoborazziApi
val generateComposePreviewRobolectricTests: GenerateComposePreviewRobolectricTestsExtension =
objects.newInstance(GenerateComposePreviewRobolectricTestsExtension::class.java)
Expand All @@ -59,6 +70,10 @@ open class RoborazziExtension @Inject constructor(objects: ObjectFactory) {
}
}

open class RoborazziCompareExtension @Inject constructor(objects: ObjectFactory) {
val outputDir: DirectoryProperty = objects.directoryProperty()
}

val KnownImageFileExtensions: Set<String> = setOf("png", "gif", "jpg", "jpeg", "webp")

@Suppress("unused")
Expand Down Expand Up @@ -93,6 +108,8 @@ abstract class RoborazziPlugin : Plugin<Project> {
// For fixing unexpected skip test
val outputDir =
extension.outputDir.convention(project.layout.buildDirectory.dir(DEFAULT_OUTPUT_DIR))
val compareOutputDirProvider =
extension.compare.outputDir.convention(project.layout.buildDirectory.dir(DEFAULT_OUTPUT_DIR))
val testTaskOutputDir: DirectoryProperty = project.objects.directoryProperty()
val intermediateDir =
testTaskOutputDir.convention(project.layout.buildDirectory.dir(DEFAULT_TEMP_DIR))
Expand Down Expand Up @@ -234,7 +251,7 @@ abstract class RoborazziPlugin : Plugin<Project> {
}
}
}
val isImageInputUsedProvider = isVerifyRun.flatMap { isVerifyTaskRun ->
val isCompareOrVerifyRunProvider = isVerifyRun.flatMap { isVerifyTaskRun ->
isCompareRun.map { isCompareTaskRun ->
isVerifyTaskRun || isCompareTaskRun
}
Expand All @@ -248,6 +265,7 @@ abstract class RoborazziPlugin : Plugin<Project> {
project.projectDir.absolutePath
}
val outputDirRelativePathFromProjectProvider = outputDir.map { project.relativePath(it) }

val resultDirFileProperty =
project.layout.buildDirectory.dir(RoborazziReportConst.resultDirPathFromBuildDir)
val resultDirFileTree =
Expand All @@ -270,11 +288,6 @@ abstract class RoborazziPlugin : Plugin<Project> {
finalizeTestTask.infoln("Roborazzi: roborazziTestFinalizer.onlyIf doesRoborazziRun $doesRoborazziRun")
doesRoborazziRun
}
val taskPath = if (project.path == ":") {
":"
} else {
project.path + ":"
}
finalizeTestTask.doLast {
val startCopy = System.currentTimeMillis()
intermediateDir.get().asFile.mkdirs()
Expand All @@ -289,12 +302,12 @@ abstract class RoborazziPlugin : Plugin<Project> {
testTaskProvider
.configureEach { test: AbstractTestTask ->
val resultsDir = resultDirFileProperty.get().asFile
test.inputs.files(
isImageInputUsedProvider.map { isImageInputUsed ->
val imageInputProvider: Provider<FileCollection> =
isCompareOrVerifyRunProvider.flatMap { isImageInputUsed ->
if (!isImageInputUsed) {
// Note: this is not files in outputDir,
// but empty input when running in record mode.
outputDir.get().files()
outputDir.map { it.files(/* this means empty files*/) }
} else if (restoreOutputDirRoborazziTaskProvider.isPresent) {
// Previous outputs are an input when running in compare or verify mode.
// However, during record runs the output dir might not exist yet, so we use
Expand All @@ -317,7 +330,38 @@ abstract class RoborazziPlugin : Plugin<Project> {
}
}
}
test.inputs.files(
imageInputProvider
)
test.outputs.dirs(
compareOutputDirProvider.flatMap { compareOutputDir: Directory ->
isCompareOrVerifyRunProvider.flatMap { isCompareOrVerifyRun ->
imageInputProvider
.map { imageInput: FileCollection ->
if (!isCompareOrVerifyRun) {
// If it is not compare or verify, we don't need to output anything for comparison
outputDir.files(/* empty files */)
} else if (imageInput.files.any {
// Check if the compare output directory is the same as the input directory
if (it.isDirectory) {
it.absolutePath == compareOutputDir.asFile.absolutePath
} else {
it.parentFile.absolutePath == compareOutputDir.asFile.absolutePath
}
}) {
outputDir.files(/* empty files */)
} else {
if (!compareOutputDir.asFile.exists()) {
compareOutputDir.asFile.mkdirs()
}
compareOutputDir
}
}
}
}.map {
test.infoln("Roborazzi: Set output dir ${it} to test task")
it
})
test.outputs.dir(intermediateDirForEachVariant.map {
test.infoln("Roborazzi: Set output dir $it to test task")
it
Expand Down Expand Up @@ -374,6 +418,10 @@ abstract class RoborazziPlugin : Plugin<Project> {
// Other properties
test.systemProperties["roborazzi.output.dir"] =
outputDirRelativePathFromProjectProvider.get()
if (compareOutputDirProvider.isPresent) {
test.systemProperties["roborazzi.compare.output.dir"] =
compareOutputDirProvider.get()
}
test.systemProperties["roborazzi.result.dir"] =
resultDirRelativePathFromProjectProvider.get()
test.systemProperties["roborazzi.project.path"] =
Expand Down

0 comments on commit 0c2a00b

Please sign in to comment.