Releases: takahirom/roborazzi
1.38.0
Bugfix
Fix: Bug preventing the revival of deleted screenshot images
Thanks to @pedromfmachado's contribution, we've addressed a bug where deleted screenshot test images would be erroneously revived. Previously, when screenshot tests were removed and their corresponding images deleted from the output directory, running the record
task again would cause the Roborazzi Gradle task to recreate the removed images. While this could be temporarily resolved with roborazzi.cleanupOldScreenshots=true
, that approach was undesirable as it would also remove images when test filters were used. This was a complex issue to solve, and we extend our gratitude to @pedromfmachado for the fix.
Fix: Made screenshot dump deterministic
We have a dump feature that allows us to check the properties or hierarchy of views or composables in an image. We sorted the properties to ensure a deterministic output. Thanks to @siarhei-luskanau for this contribution.
New Feature
Feature: Added RoborazziComposeActivityThemeOption
Previously, changing the theme of RoborazziTransparentActivity
was not possible. We have now deprecated RoborazziTransparentActivity
and renamed it to RoborazziActivity
. Additionally, we added RoborazziComposeActivityThemeOption
to enable changing the Activity's theme. Thank you again, @pedromfmachado, for this feature.
You can now change the Activity theme like this:
@OptIn(ExperimentalRoborazziApi::class)
@Test
fun whenNonTransparentThemeItShouldHaveNonTransparentBackground() {
captureRoboImage(
roborazziComposeOptions = RoborazziComposeOptions {
activityTheme(android.R.style.Theme_Material_Light)
}
) {
Text("This composable function should NOT have transparent background!")
}
}
What's Changed
- Sort key order of ComposePrintToString to stabilize output in Dump screenshots. by @siarhei-luskanau in #611
- Added RoborazziComposeActivityThemeOption by @pedromfmachado in #612
- [Code Style] Add JetCodeStyleSettings to codestyle setting by @takahirom in #614
- Removing files from intermediate dir to prevent old screenshots from reappearing by @pedromfmachado in #616
- Rename theme() to activityTheme() by @takahirom in #617
New Contributors
- @pedromfmachado made their first contribution in #612
Full Changelog: 1.37.0...1.38.0
1.37.0
Bug Fixes and Workarounds
Compose AlertDialog Support
Support for capturing Alert Dialogs has been fixed, ensuring correct screenshot display. captureScreenRoboImage()
, which merges all windows, is now used only when multiple windows are present. Thanks to @ashughes for reporting this and to @mhidaka and @hiroaki404 for bringing it to my attention at a Japan Android testing event.
Workaround for Overlap Handling for SDK 35 Compose Screenshots
Problem
When updating to SDK 35 and using the default theme, the ActionBar is included in screenshots, leading to overlap when using createComposeRule()
. This occurs because createComposeRule()
uses the default theme by default. Thank you for letting me know about this, @keyboardsurfer!
I have filed an issue for this: https://issuetracker.google.com/issues/383368165
Why this happens
SDK 35 enforces edge-to-edge display, which means it does not provide automatic padding for the ActionBar.
How Roborazzi currently workarounds this
Roborazzi now automatically hides the ActionBar when taking screenshots on SDK 35 and higher. This functions similarly to view.getActivity()?.actionBar?.hide()
. However, this workaround may cause performance issues and should not be relied upon. Roborazzi outputs warning logs with alternative suggestions. Thank you to @sergio-sastre for advising on this workaround!
If you need to include the ActionBar in screenshots, Roborazzi provides a flag, roborazzi.compose.actionbar.overlap.fix=false
, that can be added to your gradle.properties
file.
What should we do?
A permanent solution will likely require changes from Google. In the meantime, adding <application android:theme="@style/android:Theme.Material.Light.NoActionBar" />
to the module's src/test/AndroidManifest.xml
file changes the Activity's default theme and resolves the issue. Thank you to @mattinger for finding this!
Stabilized Output for Dump Screenshots
Thanks to @siarhei-luskanau for contributing an improvement that reduces unnecessary diffs for the Roborazzi dump feature.
New Features
Add inspectionMode()
for Compose Previews
Thanks to @YusukeMoriJapan's contribution, inspectionMode()
is now available in RoborazziComposeOptions
. This is particularly useful when working with libraries that have specific preview implementations. This mode is off by default to maintain high fidelity.
Here's how to enable it:
captureRoboImage(
roborazziComposeOptions = RoborazziComposeOptions {
inspectionMode(true)
}
) {
...
}
What's Changed
- Fix document build by @takahirom in #597
- Sort units and actions of ComposePrintToString to stabilize output in Dump screenshots. by @siarhei-luskanau in #600
- Update API version for sample by @takahirom in #601
- add localInspectionMode property to RoborazziComposeOptions by @YusukeMoriJapan in #588
- [Refactor] Separate compose lambda test by @takahirom in #606
- Add elevation for PreviewNormal preview test by @takahirom in #608
- Fix alert dialog support by @takahirom in #555
- Add default theme test by @takahirom in #603
- Revert compileSdk and targetSdk for library by @takahirom in #609
- Add overlap workaround by @takahirom in #604
New Contributors
- @siarhei-luskanau made their first contribution in #600
Full Changelog: 1.36.0...1.37.0
1.36.0
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:
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:
roborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory
What's Changed
- [Not library dependency update for users] Update dependency org.robolectric:robolectric to v4.14.1 by @renovate in #583
- Add roborazzi.compare.output.dir gradle.properties by @takahirom in #592
- Add README section of outputDir by @takahirom in #593
- Fix documentation README tag problem by @takahirom in #594
Full Changelog: 1.35.0...1.36.0
1.35.0
Add support for heightDp, widthDp, showBackground, and backgroundColor of Compose Preview parameters
We have introduced Experimental Compose Preview Support in 1.22.0, but it does not yet support all parameters of the @Preview
annotation. Thanks to @sergio-sastre 's pull request, the Experimental Compose Preview Support now includes support for heightDp, widthDp, showBackground, and backgroundColor.
Introduce RoborazziComposeOptions
To accommodate the changes in preview parameters, we’ve made the API more flexible.
We’ve added the RoborazziComposeOptions
parameter to ComposablePreview<AndroidPreviewInfo>.captureRoboImage()
and the composable function version, captureRoboImage{}
. Previously, the API was limited; for example, you couldn’t access the ActivityScenario or the composable function directly. Now, you have full control over these components. Thank you for your code review @sergio-sastre
Here’s an example of a custom option:
captureRoboImage(
roborazziComposeOptions = RoborazziComposeOptions {
// We have several options to configure the test environment.
fontScale(2f)
// We can also configure the activity scenario and the composable content.
addOption(
object : RoborazziComposeComposableOption,
RoborazziComposeActivityScenarioOption {
override fun configureWithActivityScenario(scenario: ActivityScenario<out Activity>) {
scenario.onActivity {
it.window.decorView.setBackgroundColor(Color.BLUE)
}
}
override fun configureWithComposable(content: @Composable () -> Unit): @Composable () -> Unit {
return {
Box(Modifier
.padding(10.dp)
.background(color = androidx.compose.ui.graphics.Color.Red)
.padding(10.dp)
) {
content()
}
}
}
}
)
},
) {
Text("Hello Compose!")
}
Breaking Change: fun ComposablePreview<AndroidPreviewInfo>.applyToRobolectricConfiguration()
is now deprecated and marked as an error
As part of our updates to how composables are configured, the applyToRobolectricConfiguration()
function is now deprecated and marked as an error. Instead, you can use one of the following alternatives:
previewInfo.toRoborazziComposeOptions().apply(scenario, composeContent)
ComposablePreview<AndroidPreviewInfo>.captureRoboImage(roborazziComposeOptions)
We believe this feature is not widely used, and the migration should be straightforward. However, if you encounter any issues, please don’t hesitate to reach out.
Breaking Change: Composable captureRoboImage{}
Behavior Change
We intended to use a transparent background for the Compose captureRoboImage
function, but it was not applied due to Robolectric's resource merging mechanism. We have fixed this behavior; however, this change will result in altered screenshots. You can now specify the background in the new RoborazziComposeOptions
as we had previously.
@Test
fun captureComposeLambdaImage() {
captureRoboImage(
roborazziComposeOptions = RoborazziComposeOptions {
background(
showBackground = true
)
}
) {
Text("Hello Compose!")
}
}
What's Changed
- [Not Library dependency Update] Update dependency androidx.compose.runtime:runtime to v1.7.5 by @renovate in #548
- Add .gitignore by @takahirom in #578
- Refactor Add support for heightDp, widthDp, showBackground, backgroundColor by @takahirom in #577
- Add support for heightDp, widthDp, showBackground, backgroundColor by @sergio-sastre in #576
- Rename Applier to Config by @takahirom in #579
- Refactor naming by @takahirom in #582
- Add Assertion for minus or zero fontScale by @takahirom in #584
- Add a deprecated method of ComposablePreview.applyToRobolectricConfiguration() as it was a ExperimentalApi by @takahirom in #585
- Adjustment for compose preview config by @takahirom in #586
- Refactor RoborazziComposeConfigBuilder by @takahirom in #590
Full Changelog: 1.34.0...1.35.0
1.34.0
Stabilize RoborazziOptions and RoborazziRule.Options
RoborazziOptions and RoborazziRule.Options were not annotated with ExperimentalRoborazziApi
, but their constructor parameters had the annotation. This caused ExperimentalRoborazziApi
to be unintentionally exposed. To address this, we have separated constructors for stable parameters. Additionally, within Roborazzi samples, we have enabled allWarningsAsErrors = true
to ensure that any unexpected exposure can be promptly identified.
Thank you for reporting this issue and for your code review, @colinrtwhite!
Fixes for Accessibility Checks
We introduced Experimental Accessibility Test Framework checks in version 1.33.0.
Thanks to @yschimke's contribution, several fixes have been implemented for accessibility checks, including check suppression and improved logging.
What's Changed
- Update accessibility sample to show presets by @yschimke in #562
- Log warnings when Accessibility checks not configured correctly by @yschimke in #572
- Fix: Restore Robolectric fingerprint after accessibility checks by @yschimke in #574
- [Idea Plugin] Fix wrong variable naming in Roborazzi IntelliJ Plugin by @takahirom in #571
- Apply suppressions to AccessibilityViewCheckResult instead of filtering by @yschimke in #573
- Fix the issue where the experimental annotation was exposed when using stable APIs in Roborazzi options by @takahirom in #566
Full Changelog: 1.33.0...1.34.0
1.33.0
Experimental Accessibility Test Framework checks
Thanks to @yschimke's contribution, we now have a library that integrates accessibility checks into Roborazzi. It uses the Accessibility Test Framework to ensure accessibility.
Please add the library dependency:
testImplementation("io.github.takahirom.roborazzi:roborazzi-accessibility-check:[version]")
https://github.com/takahirom/roborazzi/tree/main/roborazzi-accessibility-check
Configure in Junit Rule
@get:Rule
val roborazziRule = RoborazziRule(
composeRule = composeTestRule,
captureRoot = composeTestRule.onRoot(),
options = Options(
roborazziAccessibilityOptions = RoborazziATFAccessibilityCheckOptions(
checker = RoborazziATFAccessibilityChecker(
checks = setOf(NoRedTextCheck()),
suppressions = matchesElements(withTestTag("suppress"))
),
failureLevel = RoborazziATFAccessibilityChecker.CheckLevel.Warning
),
// If you want to automatically check accessibility after a test, use AccessibilityCheckAfterTestStrategy.
accessibilityCheckStrategy = AccessibilityCheckAfterTestStrategy(),
)
)
Add accessibility checks
composeTestRule.onNodeWithTag("nothard").checkRoboAccessibility(
// If you don't specify options, the options in RoborazziRule will be used.
roborazziATFAccessibilityCheckOptions = RoborazziATFAccessibilityCheckOptions(
checker = RoborazziATFAccessibilityChecker(
preset = AccessibilityCheckPreset.LATEST,
),
failureLevel = RoborazziATFAccessibilityChecker.CheckLevel.Warning
)
)
Not only is this library designed to make our app accessible to everyone, but I also believe that AI agent testing will be a future trend, and we want to prepare for it. I think this could help make our app accessible to AI as well, enabling agents to interact with it, such as clicking on image buttons using content descriptions. Additionally, we can create custom checks, like NoRedTextCheck, specifically for AI.
What's Changed
- Include AI assertion results in the Roborazzi test report by @takahirom in #552
- Add modules and configurations to dependency diff by @takahirom in #558
- Update Robolectric 4.14 by @takahirom in #426
- Add basic ATF A11y checks via AccessibilityCheckAfterTestStrategy and checkRoboAccessibility by @yschimke in #557
- [Dependency for sample in Roborazzi, Not a library dependency change]Update dependency androidx.compose.foundation:foundation to v1.7.5 by @renovate in #520
Full Changelog: 1.32.2...1.33.0
1.32.2
WebP Image Comparison Improvements
Improved handling for transparent pixels in WebP image comparisons. Previously, transparent pixels in WebP images were sometimes returned with unexpected color values (e.g., r = 1, g = 0, b = 0, a = 0), which caused inconsistencies in image comparisons. The comparison logic now correctly handles transparent pixels by interpreting them as fully transparent black (r = 0, g = 0, b = 0, a = 0) to ensure consistent results across comparisons.
Special thanks to @ArcaNO93 for identifying and reporting this issue!
What's Changed
- Fix WebP comparison issue with transparent colors by @takahirom in #547
- [CI]Update dependency gradle to v8.10.2 by @renovate in #543
Full Changelog: 1.32.1...1.32.2
1.32.1
Experimental WebP support and other image formats
Now, you can set roborazzi.record.image.extension
to webp
in your gradle.properties
file to generate WebP images.
roborazzi.record.image.extension=webp
To enable WebP support, add testImplementation("io.github.darkxanter:webp-imageio:0.3.3")
to your build.gradle.kts
file.
WebP is a lossy image format by default, which can make managing image differences challenging. To address this, we provide a lossless WebP image feature.
WebP lossless images are reported to be 26% smaller in size compared to PNGs.
onView(ViewMatchers.withId(R.id.textview_first))
.captureRoboImage(
roborazziOptions = RoborazziOptions(
recordOptions = RoborazziOptions.RecordOptions(
imageIoFormat = LosslessWebPImageIoFormat(),
),
)
)
You can also use other image formats by implementing your own AwtImageWriter
and AwtImageLoader
.
data class JvmImageIoFormat(
val awtImageWriter: AwtImageWriter,
val awtImageLoader: AwtImageLoader
) : ImageIoFormat
Thank you, @ArcaNO93, for providing this suggestion and the code review!
Adjustments to AI-Powered Image Assertion
In version 1.30.0, we introduced Roborazzi AI-Powered Image Assertion.
We made some adjustments to the AI-Powered Image Assertion
- The OpenAiAiAssertionModel, which utilizes OpenAI APIs, previously lacked a timeout specification, leading to frequent timeout issues. We have now implemented a customizable timeout setting.
- The provideRoborazziContext().option was marked as
InternalRoborazziApi
despite being documented in the README. We have now changed it to ExperimentalRoborazzi API.
Changes from 1.32.0
API Key Masking in Logs for OpenAiAiAssertionModel
For users enabling logging through OpenAiAiAssertionModel.loggingEnabled
, API keys in log outputs are now masked to improve security. Previously, API keys could appear in logs, which could unintentionally expose them, especially in test reports. With this update, sensitive data is automatically masked, helping users avoid accidental exposure.
What's Changed
- Make OpenAiAiAssertionModel httpClient customizable and add timeout setting by @takahirom in #541
- Add settings panel for configuring Roborazzi tasks explanation by @sanao1006 in #537
- Add the ability to change File writers and readers and add support for lossless WebP by @takahirom in #529
- Make image extension changeable by @takahirom in #527
- fix(deps): update dependency androidx.compose.ui:ui-test-junit4 to v1.7.5 by @renovate in #526
- Make RoborazziContextImpl.options ExperimentalRoborazziApi by @takahirom in #544
- Remove API key from log when using OpenAiAiAssertionModel.loggingEnabled = true by @takahirom in #545
Full Changelog: 1.31.0...1.32.1
1.32.0
Please refer to the 1.32.1 release at https://github.com/takahirom/roborazzi/releases/tag/1.32.1
1.31.0
Added Experimental Support for Cleaning Up Old Screenshots
This release introduces the roborazzi.cleanupOldScreenshots=true
option in gradle.properties
, allowing users to automatically remove outdated screenshots. By default, this is set to false
to prevent accidental deletions when running filtered tests. Please note that enabling this option may result in unintended deletions when running filtered tests. You can use -Proborazzi.cleanupOldScreenshots=true
for CI configurations to clean up screenshots only in CI runs without affecting local settings.
This cleanup implementation may affect the cache mechanism. We have some integration tests in place, but if you notice any issues, please let us know.
Thank you, @JackEblan, for suggesting the cleanup feature!
Improve AI Assertion Error Message
In the previous release, we introduced Roborazzi AI-Powered Image Assertion.
When an AI assertion fails, it’s important to review the screenshots to understand what went wrong with the images. Therefore, I enhanced the error message to include the file paths of the images.
What's Changed
- Add cleanupOldScreenshots option by @takahirom in #539
- Improve AI assertion error message by @takahirom in #540
Full Changelog: 1.30.1...1.31.0