diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ed5fa237d..28ec373b7 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,10 +1,19 @@ -Thanks for submitting a pull request. Please include the following information. +_Thanks for submitting a pull request. Please include the following information._ **What I have done and why** -Include a summary of what your pull request contains, and why you have made these changes. + +_Include a summary of what your pull request contains, and why you have made these changes._ Fixes # +**How I'm testing it** + +_Choose at least one:_ +- Unit tests +- UI tests +- Screenshot tests +- N/A _(provide justification)_ + **Do tests pass?** - [ ] Run local tests on `DemoDebug` variant: `./gradlew testDemoDebug` - [ ] Check formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply` diff --git a/.github/workflows/Build.yaml b/.github/workflows/Build.yaml index 001140a87..db326c380 100644 --- a/.github/workflows/Build.yaml +++ b/.github/workflows/Build.yaml @@ -17,6 +17,7 @@ jobs: permissions: contents: write + pull-requests: write timeout-minutes: 60 @@ -100,12 +101,13 @@ jobs: commit_message: "🤖 Updates screenshots" # Run local tests after screenshot tests to avoid wrong UP-TO-DATE. TODO: Ignore screenshots. - - name: Run local tests + - name: Run local tests and create report if: always() run: ./gradlew testDemoDebug :lint:test # Replace task exclusions with `-Pandroidx.baselineprofile.skipgeneration` when # https://android-review.googlesource.com/c/platform/frameworks/support/+/2602790 landed in a # release build + - name: Build all build type and flavor permutations run: ./gradlew :app:assemble :benchmarks:assemble -x pixel6Api33ProdNonMinifiedReleaseAndroidTest @@ -119,11 +121,11 @@ jobs: name: APKs path: '**/build/outputs/apk/**/*.apk' - - name: Upload test results (XML) + - name: Upload JVM local results (XML) if: always() uses: actions/upload-artifact@v4 with: - name: test-results + name: local-test-results path: '**/build/test-results/test*UnitTest/**.xml' - name: Check lint @@ -180,10 +182,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v3 - - name: Build projects before running emulator - run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest - - - name: Run instrumentation tests + - name: Build projects and run instrumentation tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} @@ -193,9 +192,41 @@ jobs: heap-size: 600M script: ./gradlew connectedDemoDebugAndroidTest --daemon + - name: Run local tests (including Roborazzi) for the combined coverage report (only API 30) + if: matrix.api-level == 30 + # There is no need to verify Roborazzi tests to generate coverage. + run: ./gradlew testDemoDebugUnitTest -Proborazzi.test.verify=false # Add Prod if we ever add JVM tests for prod + + # Add `createProdDebugUnitTestCoverageReport` if we ever add JVM tests for prod + - name: Generate coverage reports for Debug variants (only API 30) + if: matrix.api-level == 30 + run: ./gradlew createDemoDebugCombinedCoverageReport + - name: Upload test reports if: always() uses: actions/upload-artifact@v4 with: name: test-reports-${{ matrix.api-level }} path: '**/build/reports/androidTests' + + - name: Display local test coverage (only API 30) + if: matrix.api-level == 30 + id: jacoco + uses: madrapps/jacoco-report@v1.6.1 + with: + title: Combined test coverage report + min-coverage-overall: 40 + min-coverage-changed-files: 60 + paths: | + ${{ github.workspace }}/**/build/reports/jacoco/**/*Report.xml + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload local coverage reports (XML + HTML) (only API 30) + if: matrix.api-level == 30 + uses: actions/upload-artifact@v4 + with: + name: coverage-reports + if-no-files-found: error + compression-level: 1 + overwrite: false + path: '**/build/reports/jacoco/' diff --git a/app-nia-catalog/build.gradle.kts b/app-nia-catalog/build.gradle.kts index 0991ea0c5..94d55b81c 100644 --- a/app-nia-catalog/build.gradle.kts +++ b/app-nia-catalog/build.gradle.kts @@ -59,7 +59,7 @@ android { // To publish on the Play store a private signing key is required, but to allow anyone // who clones the code to sign and run the release variant, use the debug signing key. // TODO: Abstract the signing configuration to a separate file to avoid hardcoding this. - signingConfig = signingConfigs.getByName("debug") + signingConfig = signingConfigs.named("debug").get() } } } diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9e68ffa7d..56e265c45 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,7 +21,6 @@ plugins { alias(libs.plugins.nowinandroid.android.application.flavors) alias(libs.plugins.nowinandroid.android.application.jacoco) alias(libs.plugins.nowinandroid.android.hilt) - id("jacoco") alias(libs.plugins.nowinandroid.android.application.firebase) id("com.google.android.gms.oss-licenses-plugin") alias(libs.plugins.baselineprofile) @@ -53,7 +52,7 @@ android { // To publish on the Play store a private signing key is required, but to allow anyone // who clones the code to sign and run the release variant, use the debug signing key. // TODO: Abstract the signing configuration to a separate file to avoid hardcoding this. - signingConfig = signingConfigs.getByName("debug") + signingConfig = signingConfigs.named("debug").get() // Ensure Baseline Profile is fresh for release builds. baselineProfile.automaticGenerationDuringBuild = true } diff --git a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt index 4c3acc520..ac385b0d9 100644 --- a/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt @@ -15,6 +15,7 @@ */ import com.android.build.api.variant.ApplicationAndroidComponentsExtension +import com.android.build.gradle.internal.dsl.BaseAppModuleExtension import com.google.samples.apps.nowinandroid.configureJacoco import org.gradle.api.Plugin import org.gradle.api.Project @@ -23,13 +24,15 @@ import org.gradle.kotlin.dsl.getByType class AndroidApplicationJacocoConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("org.gradle.jacoco") - apply("com.android.application") + pluginManager.apply("jacoco") + val androidExtension = extensions.getByType() + + androidExtension.buildTypes.configureEach { + enableAndroidTestCoverage = true + enableUnitTestCoverage = true } - val extension = extensions.getByType() - configureJacoco(extension) + + configureJacoco(extensions.getByType()) } } - -} \ No newline at end of file +} diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt index 86ca091c3..6f2ff60c5 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +import com.android.build.api.dsl.LibraryExtension +import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.google.samples.apps.nowinandroid.configureJacoco import org.gradle.api.Plugin @@ -23,13 +25,15 @@ import org.gradle.kotlin.dsl.getByType class AndroidLibraryJacocoConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { - with(pluginManager) { - apply("org.gradle.jacoco") - apply("com.android.library") + pluginManager.apply("jacoco") + val androidExtension = extensions.getByType() + + androidExtension.buildTypes.configureEach { + enableAndroidTestCoverage = true + enableUnitTestCoverage = true } - val extension = extensions.getByType() - configureJacoco(extension) + + configureJacoco(extensions.getByType()) } } - -} \ No newline at end of file +} diff --git a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt index 29d31f9e6..dbca79a5e 100644 --- a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt @@ -15,6 +15,7 @@ */ import androidx.room.gradle.RoomExtension +import com.google.devtools.ksp.gradle.KspExtension import com.google.samples.apps.nowinandroid.libs import org.gradle.api.Plugin import org.gradle.api.Project @@ -28,6 +29,10 @@ class AndroidRoomConventionPlugin : Plugin { pluginManager.apply("androidx.room") pluginManager.apply("com.google.devtools.ksp") + extensions.configure { + arg("room.generateKotlin", "true") + } + extensions.configure { // The schemas directory contains a schema file for each version of the Room database. // This is required to enable Room auto migrations. diff --git a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt index 596c4f579..7820a978e 100644 --- a/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt +++ b/build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,13 @@ package com.google.samples.apps.nowinandroid +import com.android.build.api.artifact.ScopedArtifact import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.ScopedArtifacts import org.gradle.api.Project +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.register @@ -32,13 +37,24 @@ private val coverageExclusions = listOf( "**/R.class", "**/R\$*.class", "**/BuildConfig.*", - "**/Manifest*.*" + "**/Manifest*.*", + "**/*_Hilt*.class", + "**/Hilt_*.class", ) private fun String.capitalize() = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } +/** + * Creates a new task that generates a combined coverage report with data from local and + * instrumented tests. + * + * `create{variant}CombinedCoverageReport` + * + * Note that coverage data must exist before running the task. This allows us to run device + * tests on CI using a different Github Action or an external device farm. + */ internal fun Project.configureJacoco( androidComponentsExtension: AndroidComponentsExtension<*, *, *>, ) { @@ -46,37 +62,53 @@ internal fun Project.configureJacoco( toolVersion = libs.findVersion("jacoco").get().toString() } - val jacocoTestReport = tasks.create("jacocoTestReport") - androidComponentsExtension.onVariants { variant -> - val testTaskName = "test${variant.name.capitalize()}UnitTest" + val myObjFactory = project.objects val buildDir = layout.buildDirectory.get().asFile - val reportTask = tasks.register("jacoco${testTaskName.capitalize()}Report", JacocoReport::class) { - dependsOn(testTaskName) + val allJars: ListProperty = myObjFactory.listProperty(RegularFile::class.java) + val allDirectories: ListProperty = myObjFactory.listProperty(Directory::class.java) + val reportTask = + tasks.register("create${variant.name.capitalize()}CombinedCoverageReport", JacocoReport::class) { - reports { - xml.required.set(true) - html.required.set(true) - } - - classDirectories.setFrom( - fileTree("$buildDir/tmp/kotlin-classes/${variant.name}") { - exclude(coverageExclusions) + classDirectories.setFrom( + allJars, + allDirectories.map { dirs -> + dirs.map { dir -> + myObjFactory.fileTree().setDir(dir).exclude(coverageExclusions) + } + } + ) + reports { + xml.required.set(true) + html.required.set(true) } - ) - sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin")) - executionData.setFrom(file("$buildDir/jacoco/$testTaskName.exec")) - } + // TODO: This is missing files in src/debug/, src/prod, src/demo, src/demoDebug... + sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin")) + + executionData.setFrom( + project.fileTree("$buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest") + .matching { include("**/*.exec") }, + + project.fileTree("$buildDir/outputs/code_coverage/${variant.name}AndroidTest") + .matching { include("**/*.ec") } + ) + } + - jacocoTestReport.dependsOn(reportTask) + variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT) + .use(reportTask) + .toGet( + ScopedArtifact.CLASSES, + { _ -> allJars }, + { _ -> allDirectories }, + ) } tasks.withType().configureEach { configure { // Required for JaCoCo + Robolectric // https://github.com/robolectric/robolectric/issues/2230 - // TODO: Consider removing if not we don't add Robolectric isIncludeNoLocationClasses = true // Required for JDK 11 with the above diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DaosModule.kt similarity index 90% rename from core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt rename to core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DaosModule.kt index afbbf42e6..e7456054e 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DaosModule.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DaosModule.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.database +package com.google.samples.apps.nowinandroid.core.database.di +import com.google.samples.apps.nowinandroid.core.database.NiaDatabase import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceFtsDao import com.google.samples.apps.nowinandroid.core.database.dao.RecentSearchQueryDao diff --git a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt similarity index 86% rename from core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt rename to core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt index c6e33f284..d79d35948 100644 --- a/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/DatabaseModule.kt +++ b/core/database/src/main/kotlin/com/google/samples/apps/nowinandroid/core/database/di/DatabaseModule.kt @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Android Open Source Project + * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,11 @@ * limitations under the License. */ -package com.google.samples.apps.nowinandroid.core.database +package com.google.samples.apps.nowinandroid.core.database.di import android.content.Context import androidx.room.Room +import com.google.samples.apps.nowinandroid.core.database.NiaDatabase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsCountUseCase.kt b/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsCountUseCase.kt deleted file mode 100644 index 3e3e1952e..000000000 --- a/core/domain/src/main/kotlin/com/google/samples/apps/nowinandroid/core/domain/GetSearchContentsCountUseCase.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.samples.apps.nowinandroid.core.domain - -import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -/** - * A use case which returns total count of *Fts tables - */ -class GetSearchContentsCountUseCase @Inject constructor( - private val searchContentsRepository: SearchContentsRepository, -) { - operator fun invoke(): Flow = - searchContentsRepository.getSearchContentsCount() -} diff --git a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt index ad6ca6112..6c2af240c 100644 --- a/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt +++ b/feature/search/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModel.kt @@ -23,9 +23,9 @@ import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsEvent.Param import com.google.samples.apps.nowinandroid.core.analytics.AnalyticsHelper import com.google.samples.apps.nowinandroid.core.data.repository.RecentSearchRepository +import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository import com.google.samples.apps.nowinandroid.core.data.repository.UserDataRepository import com.google.samples.apps.nowinandroid.core.domain.GetRecentSearchQueriesUseCase -import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsCountUseCase import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsUseCase import com.google.samples.apps.nowinandroid.core.model.data.UserSearchResult import dagger.hilt.android.lifecycle.HiltViewModel @@ -42,8 +42,8 @@ import javax.inject.Inject @HiltViewModel class SearchViewModel @Inject constructor( getSearchContentsUseCase: GetSearchContentsUseCase, - getSearchContentsCountUseCase: GetSearchContentsCountUseCase, recentSearchQueriesUseCase: GetRecentSearchQueriesUseCase, + private val searchContentsRepository: SearchContentsRepository, private val recentSearchRepository: RecentSearchRepository, private val userDataRepository: UserDataRepository, private val savedStateHandle: SavedStateHandle, @@ -53,7 +53,7 @@ class SearchViewModel @Inject constructor( val searchQuery = savedStateHandle.getStateFlow(key = SEARCH_QUERY, initialValue = "") val searchResultUiState: StateFlow = - getSearchContentsCountUseCase() + searchContentsRepository.getSearchContentsCount() .flatMapLatest { totalCount -> if (totalCount < SEARCH_MIN_FTS_ENTITY_COUNT) { flowOf(SearchResultUiState.SearchNotReady) diff --git a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt index 128a98206..417fb07ee 100644 --- a/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt +++ b/feature/search/src/test/kotlin/com/google/samples/apps/nowinandroid/feature/search/SearchViewModelTest.kt @@ -19,7 +19,6 @@ package com.google.samples.apps.nowinandroid.feature.search import androidx.lifecycle.SavedStateHandle import com.google.samples.apps.nowinandroid.core.analytics.NoOpAnalyticsHelper import com.google.samples.apps.nowinandroid.core.domain.GetRecentSearchQueriesUseCase -import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsCountUseCase import com.google.samples.apps.nowinandroid.core.domain.GetSearchContentsUseCase import com.google.samples.apps.nowinandroid.core.testing.data.newsResourcesTestData import com.google.samples.apps.nowinandroid.core.testing.data.topicsTestData @@ -60,7 +59,6 @@ class SearchViewModelTest { ) private val recentSearchRepository = TestRecentSearchRepository() private val getRecentQueryUseCase = GetRecentSearchQueriesUseCase(recentSearchRepository) - private val getSearchContentsCountUseCase = GetSearchContentsCountUseCase(searchContentsRepository) private lateinit var viewModel: SearchViewModel @@ -68,8 +66,8 @@ class SearchViewModelTest { fun setup() { viewModel = SearchViewModel( getSearchContentsUseCase = getSearchContentsUseCase, - getSearchContentsCountUseCase = getSearchContentsCountUseCase, recentSearchQueriesUseCase = getRecentQueryUseCase, + searchContentsRepository = searchContentsRepository, savedStateHandle = SavedStateHandle(), recentSearchRepository = recentSearchRepository, userDataRepository = userDataRepository, @@ -86,7 +84,8 @@ class SearchViewModelTest { fun stateIsEmptyQuery_withEmptySearchQuery() = runTest { searchContentsRepository.addNewsResources(newsResourcesTestData) searchContentsRepository.addTopics(topicsTestData) - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } viewModel.onSearchQueryChanged("") @@ -97,7 +96,8 @@ class SearchViewModelTest { @Test fun emptyResultIsReturned_withNotMatchingQuery() = runTest { - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } viewModel.onSearchQueryChanged("XXX") searchContentsRepository.addNewsResources(newsResourcesTestData) @@ -112,7 +112,8 @@ class SearchViewModelTest { @Test fun recentSearches_verifyUiStateIsSuccess() = runTest { - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.recentSearchQueriesUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.recentSearchQueriesUiState.collect() } viewModel.onSearchTriggered("kotlin") val result = viewModel.recentSearchQueriesUiState.value @@ -137,7 +138,8 @@ class SearchViewModelTest { @Test fun searchNotReady_withNoFtsTableEntity() = runTest { - val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } + val collectJob = + launch(UnconfinedTestDispatcher()) { viewModel.searchResultUiState.collect() } viewModel.onSearchQueryChanged("") diff --git a/gradle.properties b/gradle.properties index c0acfeb02..97f940e2e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,3 +39,6 @@ kotlin.code.style=official # https://developer.android.com/build/releases/gradle-plugin#default-changes android.defaults.buildfeatures.resvalues=false android.defaults.buildfeatures.shaders=false + +# Run Roborazzi screenshot tests with the local tests +roborazzi.test.verify=true