{"id":18007071,"url":"https://github.com/takahirom/roborazzi","last_synced_at":"2026-01-06T08:17:14.991Z","repository":{"id":65700355,"uuid":"594307070","full_name":"takahirom/roborazzi","owner":"takahirom","description":"Make JVM Android integration test visible 🤖📸","archived":false,"fork":false,"pushed_at":"2025-05-11T04:47:02.000Z","size":167158,"stargazers_count":811,"open_issues_count":94,"forks_count":40,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-05-11T05:23:51.035Z","etag":null,"topics":["android","compose","compose-desktop","compose-multiplatform","testing"],"latest_commit_sha":null,"homepage":"https://takahirom.github.io/roborazzi/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/takahirom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-01-28T05:52:02.000Z","updated_at":"2025-05-11T04:41:18.000Z","dependencies_parsed_at":"2023-12-21T02:30:26.212Z","dependency_job_id":"e5bbcebc-5424-42b5-9223-c2a7bdbbe2b7","html_url":"https://github.com/takahirom/roborazzi","commit_stats":{"total_commits":1291,"total_committers":21,"mean_commits":"61.476190476190474","dds":"0.16421378776142526","last_synced_commit":"156668549536dd3658ee488f80d5912566a74c40"},"previous_names":[],"tags_count":135,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takahirom%2Froborazzi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takahirom%2Froborazzi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takahirom%2Froborazzi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/takahirom%2Froborazzi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/takahirom","download_url":"https://codeload.github.com/takahirom/roborazzi/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254264771,"owners_count":22041794,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","compose","compose-desktop","compose-multiplatform","testing"],"created_at":"2024-10-30T01:11:59.060Z","updated_at":"2026-01-06T08:17:14.985Z","avatar_url":"https://github.com/takahirom.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- Generated by ./gradlew generateReadme. Do not edit this file. --\u003e\n\u003c!-- topic_top --\u003e\n\n\u003c!-- Generated by docs/topics/top.md. Do not edit this file. --\u003e\n# Roborazzi\n\n**Make JVM Android Integration Test Visible**\n\n## Roborazzi now supports [Robolectric Native Graphics (RNG)](https://github.com/robolectric/robolectric/releases/tag/robolectric-4.10) and enables screenshot testing.📣\n\n## Why Choose Roborazzi?\n\n### Why is screenshot testing important?\n\nScreenshot testing is key to validate your app's appearance and functionality. It efficiently\ndetects visual issues and tests the app as users would use it, making it easier to spot problems.\nIt's quicker than writing many assert statements, ensuring your app looks right and behaves\ncorrectly.\n\n### What are JVM tests and why test with JVM instead of on Android?\n\nJVM tests, also known as local tests, are placed in the test/ directory and are run on a developer's\nPC or CI environment. On the other hand, device tests, also known as Instrumentation tests, are\nwritten in the androidTest/ directory and are run on real devices or emulators. Device testing can\nresult in frequent failures due to the device environment, leading to false negatives. These\nfailures are often hard to reproduce, making them tough to resolve.\n\n### Paparazzi and Roborazzi: A Comparison\n\nPaparazzi is a great tool for visualizing displays within the JVM. However, it's incompatible with\nRobolectric, which also mocks the Android framework.\n\nRoborazzi fills this gap. It integrates with Robolectric, allowing tests to run with Hilt and\ninteract with components. Essentially, Roborazzi enhances Paparazzi's capabilities, providing a more\nefficient and reliable testing process by capturing screenshots with Robolectric.\n\n**Leveraging Roborazzi in Test Architecture: An Example**\n\n\u003cimg src=\"https://github.com/takahirom/roborazzi/assets/1386930/937a96a4-f637-4029-87e1-c1bb94abc8ae\" width=\"320\" /\u003e\n\n\n**Integrating Roborazzi into the Architecture: An Example from DroidKaigi 2023 App**\n\nIn the DroidKaigi 2023 app, Roborazzi was introduced from the early stages of development as part of the architectural design. This integration allowed the team to verify changes throughout the development process. The specific architectural decisions and how they were implemented can be found [README](https://github.com/DroidKaigi/conference-app-2023#screenshot-testing-with-robolectric-native-graphics-rng-and-roborazzi).\n\n\u003c!-- end --\u003e\n\u003c!-- topic_try_it_out --\u003e\n\n\u003c!-- Generated by docs/topics/try_it_out.md. Do not edit this file. --\u003e\n# Try it out\n\nAvailable on Maven Central.\n\n### Add Robolectric\n\nThis is an example of adding Robolectric to your project:\nhttps://github.com/takahirom/roborazzi-usage-examples/compare/b697...5c12\n\nThis library is dependent on Robolectric. Please see below to add Robolectric.\n\nhttps://robolectric.org/getting-started/\n\n\n### Add Roborazzi\n\nThis is an example of adding Roborazzi to your project:\nhttps://github.com/takahirom/roborazzi-usage-examples/commit/3a02\n\nTo take screenshots, please use Robolectric 4.10 alpha 1 or later and please\nadd `@GraphicsMode(GraphicsMode.Mode.NATIVE)` to your test class.\n\n```kotlin\n@GraphicsMode(GraphicsMode.Mode.NATIVE)\n```\n\n\u003c!-- end --\u003e\n\u003c!-- topic_build_setup --\u003e\n\n\u003c!-- Generated by docs/topics/build_setup.md. Do not edit this file. --\u003e\n# Build setup\n\nRoborazzi is available on maven central.\n\nThis plugin simply creates Gradle tasks record, verify, compare and passes the configuration to the\ntest.\n\n**build.gradle.kts**\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003eplugins\u003c/td\u003e\u003ctd\u003ebuildscript\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n\nDefine plugin in root build.gradle.kts\n\n```kotlin\nplugins {\n  ...\n  id(\"io.github.takahirom.roborazzi\") version \"[version]\" apply false\n}\n```\n\nApply plugin in module build.gradle.kts\n\n```kotlin\nplugins {\n  ...\n  id(\"io.github.takahirom.roborazzi\")\n}\n```\n\n\u003c/td\u003e\u003ctd\u003e\n\nroot build.gradle.kts\n\n```kotlin\nbuildscript {\n  dependencies {\n    ...\n    classpath(\"io.github.takahirom.roborazzi:roborazzi-gradle-plugin:[version]\")\n  }\n}\n```\n\nmodule build.gradle.kts\n\n```kotlin\nplugins {\n    ...\n    id(\"io.github.takahirom.roborazzi\")\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\n\u003c/table\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ebuild.gradle version\u003c/summary\u003e\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003eplugins\u003c/td\u003e\u003ctd\u003ebuildscript\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n\nDefine plugin in root build.gradle\n\n```groovy\nplugins {\n  ...\n  id \"io.github.takahirom.roborazzi\" version \"[version]\" apply false\n}\n```\n\nApply plugin in module build.gradle\n\n```groovy\nplugins {\n  ...\n  id 'io.github.takahirom.roborazzi'\n}\n```\n\n\u003c/td\u003e\u003ctd\u003e\n\nroot build.gradle\n\n```groovy\nbuildscript {\n  dependencies {\n    ...\n    classpath \"io.github.takahirom.roborazzi:roborazzi-gradle-plugin:[version]\"\n  }\n}\n```\n\nmodule build.gradle\n\n```groovy\napply plugin: \"io.github.takahirom.roborazzi\"\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\n\u003c/table\u003e\n\u003c/details\u003e\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd\u003e Use Roborazzi task \u003c/td\u003e \u003ctd\u003e Use default unit test task \u003c/td\u003e \u003ctd\u003e Description \u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n`./gradlew recordRoborazziDebug`\n\n\n\u003c/td\u003e\u003ctd\u003e \n\n`./gradlew testDebugUnitTest` after adding `roborazzi.test.record=true` to your gradle.properties file.\n\nor\n\n`./gradlew testDebugUnitTest -Proborazzi.test.record=true`\n\n\n\u003c/td\u003e\u003ctd\u003e \n\nRecord a screenshot  \nDefault output directory is `build/outputs/roborazzi`  \nYou can check a report under `build/reports/roborazzi/index.html`\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n`./gradlew compareRoborazziDebug`\n\n\u003c/td\u003e\u003ctd\u003e \n\n\n`./gradlew testDebugUnitTest` after adding `roborazzi.test.compare=true` to your gradle.properties file.\n\nor\n\n`./gradlew testDebugUnitTest -Proborazzi.test.compare=true`\n\n\u003c/td\u003e\u003ctd\u003e\n\nReview changes made to an image. This action will\ncompare the current image with the saved one, generating a comparison image labeled\nas `[original]_compare.png`. It also produces a JSON file containing the diff information, which can\nbe found under `build/test-results/roborazzi`.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n`./gradlew verifyRoborazziDebug`\n\n\u003c/td\u003e\u003ctd\u003e \n\n\n`./gradlew testDebugUnitTest` after adding `roborazzi.test.verify=true` to your gradle.properties file.\n\nor\n\n`./gradlew testDebugUnitTest -Proborazzi.test.verify=true`\n\n\u003c/td\u003e\u003ctd\u003e\n\nValidate changes made to an image. If there is any difference between the current image and the\nsaved one, the test will fail.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n`./gradlew verifyAndRecordRoborazziDebug`\n\n\u003c/td\u003e\u003ctd\u003e \n\n\n`./gradlew testDebugUnitTest` after adding  `roborazzi.test.verify=true` and `roborazzi.test.record=true` to your gradle.properties file.\n\nor\n\n`./gradlew testDebugUnitTest -Proborazzi.test.verify=true -Proborazzi.test.record=true`\n\n\u003c/td\u003e\u003ctd\u003e\n\nThis task will first verify the images and, if differences are detected, it will record a new\nbaseline.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd\u003e\n\n`./gradlew clearRoborazziDebug`\n\n\u003c/td\u003e\u003ctd\u003e \n\nThis is not a test task.\n\n\u003c/td\u003e\u003ctd\u003e\n\nNote: This is an experimental task.\nThis task will clear the saved images. This task also deletes the cached images.\nPlease be careful when using this task.\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\n\n\u003c/table\u003e\n\nThe comparison image, saved as `[original]_compare.png`, is shown below:\n\n![image](https://github.com/takahirom/roborazzi/assets/1386930/722090ff-77c4-4a04-a0e3-8ce562ffa6be)\n\nYou can check the test report in `build/reports/roborazzi/index.html`\n\n\u003cimg width=\"400\" alt=\"image\" src=\"https://github.com/takahirom/roborazzi/assets/1386930/7834a436-1927-438d-8656-61f583ae3f48\" /\u003e\n\n\nThis\nuses [JetNew from Compose Samples](https://github.com/android/compose-samples/tree/main/JetNews).\nYou can check the pull request introducing Roborazzi to the\ncompose-samples [here](https://github.com/takahirom/compose-samples/pull/1/files).\n\n## Gradle DSL Options (Optional)\nRoborazzi uses `module/build/outputs/roborazzi` as the default directory for reference images and comparison images. You can customize these paths in your `build.gradle` file:\n\n```kotlin\nroborazzi {\n  // Directory for reference images\n  outputDir.set(file(\"src/screenshots\"))\n  // Directory for comparison images (Experimental option)\n  compare {\n    outputDir.set(file(\"build/outputs/screenshots_comparison\"))\n  }\n}\n```\n\n\u003e [!NOTE]\n\u003e By default, when you use `captureRoboImage(\"image.png\")`, the image will be saved as `module/image.png`.  \n\u003e 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`.  \n\u003e This can be configured in your `gradle.properties` file:  \n\u003e\n\u003e ```\n\u003e roborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory\n\u003e ```\n\n### Add dependencies\n\n| Description     | Dependencies                                                                         |\n|-----------------|--------------------------------------------------------------------------------------|\n| Core functions  | `testImplementation(\"io.github.takahirom.roborazzi:roborazzi:[version]\")`            |\n| Jetpack Compose | `testImplementation(\"io.github.takahirom.roborazzi:roborazzi-compose:[version]\")`    |\n| JUnit rules     | `testImplementation(\"io.github.takahirom.roborazzi:roborazzi-junit-rule:[version]\")` |\n\n\u003c!-- end --\u003e\n\u003c!-- topic_how_to_use --\u003e\n\n\u003c!-- Generated by docs/topics/how_to_use.md. Do not edit this file. --\u003e\n# How to use\n\n### Take a screenshot manually\n\nYou can take a screenshot by calling captureRoboImage().\n\napp/src/test/java/../ManualTest.kt\n\n```kotlin\nimport androidx.compose.ui.test.junit4.createAndroidComposeRule\nimport androidx.test.core.app.ActivityScenario\nimport androidx.test.espresso.Espresso.onView\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.robolectric.annotation.GraphicsMode\n\n// All you need to do is use the captureRoboImage function in the test!\nimport com.github.takahirom.roborazzi.captureRoboImage\n\n\n// Tips: You can use Robolectric while using AndroidJUnit4\n@RunWith(AndroidJUnit4::class)\n// Enable Robolectric Native Graphics (RNG) \n@GraphicsMode(GraphicsMode.Mode.NATIVE)\nclass ManualTest {\n  @get:Rule\n  val composeTestRule = createAndroidComposeRule\u003cMainActivity\u003e()\n\n  @Test\n  fun captureRoboImageSample() {\n    // Tips: You can use Robolectric with Espresso API\n    // launch\n    ActivityScenario.launch(MainActivity::class.java)\n\n    // Capture screen\n    onView(ViewMatchers.isRoot())\n      // If you don't specify a screenshot file name, Roborazzi will automatically use the method name as the file name for you.\n      // The format of the file name will be as follows:\n      // build/outputs/roborazzi/com_..._ManualTest_captureRoboImageSample.png\n      .captureRoboImage()\n\n    // Capture Jetpack Compose Node\n    composeTestRule.onNodeWithTag(\"AddBoxButton\")\n      .onParent()\n      .captureRoboImage(\"build/compose.png\")\n  }\n}\n```\n\nRoborazzi supports the following APIs.\n\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003eCapture\u003c/td\u003e\u003ctd\u003eCode\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n✅ Jetpack Compose's onNode()\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\ncomposeTestRule.onNodeWithTag(\"AddBoxButton\")\n  .captureRoboImage()\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n✅ Espresso's onView()\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\nonView(ViewMatchers.isRoot())\n  .captureRoboImage()\n```\n\n```kotlin\nonView(withId(R.id.button_first))\n  .captureRoboImage()\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n✅ View\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\nval view: View = composeTestRule.activity.findViewById\u003cView\u003e(R.id.button_second)\nview.captureRoboImage()\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\u003ctd\u003e\n✅ Jetpack Compose lambda\n\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\ncaptureRoboImage() {\n  Text(\"Hello Compose!\")\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\u003ctd\u003e\nExperimental🧪\u003cbr/\u003e \n✅ Captures the entire screen, including dialogs\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\ncaptureScreenRoboImage()\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\u003ctd\u003e\n✅ Bitmap\n\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\nval bitmap: Bitmap = createBitmap(100, 100, Bitmap.Config.ARGB_8888)\n  .apply {\n    applyCanvas {\n      drawColor(android.graphics.Color.YELLOW)\n    }\n  }\nbitmap.captureRoboImage()\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\n\u003c/table\u003e\n\n### Device configuration\n\nYou can configure the device by using the `@Config` annotation and `RobolectricDeviceQualifiers`.\n\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003eConfiguration\u003c/td\u003e\u003ctd\u003eCode\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n✅ Predefined device configuration\n\u003c/td\u003e\u003ctd\u003e\n\nYou can change the device configuration by adding `@Config` to the class or method.\n\n```kotlin\n@RunWith(AndroidJUnit4::class)\n@GraphicsMode(GraphicsMode.Mode.NATIVE)\n@Config(qualifiers = RobolectricDeviceQualifiers.Pixel5)\nclass RoborazziTest {\n```\n\n```kotlin\n@Test\n@Config(qualifiers = RobolectricDeviceQualifiers.Pixel5)\nfun test() {\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n✅ Night mode\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\n@Config(qualifiers = \"+night\")\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n✅ Locale\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\n@Config(qualifiers = \"+ja\")\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e\n✅ Screen size\n\u003c/td\u003e\u003ctd\u003e\n\n```kotlin\n@Config(qualifiers = RobolectricDeviceQualifiers.MediumTablet)\n```\n\n\u003c/td\u003e\u003c/tr\u003e\n\n\u003c/table\u003e\n\n### Integrate to your GitHub Actions\n\nIt is easy to integrate Roborazzi to your GitHub Actions.\n\n#### Add a job to store screenshots\n\n```yaml\nname: store screenshots\n\non:\n  push\n\nenv:\n  GRADLE_OPTS: \"-Dorg.gradle.jvmargs=-Xmx6g -Dorg.gradle.daemon=false -Dkotlin.incremental=false\"\n\njobs:\n  test:\n    runs-on: macos-latest\n\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-java@v3.9.0\n        with:\n          distribution: 'zulu'\n          java-version: 19\n\n      - name: Gradle cache\n        uses: gradle/gradle-build-action@v2\n\n      - name: test\n        run: |\n          # Create screenshots\n          ./gradlew app:recordRoborazziDebug --stacktrace\n\n      # Upload screenshots to GitHub Actions Artifacts\n      - uses: actions/upload-artifact@v3\n        with:\n          name: screenshots\n          path: app/build/outputs/roborazzi\n          retention-days: 30\n```\n\n#### Add a job to verify screenshots\n\n```yaml\nname: verify test\n\non:\n  push\n\nenv:\n  GRADLE_OPTS: \"-Dorg.gradle.jvmargs=-Xmx6g -Dorg.gradle.daemon=false -Dkotlin.incremental=false\"\n\njobs:\n  test:\n    runs-on: macos-latest\n\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-java@v3.9.0\n        with:\n          distribution: 'zulu'\n          java-version: 19\n\n      - name: Gradle cache\n        uses: gradle/gradle-build-action@v2\n\n      # Download screenshots from main branch\n      - uses: dawidd6/action-download-artifact@v2\n        with:\n          name: screenshots\n          path: app/build/outputs/roborazzi\n          workflow: test.yaml\n          branch: main\n\n      - name: verify test\n        id: verify-test\n        run: |\n          # If there is a difference between the screenshots, the test will fail.\n          ./gradlew app:verifyRoborazziDebug --stacktrace\n\n      - uses: actions/upload-artifact@v3\n        if: ${{ always() }}\n        with:\n          name: screenshot-diff\n          path: app/build/outputs/roborazzi\n          retention-days: 30\n\n      - uses: actions/upload-artifact@v3\n        if: ${{ always() }}\n        with:\n          name: screenshot-diff-reports\n          path: app/build/reports\n          retention-days: 30\n\n      - uses: actions/upload-artifact@v3\n        if: ${{ always() }}\n        with:\n          name: screenshot-diff-test-results\n          path: app/build/test-results\n          retention-days: 30\n\n```\n\n#### Advanced workflow Sample: Compare Snapshot Results on Pull Requests\n\nFor those who are looking for a more advanced example, we have prepared a sample repository that\ndemonstrates how to use Roborazzi to compare snapshot results on GitHub pull requests. This sample\nshowcases the integration of Roborazzi with GitHub Actions workflows, making it easy to visualize\nand review the differences between snapshots directly in the pull request comments.\n\nCheck out\nthe [roborazzi-compare-on-github-comment-sample](https://github.com/takahirom/roborazzi-compare-on-github-comment-sample)\nrepository to see this powerful feature in action and learn how to implement it in your own\nprojects.\n\nExample of the comment\n\n\u003cimg src=\"https://user-images.githubusercontent.com/1386930/236480693-80483cde-53fe-4c04-ba1f-2352e14b5f15.png\" width=\"600\" /\u003e\n\n## RoborazziRule (Optional)\n\nRoborazziRule is a JUnit rule for Roborazzi.\nRoborazziRule is **optional**. You can use `captureRoboImage()` without this rule.\n\nRoborazziRule has two features.\n\n1. Provide context such as `RoborazziOptions` and `outputDirectoryPath` etc for `captureRoboImage()`.\n2. Capture screenshots for each test when specifying RoborazziRule.options.captureType.\n\nFor example, The following code generates an output file\nnamed `**custom_outputDirectoryPath**/**custom_outputFileProvider**-com.github.takahirom.roborazzi.sample.RuleTestWithPath.captureRoboImage.png` :\n\n```kotlin\n@RunWith(AndroidJUnit4::class)\n@GraphicsMode(GraphicsMode.Mode.NATIVE)\nclass RuleTestWithPath {\n  @get:Rule\n  val roborazziRule = RoborazziRule(\n    options = Options(\n      outputDirectoryPath = \"$DEFAULT_ROBORAZZI_OUTPUT_DIR_PATH/custom_outputDirectoryPath\",\n      outputFileProvider = { description, outputDirectory, fileExtension -\u003e\n        File(\n          outputDirectory,\n          \"custom_outputFileProvider-${description.testClass.name}.${description.methodName}.$fileExtension\"\n        )\n      }\n    ),\n  )\n\n  @Test\n  fun captureRoboImage() {\n    launch(MainActivity::class.java)\n    // The file will be saved using the rule's outputDirectoryPath and outputFileProvider\n    onView(isRoot()).captureRoboImage()\n  }\n}\n```\n\n### Generate gif image\n\n```kotlin\n@Test\nfun captureRoboGifSample() {\n  onView(ViewMatchers.isRoot())\n    .captureRoboGif(\"build/test.gif\") {\n      // launch\n      ActivityScenario.launch(MainActivity::class.java)\n      // move to next page\n      onView(withId(R.id.button_first))\n        .perform(click())\n      // back\n      pressBack()\n      // move to next page\n      onView(withId(R.id.button_first))\n        .perform(click())\n    }\n}\n```\n\n\u003cimg width=\"350\" src=\"https://user-images.githubusercontent.com/1386930/226362212-35d34c9e-6df1-4671-8949-10fad7ad98c9.gif\" /\u003e\n\n### Generate gif with test rule\n\n\u003e **Note**  \n\u003e You **don't need to use RoborazziRule** if you're using captureRoboImage().\n\nWith the JUnit test rule, you do not need to name the gif image,\nand if you prefer, you can output the gif image **only if the test fails**.\n\nThis test will output this file.\n\n`build/outputs/roborazzi/com.github.takahirom.roborazzi.sample.RuleTestWithOnlyFail_captureRoboGifSampleFail.gif`\n\n```kotlin\n@RunWith(AndroidJUnit4::class)\n@GraphicsMode(GraphicsMode.Mode.NATIVE)\nclass RuleTestWithOnlyFail {\n  @get:Rule\n  val roborazziRule = RoborazziRule(\n    captureRoot = onView(isRoot()),\n    options = Options(\n      onlyFail = true,\n      captureType = RoborazziRule.CaptureType.Gif,\n    )\n  )\n\n  @Test\n  fun captureRoboLastImageSampleFail() {\n    // launch\n    ActivityScenario.launch(MainActivity::class.java)\n    // move to next page\n    onView(withId(R.id.button_first))\n      .perform(click())\n    // should fail because the button does not exist\n    // Due to failure, the gif image will be saved in the outputs folder.\n    onView(withId(R.id.button_first))\n      .perform(click())\n  }\n}\n```\n\n### Generate Jetpack Compose gif with test rule\n\nTest target\n\n```kotlin\n@Composable\nfun SampleComposableFunction() {\n  var count by remember { mutableStateOf(0) }\n  Column(\n    Modifier\n      .size(300.dp)\n  ) {\n    Box(\n      Modifier\n        .testTag(\"AddBoxButton\")\n        .size(50.dp)\n        .clickable {\n          count++\n        }\n    )\n    (0..count).forEach {\n      Box(\n        Modifier\n          .size(30.dp)\n      )\n    }\n  }\n}\n```\n\nTest (Just add RoborazziRule)\n\n```kotlin\n@RunWith(AndroidJUnit4::class)\n@GraphicsMode(GraphicsMode.Mode.NATIVE)\nclass ComposeTest {\n  @get:Rule\n  val composeTestRule = createAndroidComposeRule\u003cComponentActivity\u003e()\n\n  @get:Rule\n  val roborazziRule = RoborazziRule(\n    composeRule = composeTestRule,\n    captureRoot = composeTestRule.onRoot(),\n    options = RoborazziRule.Options(\n      RoborazziRule.CaptureType.Gif()\n    )\n  )\n\n  @Test\n  fun composable() {\n    composeTestRule.setContent {\n      SampleComposableFunction()\n    }\n    (0 until 3).forEach { _ -\u003e\n      composeTestRule\n        .onNodeWithTag(\"AddBoxButton\")\n        .performClick()\n    }\n  }\n}\n```\n\n![com github takahirom roborazzi sample ComposeTest_composable](https://user-images.githubusercontent.com/1386930/226366224-b9950b60-26a2-489e-bc03-08bfb86c533a.gif)\n\n### RoborazziRule options\n\nYou can use some RoborazziRule options\n\n```kotlin\n/**\n * This rule is a JUnit rule for roborazzi.\n * This rule is optional. You can use [captureRoboImage] without this rule.\n *\n * This rule have two features.\n * 1. Provide context such as `RoborazziOptions` and `outputDirectoryPath` etc for [captureRoboImage].\n * 2. Capture screenshots for each test when specifying RoborazziRule.options.captureType.\n */\nclass RoborazziRule private constructor(\n  private val captureRoot: CaptureRoot,\n  private val options: Options = Options()\n) : TestWatcher() {\n  /**\n   * If you add this annotation to the test, the test will be ignored by\n   * roborazzi's CaptureType.LastImage, CaptureType.AllImage and CaptureType.Gif.\n   */\n  annotation class Ignore\n\n  data class Options(\n    val captureType: CaptureType = CaptureType.None,\n    /**\n     * output directory path\n     */\n    val outputDirectoryPath: String = provideRoborazziContext().outputDirectory,\n\n    val outputFileProvider: FileProvider = provideRoborazziContext().fileProvider\n      ?: defaultFileProvider,\n    val roborazziOptions: RoborazziOptions = provideRoborazziContext().options,\n  )\n\n  sealed interface CaptureType {\n    /**\n     * Do not generate images. Just provide the image path to [captureRoboImage].\n     */\n    object None : CaptureType\n\n    /**\n     * Generate last images for each test\n     */\n    data class LastImage(\n      /**\n       * capture only when the test fail\n       */\n      val onlyFail: Boolean = false,\n    ) : CaptureType\n\n    /**\n     * Generate images for Each layout change like TestClass_method_0.png for each test\n     */\n    data class AllImage(\n      /**\n       * capture only when the test fail\n       */\n      val onlyFail: Boolean = false,\n    ) : CaptureType\n\n    /**\n     * Generate gif images for each test\n     */\n    data class Gif(\n      /**\n       * capture only when the test fail\n       */\n      val onlyFail: Boolean = false,\n    ) : CaptureType\n  }\n```\n\n#### Image comparator custom settings\nWhen comparing images, you may encounter differences due to minor changes related to antialiasing. You can use the options below to avoid this.\n```kotlin\n@get:Rule\nval roborazziRule = RoborazziRule(\n    options = RoborazziRule.Options(\n        roborazziOptions = RoborazziOptions(\n            compareOptions = RoborazziOptions.CompareOptions(\n                changeThreshold = 0.01, // For 1% accepted difference\n                imageComparator = SimpleImageComparator(\n                    maxDistance = 0.007F, // 0.001F is default value from Differ\n                    vShift = 2, // Increasing the shift can help resolve antialiasing issues\n                    hShift = 2 // Increasing the shift can help resolve antialiasing issues\n                )\n            )\n        )\n    )\n)\n```\n\n### Experimental WebP support and other image formats\n\nYou can set `roborazzi.record.image.extension` to `webp` in your `gradle.properties` file to generate WebP images.\n\n```kotlin\nroborazzi.record.image.extension=webp\n```\n\nWebP is a lossy image format by default, which can make managing image differences challenging. To address this, we provide a lossless WebP image comparison feature.\nTo enable WebP support, add `testImplementation(\"io.github.darkxanter:webp-imageio:0.3.3\")` to your `build.gradle.kts` file.\n\n```kotlin\nonView(ViewMatchers.withId(R.id.textview_first))\n  .captureRoboImage(\n    roborazziOptions = RoborazziOptions(\n      recordOptions = RoborazziOptions.RecordOptions(\n        imageIoFormat = LosslessWebPImageIoFormat(),\n      ),\n    )\n  )\n```\n\nYou can also use other image formats by implementing your own `AwtImageWriter` and `AwtImageLoader`.\n\n```kotlin\ndata class JvmImageIoFormat(\n  val awtImageWriter: AwtImageWriter,\n  val awtImageLoader: AwtImageLoader\n) : ImageIoFormat\n\n```\n\n### Dump mode\n\nIf you are having trouble debugging your test, try Dump mode as follows.\n\n![image](https://user-images.githubusercontent.com/1386930/226364158-a07a0fb0-d8e7-46b7-a495-8dd217faaadb.png)\n\n### Accessibility Check\n\nRoborazzi Accessibility Checks is a library that integrates accessibility checks into Roborazzi.\nPlease refer to [Accessibility Check](https://github.com/takahirom/roborazzi/blob/main/roborazzi-accessibility-check/README.md)\n\n### Roborazzi options\n\nPlease check out [RoborazziOptions](https://github.com/takahirom/roborazzi/blob/main/include-build/roborazzi-core/src/commonJvmMain/kotlin/com/github/takahirom/roborazzi/RoborazziOptions.kt) for available Roborazzi options.\n\n\u003c!-- end --\u003e\n\u003c!-- topic_preview_support --\u003e\n\n\u003c!-- Generated by docs/topics/preview_support.md. Do not edit this file. --\u003e\n# Experimental Compose Preview Support\n\nRoborazzi provides support for generating screenshot tests and easy setup for Jetpack Compose Preview.\nThis support uses [ComposablePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner) to scan the Composable Previews in your project.\n\n## Generate Compose Preview screenshot tests\n\nYou first need to add the Roborazzi plugin to your project. Please refer to the [setup guide](https://takahirom.github.io/roborazzi/build-setup.html) for more information.\nThen you can enable the Compose Preview screenshot test generation feature by adding the following configuration to your `build.gradle.kts` file:\n\n```kotlin\nroborazzi {\n  generateComposePreviewRobolectricTests {\n    enable = true\n  }\n}\n```\n\nThe plugin will not automatically change your settings or add dependencies to prevent conflicts with your existing setup. However, it will provide instructions on what to do next, such as adding dependencies and required code. You can also check the [sample project](https://github.com/takahirom/roborazzi/tree/main/sample-generate-preview-tests) for a complete example.\n\nAfter that, you can run the `recordRoborazziDebug` task to generate screenshots using the generated tests, as described in the [setup guide](https://takahirom.github.io/roborazzi/build-setup.html).\n\n### Customizing the Preview screenshot test\n\nYou can customize the generated test by adding the following configuration to your `build.gradle.kts` file:\n\n```kotlin\nroborazzi {\n  @OptIn(ExperimentalRoborazziApi::class)\n  generateComposePreviewRobolectricTests {\n    enable = true\n    // The package names to scan for Composable Previews.\n    packages = listOf(\"com.example\")\n    // robolectricConfig will be passed to Robolectric's @Config annotation in the generated test class.\n    // See https://robolectric.org/configuring/ for more information.\n    robolectricConfig = mapOf(\n      \"sdk\" to \"[32]\",\n      \"qualifiers\" to \"RobolectricDeviceQualifiers.Pixel5\",\n    )\n    // If true, the private previews will be included in the test.\n    includePrivatePreviews = true\n    // The fully qualified class name of the custom test class that implements [com.github.takahirom.roborazzi.ComposePreviewTester].\n    testerQualifiedClassName = \"com.example.MyCustomComposePreviewTester\"\n    // The number of test classes to generate. Set this to match maxParallelForks for parallel test execution.\n    generatedTestClassCount = 4\n  }\n}\n```\n\n#### Advanced: Custom ComposePreviewTester Implementation\n\nYou can create a custom `ComposePreviewTester` to control the screenshot capture behavior, such as setting a custom image comparison threshold:\n\n```kotlin\nimport com.dropbox.differ.SimpleImageComparator\nimport com.github.takahirom.roborazzi.*\n\n@ExperimentalRoborazziApi\nclass MyCustomComposePreviewTester(\n  private val defaultCapturer: AndroidComposePreviewTester.Capturer = AndroidComposePreviewTester.DefaultCapturer()\n) : AndroidComposePreviewTester(\n  capturer = { parameter -\u003e\n    val customOptions = parameter.roborazziOptions.copy(\n      compareOptions = parameter.roborazziOptions.compareOptions.copy(\n        // Set custom comparison threshold (0.0 = exact match, 1.0 = ignore differences)\n        imageComparator = SimpleImageComparator(maxDistance = 0.01f)\n      )\n    )\n    defaultCapturer.capture(\n      parameter.copy(roborazziOptions = customOptions)\n    )\n  }\n)\n```\n\nThen reference your custom tester in the Gradle configuration:\n\n```kotlin\nroborazzi {\n  @OptIn(ExperimentalRoborazziApi::class)\n  generateComposePreviewRobolectricTests {\n    enable = true\n    testerQualifiedClassName = \"com.example.MyCustomComposePreviewTester\"\n  }\n}\n```\n\n\u003e [!NOTE] \n\u003e If you are using Groovy DSL instead of Kotlin DSL, you need to use the set method for each assignment:\n\u003e ```kotlin\n\u003e generateComposePreviewRobolectricTests.enable.set(true)\n\u003e generateComposePreviewRobolectricTests.packages.set([\"com.example\"])\n\u003e ```\n\n## Annotation-based Capture Control\n\nTo enable fine-grained control over screenshot timing in Compose Previews, add the new annotations dependency:\n\n```gradle\ntestImplementation(\"io.github.takahirom.roborazzi:roborazzi-annotations:[version]\")\n```\n\nUse `@RoboComposePreviewOptions` to configure time-based captures:\n\n```kotlin\n@RoboComposePreviewOptions(\n  manualClockOptions = [ManualClockOptions(advanceTimeMillis = 516L)]\n)\n@Preview\n@Composable\nfun DelayedPreview() {\n  var visible by remember { mutableStateOf(false) }\n  LaunchedEffect(Unit) {\n    delay(500)\n    visible = true\n  }\n  if (visible) {\n    Text(\"Content appears after 500ms\")\n  }\n}\n```\n\nThis annotation enables capturing screenshots at specific time intervals, particularly useful for testing animated components or delayed state changes.\n\n## Manually adding Compose Preview screenshot tests\n\nRoborazzi provides a helper function for ComposablePreviewScanner.\nYou can add the following dependency to your project to use the helper function:\n\n`testImplementation(\"io.github.takahirom.roborazzi:roborazzi-compose-preview-scanner-support:[version]\")`\n\nThen you can use the `ComposablePreview\u003cAndroidPreviewInfo\u003e.captureRoboImage()` function to capture the Composable Preview using the settings in Preview annotations.\nTo obtain the `ComposablePreview` object, please refer to [ComposablePreviewScanner](https://github.com/sergio-sastre/ComposablePreviewScanner).\n\n```kotlin\nfun ComposablePreview\u003cAndroidPreviewInfo\u003e.captureRoboImage(\n  filePath: String,\n  roborazziOptions: RoborazziOptions\n)\n```\n\n### The supported `@Preview` annotation options\n\nCurrently, we don't support all the annotation options provided by the Compose Preview.\nYou can check the supported annotations in the [source code](https://github.com/takahirom/roborazzi/blob/0810ceb7133e6ec38472046cb741242a5ef6ab9e/roborazzi-compose-preview-scanner-support/src/main/java/com/github/takahirom/roborazzi/RoborazziPreviewScannerSupport.kt#L27).\nWe are looking forward to your contributions to support more annotation options.\n\n\u003c!-- end --\u003e\n\u003c!-- topic_ai_powered_image_assertion --\u003e\n\n\u003c!-- Generated by docs/topics/ai_powered_image_assertion.md. Do not edit this file. --\u003e\n# Experimental AI-Powered Image Assertion\n\nRoborazzi supports AI-powered image assertion.\nAI-powered image assertion is an experimental feature. Screenshot tests are a great way to verify your app's UI, but verifying the content of the images can be a tedious and time-consuming task. This manual effort reduces scalability. Roborazzi can help automate this process through AI-powered image assertion, making it more efficient and scalable.\n\nThere are two new library modules: `io.github.takahirom.roborazzi:roborazzi-ai-gemini` and `io.github.takahirom.roborazzi:roborazzi-ai-openai` for AI-powered image assertion.\n\n`roborazzi-ai-gemini` leverages [Gemini](https://gemini.google.com/) and [generative-ai-kmp](https://github.com/PatilShreyas/generative-ai-kmp), while `roborazzi-ai-openai` utilizes the [OpenAI API](https://platform.openai.com/) through raw HTTP API calls implemented with Ktor and KotlinX Serialization\n\n```kotlin\n...\n@get:Rule\nval composeTestRule = createAndroidComposeRule\u003cMainActivity\u003e()\n\n@get:Rule\nval roborazziRule = RoborazziRule(\n  options = RoborazziRule.Options(\n    roborazziOptions = RoborazziOptions(\n      compareOptions = RoborazziOptions.CompareOptions(\n        aiAssertionOptions = AiAssertionOptions(\n          aiAssertionModel = GeminiAiAssertionModel(\n            // DO NOT HARDCODE your API key in your code.\n            // This is an example passing API Key through unitTests.all{ environment(key, value) }\n            apiKey = System.getenv(\"gemini_api_key\") ?: \"\"\n          ),\n        )\n      )\n    )\n  )\n)\n\n@Test\nfun captureWithAi() {\n  ROBORAZZI_DEBUG = true\n  onView(ViewMatchers.isRoot())\n    .captureRoboImage(\n      roborazziOptions = provideRoborazziContext().options.addedAiAssertions(\n        AiAssertionOptions.AiAssertion(\n          assertionPrompt = \"it should have PREVIOUS button\",\n          requiredFulfillmentPercent = 90,\n        ),\n        AiAssertionOptions.AiAssertion(\n          assertionPrompt = \"it should show First Fragment\",\n          requiredFulfillmentPercent = 90,\n        )\n      )\n    )\n}\n```\n\n## Behavior of AI-Powered Image Assertion\n\nAI-Powered Image Assertion runs only when the images are different. If the images are the same, AI-Powered Image Assertion is skipped.  \nThis is because AI-Powered Image Assertion can be slow and expensive.\n\n## Manual Image Assertion\n\nYou can use manual image assertion with Roborazzi. This allows you to utilize local LLMs or other LLMs. Manual Image Assertion doesn't require adding any dependencies other than Roborazzi itself.\n\nYou must provide the `AiAssertionModel` to `RoborazziOptions` to use manual image assertion.\n\n```kotlin\ninterface AiAssertionModel {\n  fun assert(\n    referenceImageFilePath: String,\n    comparisonImageFilePath: String,\n    actualImageFilePath: String,\n    aiAssertionOptions: AiAssertionOptions\n  ): AiAssertionResults\n}\n```\n\n```kotlin\ncompareOptions = RoborazziOptions.CompareOptions(\n  aiAssertionOptions = AiAssertionOptions(\n    aiAssertionModel = object : AiAssertionOptions.AiAssertionModel {\n      override fun assert(\n        comparisonImageFilePath: String,\n        aiAssertionOptions: AiAssertionOptions\n      ): AiAssertionResults {\n        // You can use any LLMs here to create AiAssertionResults\n        return AiAssertionResults(\n          aiAssertionResults = aiAssertionOptions.aiAssertions.map { assertion -\u003e\n            AiAssertionResult(\n              assertionPrompt = assertion.assertionPrompt,\n              fulfillmentPercent = fulfillmentPercent,\n              requiredFulfillmentPercent = assertion.requiredFulfillmentPercent,\n              failIfNotFulfilled = assertion.failIfNotFulfilled,\n              explanation = \"This is a manual test.\",\n            )\n          }\n        )\n      }\n    },\n    aiAssertions = listOf(\n      AiAssertionOptions.AiAssertion(\n        assertionPrompt = \"it should have PREVIOUS button\",\n        requiredFulfillmentPercent = 90,\n      ),\n    ),\n  )\n)\n          ...\n```\n\n\u003c!-- end --\u003e\n\u003c!-- topic_idea_plugin --\u003e\n\n\u003c!-- Generated by docs/topics/idea_plugin.md. Do not edit this file. --\u003e\n# Roborazzi IntelliJ IDEA and Android Studio Plugin\n\n[https://plugins.jetbrains.com/plugin/24561-roborazzi](https://plugins.jetbrains.com/plugin/24561-roborazzi)\n\nDemo of Roborazzi IntelliJ IDEA and Android Studio Plugin.\n\n[https://github.com/takahirom/roborazzi/assets/1386930/bd3e8106-1971-4ec4-b764-3f1831999228](https://github.com/takahirom/roborazzi/assets/1386930/bd3e8106-1971-4ec4-b764-3f1831999228)\n\nThe Roborazzi plugin enhances your development workflow by automatically displaying screenshots related to Roborazzi's screenshot tests within IntelliJ IDEA. Easily view and verify visual outputs directly in your IDE, streamlining your testing and development process.\n\u003c!-- end --\u003e\n\u003c!-- topic_compose_multiplatform --\u003e\n\n\u003c!-- Generated by docs/topics/compose_multiplatform.md. Do not edit this file. --\u003e\n# Multiplatform support\n\n### Experimental feature: iOS support\n\nRoborazzi supports Compose Multiplatform iOS. You can use Roborazzi with Compose iOS as follows:\n\nGradle settings\n\n```kotlin\nplugins {\n  kotlin(\"multiplatform\")\n  id(\"org.jetbrains.compose\")\n  id(\"io.github.takahirom.roborazzi\")\n}\n\nkotlin {\n  sourceSets {\n    ...\n    val appleTest by getting {\n      dependencies {\n        implementation(project(\"io.github.takahirom.roborazzi:roborazzi-compose-ios:[1.13.0 or higher]\"))\n        implementation(kotlin(\"test\"))\n      }\n    }\n  ...\n\n```\n\nTest with Roborazzi\n\n```kotlin\nclass IosTest {\n  @OptIn(ExperimentalTestApi::class)\n  @Test\n  fun test() {\n    runComposeUiTest {\n      setContent {\n        MaterialTheme {\n          Column {\n            Button(\n              modifier = Modifier.alpha(alpha),\n              onClick = { }) {\n              Text(\"Hello World\")\n            }\n            Box(\n              modifier = Modifier\n                .background(Color.Red.copy(alpha = alpha), MaterialTheme.shapes.small)\n                .size(100.dp),\n            )\n          }\n        }\n      }\n      onRoot().captureRoboImage(this, filePath = \"ios.png\")\n      onNodeWithText(\"Hello World\").captureRoboImage(\n        this,\n        filePath = \"ios_button.png\"\n      )\n    }\n  }\n}\n```\n\nThen, you can run the Gradle tasks for iOS Support, just like you do for Android Support.\n\n```\n./gradlew recordRoborazzi[SourceSet]\n```\n\n```\n./gradlew recordRoborazziIosSimulatorArm64\n./gradlew compareRoborazziIosSimulatorArm64\n./gradlew verifyRoborazziIosSimulatorArm64\n...\n```\n\nThe currently implemented features for iOS support are as follows:\n\n| Feature | status |\n|---|---|\n| Record | supported |\n| Compare | supported |\n| Verify | supported |\n| Report | supported |\n| Dropbox/Differ comparison | 🆖  |\n| dump | 🆖  |\n| resizing image | 🆖  |\n| context data | 🆖  |\n| custom reporter | 🆖  |\n| RoborazziRecordFilePathStrategy  | 🆖  |\n| ComparisonStyle  | 🆖  |\n| resultValidator  | 🆖  |\n| resultValidator  | 🆖  |\n| applyDeviceCrop | 🆖 |\n| pixelBitConfig | 🆖 |\n\n\nWe are migrating JVM implementation to Multiplatform implementation. So, some features are not supported yet.\nWe are looking for contributors to help us implement these features.\n\n### Experimental feature: Compose Desktop support\n\nRoborazzi supports Compose Desktop. You can use Roborazzi with Compose Desktop as follows:\n\nGradle settings\n\n```kotlin\nplugins {\n  kotlin(\"multiplatform\")\n  id(\"org.jetbrains.compose\")\n  id(\"io.github.takahirom.roborazzi\")\n}\n\nkotlin {\n  // You can use your source set name\n  jvm(\"desktop\")\n  sourceSets {\n    ...\n    val desktopTest by getting {\n      dependencies {\n        implementation(project(\"io.github.takahirom.roborazzi:roborazzi-compose-desktop:[1.6.0-alpha-2 or higher]\"))\n        implementation(kotlin(\"test\"))\n      }\n    }\n    ...\n```\n\nTest target Composable function\n\n```kotlin\n@Composable\nfun App() {\n  var text by remember { mutableStateOf(\"Hello, World!\") }\n\n  MaterialTheme {\n    Button(\n      modifier = Modifier.testTag(\"button\"),\n      onClick = {\n        text = \"Hello, Desktop!\"\n      }) {\n      Text(\n        style = MaterialTheme.typography.h2,\n        text = text\n      )\n    }\n  }\n}\n```\n\nTest with Roborazzi\n\n```kotlin\nclass MainKmpTest {\n  @OptIn(ExperimentalTestApi::class)\n  @Test\n  fun test() = runDesktopComposeUiTest {\n    setContent {\n      App()\n    }\n    val roborazziOptions = RoborazziOptions(\n      recordOptions = RoborazziOptions.RecordOptions(\n        resizeScale = 0.5\n      ),\n      compareOptions = RoborazziOptions.CompareOptions(\n        changeThreshold = 0F\n      )\n    )\n    onRoot().captureRoboImage(roborazziOptions = roborazziOptions)\n\n    onNodeWithTag(\"button\").performClick()\n\n    onRoot().captureRoboImage(roborazziOptions = roborazziOptions)\n  }\n}\n```\n\nThen, you can run the Gradle tasks for Desktop Support, just like you do for Android Support.\n\n```\n./gradlew recordRoborazzi[SourceSet]\n```\n\n```\n./gradlew recordRoborazziDesktop\n./gradlew compareRoborazziDesktop\n./gradlew verifyRoborazziDesktop\n...\n```\n\nIf you use the Kotlin JVM plugin, the task will be `recordRoborazzi**Jvm**`.\n\nThe sample image\n\n![MainJvmTest test](https://github.com/takahirom/roborazzi/assets/1386930/41287c29-26ae-4539-b387-de570ae3f2b3)\n![MainJvmTest test_2](https://github.com/takahirom/roborazzi/assets/1386930/2edc828c-6fd8-4a9a-8f3d-b0e7baa85f0d)\n\n\n\u003c!-- end --\u003e\n\u003c!-- topic_gradle_properties_options --\u003e\n\n\u003c!-- Generated by docs/topics/gradle_properties_options.md. Do not edit this file. --\u003e\n# Roborazzi gradle.properties Options and Recommendations\n\nYou can configure the following options in your `gradle.properties` file.\nYou can also use `-P` to set the options in the command line. For example, `./gradlew test -Proborazzi.test.record=true`.\n\n## roborazzi.test\n\nThis option enables you to configure the behavior of Roborazzi. By default, all settings are set to false.\nFor additional configuration options, please refer to the 'Apply Roborazzi Gradle Plugin' section.\n\n```\nroborazzi.test.record=true\n# roborazzi.test.compare=true\n# roborazzi.test.verify=true\n```\n\n## roborazzi.record\n\n### roborazzi.record.resizeScale\n\nThis option lets you set the resize scale for the image being recorded. The default value is 1.0.\n\n```\nroborazzi.record.resizeScale=0.5\n```\n\n### roborazzi.record.filePathStrategy\n\nThis setting allows you to specify the file path strategy for the recorded image. The default strategy is `relativePathFromCurrentDirectory`. If you choose `relativePathFromRoborazziContextOutputDirectory`, the file will be saved in the output directory specified by `roborazzi.outputDir`.\n\n```\nroborazzi.record.filePathStrategy=relativePathFromRoborazziContextOutputDirectory\n```\n\n### roborazzi.record.namingStrategy\n\nThis option enables you to define the naming strategy for the recorded image. The default strategy is `testPackageAndClassAndMethod`.\n\n- If you choose `testPackageAndClassAndMethod`, the file name will be `com.example.MyTest.testMethod.png`.\n- If you choose `escapedTestPackageAndClassAndMethod`, the file name will be `com_example_MyTest.testMethod.png`.\n- If you choose `testClassAndMethod`, the file name will be `MyTest.testMethod.png`.\n\n```\nroborazzi.record.namingStrategy=testClassAndMethod\n```\n\n## roborazzi.cleanupOldScreenshots\n\nThis option allows you to clean up old screenshots. By default, this option is set to false.\nThe reason why Roborazzi does not delete old screenshots by default is that Roborazzi doesn't know if you are running filtered tests or not. If you are running filtered tests, Roborazzi will delete the screenshots that are not related to the current test run.\n\n```\nroborazzi.cleanupOldScreenshots=true\n```\n\n## Robolectric Options\n\n### robolectric.pixelCopyRenderMode\n\nI recommend setting `it.systemProperties[\"robolectric.pixelCopyRenderMode\"] = \"hardware\"` in your `build.gradle` file to enhance the accuracy of your screenshots. For more details, please refer to the [issue](https://github.com/takahirom/roborazzi/issues/296).\n\n```\nandroid {\n    testOptions {\n    ...\n        unitTests {\n            isIncludeAndroidResources = true\n            isReturnDefaultValues = true\n            all {\n                it.systemProperties[\"robolectric.pixelCopyRenderMode\"] = \"hardware\"\n            }\n```\n\u003c!-- end --\u003e\n\u003c!-- topic_faq --\u003e\n\n\u003c!-- Generated by docs/topics/faq.md. Do not edit this file. --\u003e\n# FAQ\n\n### Q: How can I run only screenshot tests using Roborazzi?\n\n**A:** To run only screenshot tests, you can configure your project with the following:\n\n```groovy\nandroid {\n    testOptions {\n        unitTests {\n            all {\n                // -Pscreenshot to filter screenshot tests\n                it.useJUnit {\n                    if (project.hasProperty(\"screenshot\")) {\n                        includeCategories(\"io.github.takahirom.roborazzi.testing.category.ScreenshotTests\")\n                    }\n                }\n            }\n        }\n    }\n}\n```\n\nInclude the `-Pscreenshot` property, and only the screenshot tests will be run.\n\nNote: This feature is not provided in the Roborazzi library itself, to keep it simple and utilize JUnit's built-in features for test filtering.\n\nYou can also annotate your tests like this:\n\n```kotlin\n/**\n * You can filter ScreenshotTests using -Pscreenshot parameter\n */\ninterface ScreenshotTests\n\n@Test\n@Category(ScreenshotTests::class)\nfun checkLaunchShot() {\n  onRoot().captureRoboImage(roborazziOptions = roborazziOptions)\n}\n```\n\nThis allows you to create a category of screenshot tests and filter them using the `-Pscreenshot` property, thus making it easier to run only those specific tests.\n\n### Q: How can I debug screenshot tests in Android Studio?\n### Q: How can I execute screenshot tests using Android Studio's Run button?\n\nA: To execute screenshot tests using Android Studio's Run button, configure your project as follows:\n\n`gradle.properties`\n\n```groovy\nroborazzi.test.record=true\n```\n\nAfter that, you can execute screenshot tests using either Android Studio's Run or Debug button as you normally would.\n\n### Q: My screenshot tests are not capturing images. What could be the issue?\n\n**A:** If your screenshot tests are not capturing images, there may be several patterns that are causing this issue. Please follow these troubleshooting steps:\n\n- **Enable Debugging**: Set `ROBORAZZI_DEBUG = true` to see logs.\n- **Check Plugin**: Ensure that the plugin is properly applied.\n- **Run Task**: Verify that the `recordRoborazziDebug` task is running.\n- **Call Method**: Confirm that `captureRoboImage()` is being called.\n\nBy following these steps, you should be able to identify and resolve the issue causing the screenshot tests to not capture images.\n\n### Q: I'm seeing an optimization warning related to Java lambdas in Gradle. What can I do?\n\n**A:** This warning may occur with Gradle 7.5. Upgrade to Gradle 7.6.2 to resolve this issue. Change the distribution URL in `gradle-wrapper.properties`:\n\n```\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.6.2-bin.zip\n```\n\n### Q: Can I run Roborazzi with Bazel?\n\n**A:** As of now, there is no direct support for running Roborazzi with Bazel. However, it is possible to do so. Please refer to the following comment for more details:\n[Roborazzi Bazel Support Comment](https://github.com/takahirom/roborazzi/issues/63#issuecomment-1531990825)\n\n### Q: My tests are being skipped or, conversely, are being run when they should be skipped. How can I handle caching to address this?\n\n**A:** The behavior you are experiencing may be related to caching issues. Although it's\nexperimental, you can set the `outputDir` parameter in your `build.gradle` file to handle caching\nand improve the stability of your tests. This parameter allows you to specify the output directory\nfor your screenshots, which can help in managing the cache. Here is how you can set it up:\nIf you use the default output directory(module/build/outputs/roborazzi), specifying the `outputDir`\nparameter is not necessary. For more reference, you can check\nout [the issue](https://github.com/takahirom/roborazzi/issues/193#issuecomment-1782073746).\n\n```gradle\nroborazzi {\n    outputDir = \"src/your/screenshot/folder\"\n}\n```\n\n### Q: Why do my screenshot tests fail inconsistently across different operating systems like MacOS, Ubuntu, and Windows?\n\n**A:** This is a known issue caused by variations in how graphics libraries render components on different platforms.\nThis issue is due to variations in how graphics libraries render components on different platforms. \nAs we've seen with [Now in Android](https://github.com/android/nowinandroid/issues/1242#issuecomment-2032962982), there are no guarantees for identical rendering across all environments. \nTo ensure more consistent results, consider configuring your continuous integration (CI) environment to both record and test screenshots. This setup minimizes discrepancies by using the same settings and resources for all tests.\n\n### Q: The images taken from Roborazzi seem broken\n\n**A:** First, please check if you are using the correct version of the Android SDK. Consider using API level 28 or higher because it seems that Robolectric Native Graphics doesn't work property on older SDK versions.\n\nYou can specify the SDK version like `@Config(sdk = [33])` or by using `robolectric.properties` with `sdk=33`.  \n[https://robolectric.org/configuring/](https://robolectric.org/configuring/)  \n\nIf your images are mostly fine but there are some drawing issues like shadows or shape graphics, you can try using Robolectric's Hardware Rendering mode. Please refer to [this issue](https://github.com/takahirom/roborazzi/issues/255#issuecomment-1972838571).\n\n### Q: I am seeing Out Of Memory errors.\n\n**A:** You may solve this by using `unitTests.maxHeapSize` to adjust the unit test heap size as follows:\n```groovy\nandroid {\n  ...\n  testOptions {\n    unitTests.all {\n      maxHeapSize = \"4096m\"\n    }\n  }\n}\n```\nIt is discussed in [this issue](https://github.com/takahirom/roborazzi/issues/272).\nAdditionally, it might be worth trying to run your tests with VisualVM to monitor memory usage and identify potential leaks.\n\n### Q: [IDEA Plugin] Roborazzi Gradle task is not displayed in Tool Window.\n\n**A:** It is discussed in [this issue](https://github.com/takahirom/roborazzi/issues/493).  \nTo enable the display of Roborazzi tasks, please enable ***Configure all Gradle tasks during Gradle Sync (this can make Gradle Sync slower)*** in the Settings | Experimental | Gradle.  \n\u003cimg src=\"https://github.com/user-attachments/assets/67fbb2a8-6b2a-458c-bb31-99025f1c1cab\" width=\"800\" /\u003e\n\u003c!-- end --\u003e\n\n### LICENSE\n\n```\nCopyright 2023 takahirom\nCopyright 2019 Square, Inc.\nCopyright The Android Open Source Project\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakahirom%2Froborazzi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftakahirom%2Froborazzi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftakahirom%2Froborazzi/lists"}