From a5a67dd3ce22152ff63694c9d0d9f8782a14366e Mon Sep 17 00:00:00 2001 From: Daniel Jette Date: Fri, 2 Oct 2026 11:40:06 -0400 Subject: [PATCH 1/2] 325: Add a Hilt screenshot test sample --- Samples/Flix/FlixHilt/build.gradle | 83 +++++++++++++++++ .../HiltComposableScreenshotTest_default.png | Bin 0 -> 4073 bytes .../flix/hilt/HiltComposableScreenshotTest.kt | 86 ++++++++++++++++++ .../samples/flix/hilt/HiltTestRunner.kt | 45 +++++++++ .../FlixHilt/src/main/AndroidManifest.xml | 10 ++ .../flix/hilt/HiltComposableTestActivity.kt | 55 +++++++++++ .../samples/flix/hilt/SampleViewModel.kt | 41 +++++++++ bitrise.yml | 15 +++ settings.gradle | 2 + 9 files changed, 337 insertions(+) create mode 100644 Samples/Flix/FlixHilt/build.gradle create mode 100644 Samples/Flix/FlixHilt/src/androidTest/assets/screenshots/37-1080x2220@440dp-en_US/HiltComposableScreenshotTest_default.png create mode 100644 Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltComposableScreenshotTest.kt create mode 100644 Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt create mode 100644 Samples/Flix/FlixHilt/src/main/AndroidManifest.xml create mode 100644 Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt create mode 100644 Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt diff --git a/Samples/Flix/FlixHilt/build.gradle b/Samples/Flix/FlixHilt/build.gradle new file mode 100644 index 000000000..c27a01fd5 --- /dev/null +++ b/Samples/Flix/FlixHilt/build.gradle @@ -0,0 +1,83 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +buildscript { + ext { + versions = [ + 'compose': [ + 'compilerExt': '1.5.14', + 'ui' : '1.6.7', + ] + ] + } +} + +plugins { + id 'com.android.library' + alias(libs.plugins.google.devtools.ksp) + id 'com.google.dagger.hilt.android' + id 'dev.testify' + alias(libs.plugins.compose.compiler) +} + +android { + namespace 'dev.testify.samples.flix.hilt' + compileSdk 36 + + defaultConfig { + minSdkVersion 26 + targetSdkVersion 36 + + // Hilt tests need HiltTestApplication. This setting is module-wide, which is why the + // example lives here rather than in :FlixSample — see HiltTestRunner. + testInstrumentationRunner "dev.testify.samples.flix.hilt.HiltTestRunner" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_25 + targetCompatibility JavaVersion.VERSION_25 + } + kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_25) + } + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion "${versions.compose.compilerExt}" + } +} + +dependencies { + def composeBom = platform('androidx.compose:compose-bom:2025.08.01') + implementation composeBom + + implementation "androidx.appcompat:appcompat:1.7.1" + implementation 'androidx.compose.material3:material3:1.3.2' + implementation "androidx.hilt:hilt-navigation-compose:1.2.0" + implementation "com.google.dagger:hilt-android:2.60.1" + ksp "com.google.dagger:hilt-compiler:2.60.1" + + // HiltComposableTestActivity extends ComposableTestActivity, which implements an interface from + // the core library, so both are needed to compile it. + compileOnly project(":Library") + compileOnly project(path: ":ComposeExtensions") + + androidTestImplementation composeBom + androidTestImplementation project(":Library") + androidTestImplementation project(path: ":ComposeExtensions") + androidTestImplementation 'androidx.test.ext:junit:1.3.0' + androidTestImplementation "androidx.test:core-ktx:1.4.0" + androidTestImplementation "androidx.test:rules:1.7.0" + androidTestImplementation "androidx.compose.ui:ui-test-junit4:${versions.compose.ui}" + + androidTestImplementation "com.google.dagger:hilt-android-testing:2.60.1" + kspAndroidTest "com.google.dagger:hilt-compiler:2.60.1" +} + +testify { + applicationPackageId "dev.testify.samples.flix.hilt.test" + testPackageId "dev.testify.samples.flix.hilt.test" + autoImplementLibrary false +} diff --git a/Samples/Flix/FlixHilt/src/androidTest/assets/screenshots/37-1080x2220@440dp-en_US/HiltComposableScreenshotTest_default.png b/Samples/Flix/FlixHilt/src/androidTest/assets/screenshots/37-1080x2220@440dp-en_US/HiltComposableScreenshotTest_default.png new file mode 100644 index 0000000000000000000000000000000000000000..cddfe0bce49802b5d7ec5b9a1c1a9a34a784f86d GIT binary patch literal 4073 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 composables that use Hilt + */ +@HiltAndroidTest +class HiltComposableScreenshotTest { + + @get:Rule(order = 0) + val hiltRule = HiltAndroidRule(this) + + @get:Rule(order = 1) + val rule = ComposableScreenshotScenarioRule() + + /** + * Resolves [SampleViewModel] through Hilt and renders a value that comes from it. If the + * injection fails the test throws rather than capturing, so a passing baseline is itself the + * assertion that Hilt resolved through [HiltComposableTestActivity]. + */ + @Composable + private fun HiltBackedContent() { + val viewModel = hiltViewModel() + Text(text = viewModel.label) + } + + @ScreenshotInstrumentation + @Test + fun default() { + launchActivity().use { scenario -> + rule + .withScenario(scenario) + .setCompose { + HiltBackedContent() + } + .assertSame() + } + } +} diff --git a/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt b/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt new file mode 100644 index 000000000..b7e7d63dc --- /dev/null +++ b/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt @@ -0,0 +1,45 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 ndtp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package dev.testify.samples.flix.hilt + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +/** + * Substitutes [HiltTestApplication] for the app's own [Application] so Hilt can build its test + * component. + * + * `testInstrumentationRunner` is module-wide, which is why this module exists. Pointing the `Flix` + * sample at this runner would replace its `FlixApplication`, and with it the Coil `ImageLoader` that + * `FlixApplication` supplies through `ImageLoaderFactory` — so its image-backed screenshot tests + * would capture before their images had drawn. Keeping the Hilt example in its own module leaves + * that suite alone. + */ +class HiltTestRunner : AndroidJUnitRunner() { + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application = + super.newApplication(cl, HiltTestApplication::class.java.name, context) +} diff --git a/Samples/Flix/FlixHilt/src/main/AndroidManifest.xml b/Samples/Flix/FlixHilt/src/main/AndroidManifest.xml new file mode 100644 index 000000000..951eabc85 --- /dev/null +++ b/Samples/Flix/FlixHilt/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt b/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt new file mode 100644 index 000000000..c8e936eea --- /dev/null +++ b/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt @@ -0,0 +1,55 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 ndtp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package dev.testify.samples.flix.hilt + +import dagger.hilt.android.AndroidEntryPoint +import dev.testify.ComposableTestActivity + +/** + * A Hilt-aware host for composables under test. + * + * Hilt injects only into activities annotated with [AndroidEntryPoint]. Testify's + * [ComposableTestActivity] is not, so a composable that resolves a dependency — through + * `hiltViewModel()`, for example — fails inside it with: + * + * ``` + * java.lang.IllegalStateException: Given component holder class dev.testify.ComposableTestActivity + * does not implement interface dagger.hilt.internal.GeneratedComponent or interface + * dagger.hilt.internal.GeneratedComponentManager + * ``` + * + * Subclassing it here adds the annotation without changing anything else, so + * `ComposableScreenshotScenarioRule` can host Hilt-backed composables. + * + * This lives in `main` rather than `androidTest` because the activity has to be declared in a + * merged manifest, which cannot reference test sources. That is safe here because this whole module + * exists only to host screenshot tests. In an application module, put it in the `debug` source set + * instead so it is never shipped — see the + * [Hilt recipe](https://testify.dev/docs/recipes/hilt). + * + * @see HiltComposableScreenshotTest + */ +@AndroidEntryPoint +class HiltComposableTestActivity : ComposableTestActivity() diff --git a/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt b/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt new file mode 100644 index 000000000..f43b42d84 --- /dev/null +++ b/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt @@ -0,0 +1,41 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 ndtp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package dev.testify.samples.flix.hilt + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +/** + * A view model with nothing in it, so the test has something for Hilt to resolve. + * + * The point of [HiltComposableScreenshotTest] is that `hiltViewModel()` resolves at all inside a + * screenshot test. Giving this no dependencies and no mutable state keeps the rendered output + * deterministic, so a failure means the injection broke rather than the data changed. + */ +@HiltViewModel +class SampleViewModel @Inject constructor() : ViewModel() { + val label: String = "injected by Hilt" +} diff --git a/bitrise.yml b/bitrise.yml index 491cfde31..33fc53492 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -430,6 +430,21 @@ workflows: - test_apk: "./Samples/Flix/FlixLibrary/build/outputs/apk/androidTest/debug/FlixLibrary-debug-androidTest.apk" - test_package: dev.testify.samples.flix.library.test - test_runner: androidx.test.runner.AndroidJUnitRunner + - android-build@1: + inputs: + - variant: DebugAndroidTest + - module: FlixHilt + - git::https://github.com/ndtp/bitrise-step-android-testify.git@main: + timeout: 1800 + title: Run Screenshot Tests + inputs: + - adb_command: "./gradlew FlixHilt:screenshotTest" + - module: ":FlixHilt" + - app_apk: "./Samples/Flix/FlixHilt/build/outputs/apk/androidTest/debug/FlixHilt-debug-androidTest.apk" + - app_package: dev.testify.samples.flix.hilt.test + - test_apk: "./Samples/Flix/FlixHilt/build/outputs/apk/androidTest/debug/FlixHilt-debug-androidTest.apk" + - test_package: dev.testify.samples.flix.hilt.test + - test_runner: dev.testify.samples.flix.hilt.HiltTestRunner - verbose: true - custom-test-results-export@1.0: is_always_run: true diff --git a/settings.gradle b/settings.gradle index 4e3ba1aea..1d6b79a7a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,6 +19,7 @@ dependencyResolutionManagement { include ':Accessibility' include ':ComposeExtensions' +include ':FlixHilt' include ':FlixLibrary' include ':FlixSample' include ':FullscreenCaptureMethod' @@ -32,6 +33,7 @@ includeBuild("./Plugins/Gradle") { name = "Plugin" } project(':Accessibility').projectDir = new File("./Ext/Accessibility") project(':ComposeExtensions').projectDir = new File("./Ext/Compose") +project(':FlixHilt').projectDir = new File("./Samples/Flix/FlixHilt") project(':FlixLibrary').projectDir = new File("./Samples/Flix/FlixLibrary") project(':FlixSample').projectDir = new File("./Samples/Flix") project(':FullscreenCaptureMethod').projectDir = new File("./Ext/Fullscreen") From 427d920f7f702033e86a65561588150d36390e7e Mon Sep 17 00:00:00 2001 From: Daniel Jette Date: Fri, 2 Oct 2026 16:41:47 -0400 Subject: [PATCH 2/2] 325: Move the Hilt host activity to the debug source set --- Samples/Flix/FlixHilt/build.gradle | 13 +++++++++---- .../flix/hilt/HiltComposableScreenshotTest.kt | 5 +++-- .../testify/samples/flix/hilt/HiltTestRunner.kt | 2 +- .../src/{main => debug}/AndroidManifest.xml | 0 .../flix/hilt/HiltComposableTestActivity.kt | 16 ++++++++++------ .../testify/samples/flix/hilt/SampleViewModel.kt | 2 +- bitrise.yml | 1 + 7 files changed, 25 insertions(+), 14 deletions(-) rename Samples/Flix/FlixHilt/src/{main => debug}/AndroidManifest.xml (100%) rename Samples/Flix/FlixHilt/src/{main => debug}/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt (74%) diff --git a/Samples/Flix/FlixHilt/build.gradle b/Samples/Flix/FlixHilt/build.gradle index c27a01fd5..e227c93e4 100644 --- a/Samples/Flix/FlixHilt/build.gradle +++ b/Samples/Flix/FlixHilt/build.gradle @@ -59,10 +59,15 @@ dependencies { implementation "com.google.dagger:hilt-android:2.60.1" ksp "com.google.dagger:hilt-compiler:2.60.1" - // HiltComposableTestActivity extends ComposableTestActivity, which implements an interface from - // the core library, so both are needed to compile it. - compileOnly project(":Library") - compileOnly project(path: ":ComposeExtensions") + // HiltComposableTestActivity lives in the `debug` source set, which cannot see the + // `androidTest` dependencies below. It extends ComposableTestActivity, which implements an + // interface from the core library, so both artifacts are needed to compile it. + // + // `compileOnly` is enough: at run time the Testify classes come from the test APK, as they do + // for ComposableTestActivity itself. `debugImplementation` would instead add Testify's own test + // dependencies to the module and clash with the versions `androidTest` resolves. + debugCompileOnly project(":Library") + debugCompileOnly project(path: ":ComposeExtensions") androidTestImplementation composeBom androidTestImplementation project(":Library") diff --git a/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltComposableScreenshotTest.kt b/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltComposableScreenshotTest.kt index f6d431b84..905a5e6c3 100644 --- a/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltComposableScreenshotTest.kt +++ b/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltComposableScreenshotTest.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2026 ndtp * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -41,7 +41,8 @@ import org.junit.Test * The composable under test calls `hiltViewModel()`, which needs an `@AndroidEntryPoint` host. The * three pieces that make that work: * - * 1. [HiltComposableTestActivity], an `@AndroidEntryPoint` subclass of `ComposableTestActivity`. + * 1. [HiltComposableTestActivity] in the `debug` source set, an `@AndroidEntryPoint` subclass of + * `ComposableTestActivity`, declared in `src/debug/AndroidManifest.xml`. * 2. [HiltTestRunner] as the module's `testInstrumentationRunner`. * 3. [HiltAndroidRule] ordered before the Testify rule, so the component is ready before the * activity launches. diff --git a/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt b/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt index b7e7d63dc..77147d211 100644 --- a/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt +++ b/Samples/Flix/FlixHilt/src/androidTest/java/dev/testify/samples/flix/hilt/HiltTestRunner.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2026 ndtp * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/Flix/FlixHilt/src/main/AndroidManifest.xml b/Samples/Flix/FlixHilt/src/debug/AndroidManifest.xml similarity index 100% rename from Samples/Flix/FlixHilt/src/main/AndroidManifest.xml rename to Samples/Flix/FlixHilt/src/debug/AndroidManifest.xml diff --git a/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt b/Samples/Flix/FlixHilt/src/debug/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt similarity index 74% rename from Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt rename to Samples/Flix/FlixHilt/src/debug/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt index c8e936eea..72d2391df 100644 --- a/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt +++ b/Samples/Flix/FlixHilt/src/debug/java/dev/testify/samples/flix/hilt/HiltComposableTestActivity.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2026 ndtp * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -43,11 +43,15 @@ import dev.testify.ComposableTestActivity * Subclassing it here adds the annotation without changing anything else, so * `ComposableScreenshotScenarioRule` can host Hilt-backed composables. * - * This lives in `main` rather than `androidTest` because the activity has to be declared in a - * merged manifest, which cannot reference test sources. That is safe here because this whole module - * exists only to host screenshot tests. In an application module, put it in the `debug` source set - * instead so it is never shipped — see the - * [Hilt recipe](https://testify.dev/docs/recipes/hilt). + * This lives in the `debug` source set, with its manifest entry in `src/debug/AndroidManifest.xml` + * and Testify on the debug compile classpath as `debugCompileOnly`. That is the arrangement an + * application module needs — a test-only activity has no business in a shipping build — and it is + * what the [Hilt recipe](https://testify.dev/docs/recipes/hilt) describes, so this sample mirrors + * the recipe step for step. + * + * It is not the only arrangement that works. In a library module the test APK *is* the application, + * so the activity can equally live in `androidTest` with an `androidTest` manifest and no + * `compileOnly` at all. `debug` is used here to match the recipe. * * @see HiltComposableScreenshotTest */ diff --git a/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt b/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt index f43b42d84..8b3e71a95 100644 --- a/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt +++ b/Samples/Flix/FlixHilt/src/main/java/dev/testify/samples/flix/hilt/SampleViewModel.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2026 ndtp * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/bitrise.yml b/bitrise.yml index 33fc53492..c8e2013e7 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -430,6 +430,7 @@ workflows: - test_apk: "./Samples/Flix/FlixLibrary/build/outputs/apk/androidTest/debug/FlixLibrary-debug-androidTest.apk" - test_package: dev.testify.samples.flix.library.test - test_runner: androidx.test.runner.AndroidJUnitRunner + - verbose: true - android-build@1: inputs: - variant: DebugAndroidTest