From 415dfefe7405820411ac888b3a54d3eb3d130ca4 Mon Sep 17 00:00:00 2001 From: Daniel Jette Date: Sat, 7 Mar 2026 12:24:28 -0500 Subject: [PATCH 1/4] Extract new Ktx library from the existing Testify Library project --- Ext/Ktx/README.md | 60 ++++++++++ Ext/Ktx/build.gradle | 110 ++++++++++++++++++ .../29-1080x2220@440dp-en_US/test.png | Bin 0 -> 21522 bytes .../java/dev/testify/ScreenshotUtilityTest.kt | 13 ++- Ext/Ktx/src/debug/AndroidManifest.xml | 14 +++ .../java/dev/testify/ktx/TestActivity.kt | 14 +++ .../main/java/dev/testify/CaptureMethod.kt | 2 +- .../java/dev/testify/ScreenshotUtility.kt | 8 +- .../annotation/AnnotationExtensions.kt | 62 ++++++++++ .../java/dev/testify/core/DeviceIdentifier.kt | 3 +- .../exception/RootViewNotFoundException.kt | 2 +- .../core/exception/TestifyException.kt | 3 +- .../testify/core/processor/MemoryHelpers.kt | 109 +++++++++++++++++ .../core/processor/capture/CanvasCapture.kt | 2 +- .../processor/capture/DrawingCacheCapture.kt | 2 +- .../processor/capture/PixelCopyCapture.kt | 2 +- .../InstrumentationRegistryExtensions.kt | 73 ++++++++++++ .../dev/testify/extensions/ViewExtensions.kt | 2 +- .../ExcludeFromJacocoGeneratedReport.kt | 2 +- .../internal/extensions/LocaleExtensions.kt | 13 ++- .../testify/internal/helpers/AssetLoader.kt | 2 +- .../testify/internal/helpers/BuildVersion.kt | 2 +- .../testify/internal/helpers/FindRootView.kt | 4 +- .../internal/helpers/IsRunningOnUiThread.kt | 2 +- .../internal/helpers/ManifestHelpers.kt | 4 +- .../output/DataDirectoryDestination.kt | 4 +- .../java/dev/testify/output/Destination.kt | 2 +- .../dev/testify/output/OutputFileUtility.kt | 6 +- .../dev/testify/output/SdCardDestination.kt | 6 +- .../testify/output/TestStorageDestination.kt | 4 +- .../dev/testify/core/DeviceIdentifierTest.kt | 0 .../dev/testify/output/DestinationTest.kt | 2 +- Library/build.gradle | 6 +- .../testify/core/processor/ImageBufferTest.kt | 2 +- .../annotation/AnnotationExtensions.kt | 18 --- .../java/dev/testify/core/logic/AssertSame.kt | 2 +- .../dev/testify/core/processor/ImageBuffer.kt | 83 ------------- .../InstrumentationRegistryExtensions.kt | 25 +--- settings.gradle | 22 ++-- 39 files changed, 513 insertions(+), 179 deletions(-) create mode 100644 Ext/Ktx/README.md create mode 100644 Ext/Ktx/build.gradle create mode 100644 Ext/Ktx/src/androidTest/assets/screenshots/29-1080x2220@440dp-en_US/test.png rename {Library => Ext/Ktx}/src/androidTest/java/dev/testify/ScreenshotUtilityTest.kt (87%) create mode 100644 Ext/Ktx/src/debug/AndroidManifest.xml create mode 100644 Ext/Ktx/src/debug/java/dev/testify/ktx/TestActivity.kt rename {Library => Ext/Ktx}/src/main/java/dev/testify/CaptureMethod.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/ScreenshotUtility.kt (95%) create mode 100644 Ext/Ktx/src/main/java/dev/testify/annotation/AnnotationExtensions.kt rename {Library => Ext/Ktx}/src/main/java/dev/testify/core/DeviceIdentifier.kt (98%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/core/exception/RootViewNotFoundException.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/core/exception/TestifyException.kt (93%) create mode 100644 Ext/Ktx/src/main/java/dev/testify/core/processor/MemoryHelpers.kt rename {Library => Ext/Ktx}/src/main/java/dev/testify/core/processor/capture/CanvasCapture.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/core/processor/capture/DrawingCacheCapture.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/core/processor/capture/PixelCopyCapture.kt (98%) create mode 100644 Ext/Ktx/src/main/java/dev/testify/extensions/InstrumentationRegistryExtensions.kt rename {Library => Ext/Ktx}/src/main/java/dev/testify/extensions/ViewExtensions.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/internal/annotation/ExcludeFromJacocoGeneratedReport.kt (98%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/internal/extensions/LocaleExtensions.kt (90%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/internal/helpers/AssetLoader.kt (98%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/internal/helpers/BuildVersion.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/internal/helpers/FindRootView.kt (93%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/internal/helpers/IsRunningOnUiThread.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/internal/helpers/ManifestHelpers.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/output/DataDirectoryDestination.kt (97%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/output/Destination.kt (99%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/output/OutputFileUtility.kt (92%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/output/SdCardDestination.kt (96%) rename {Library => Ext/Ktx}/src/main/java/dev/testify/output/TestStorageDestination.kt (98%) rename {Library => Ext/Ktx}/src/test/java/dev/testify/core/DeviceIdentifierTest.kt (100%) rename {Library => Ext/Ktx}/src/test/java/dev/testify/output/DestinationTest.kt (99%) diff --git a/Ext/Ktx/README.md b/Ext/Ktx/README.md new file mode 100644 index 000000000..217600258 --- /dev/null +++ b/Ext/Ktx/README.md @@ -0,0 +1,60 @@ +# Testify — Android Screenshot Testing — Kotlin Extensions + +Maven Central + +**Kotlin extensions for Android Testify, providing more idiomatic and helper APIs to work with screenshot testing in Android.** + +The new KTX library packages up a set of foundational utilities that originally lived deep inside Testify’s screenshot testing engine. These components are broadly useful for any instrumentation test suite. By extracting and stabilizing these internals, the library provides a standalone toolkit that improves the reliability, predictability, and ergonomics of your androidTest environment, even if you never call a screenshot API. + +Why Use Testify KTX? + +- Adds idiomatic Kotlin helpers around core Testify APIs, reducing boilerplate. +- Provides a simplified set of file I/O utilities for files on the emulator SD card, `data/data` directory, or Test Storage. +- Includes utilities for working with annotations, device identification, and test instrumentation. + +# Set up testify-ktx + +**Root build.gradle** + +```groovy +plugins { + id("dev.testify") version "5.0.0" apply false +} +``` + +**settings.gradle** + +Ensure that `mavenCentral()` is available in `dependencyResolutionManagement`. + +**Application build.gradle** +```groovy +dependencies { + androidTestImplementation "dev.testify:testify-ktx:3.2.3" +} +``` + +--- + +# License + + MIT License + + 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 + 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. \ No newline at end of file diff --git a/Ext/Ktx/build.gradle b/Ext/Ktx/build.gradle new file mode 100644 index 000000000..a2130af36 --- /dev/null +++ b/Ext/Ktx/build.gradle @@ -0,0 +1,110 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile + +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'org.jetbrains.dokka' + id 'maven-publish' + id 'signing' +} + +ext { + pom = [ + publishedGroupId : 'dev.testify', + artifact : 'testify-ktx', + libraryName : 'testify-ktx', + libraryDescription: 'Kotlin extension methods and helpers for Android Testify', + siteUrl : 'https://github.com/ndtp/android-testify', + gitUrl : 'https://github.com/ndtp/android-testify.git', + licenseName : 'The MIT License', + licenseUrl : 'https://opensource.org/licenses/MIT', + author : 'ndtp' + ] +} + +version = project.findProperty("testify_version") ?: "0.0.1-SNAPSHOT" +group = pom.publishedGroupId +archivesBaseName = pom.artifact + +android { + namespace "dev.testify.ktx" + + lintOptions { + abortOnError true + warningsAsErrors true + textOutput 'stdout' + textReport true + xmlReport false + } + + defaultConfig { + compileSdkVersion = libs.versions.compileSdk.get().toInteger() + minSdkVersion libs.versions.minSdk.get().toInteger() + targetSdkVersion libs.versions.targetSdk.get().toInteger() + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + libraryVariants.configureEach { variant -> + variant.outputs.all { + outputFileName = "${archivesBaseName}-${version}.aar" + } + } + + testOptions { + unitTests.returnDefaultValues = true + unitTests.all { + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen { false } + showStandardStreams = true + } + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } + + dependencies { + implementation libs.androidx.monitor + implementation libs.androidx.rules + implementation libs.androidx.test.storage + implementation libs.androidx.uiautomator + implementation libs.core.ktx + implementation libs.material + + testImplementation libs.mockk + + androidTestImplementation libs.androidx.junit + androidTestImplementation libs.androidx.runner + } + packagingOptions { + resources { + excludes += [ + 'MANIFEST.MF', + 'META-INF/LICENSE.md', + 'META-INF/LICENSE-notice.md' + ] + } + } + publishing { + singleVariant("release") { + // if you don't want sources/javadoc, remove these lines + withSourcesJar() + withJavadocJar() + } + } +} + +afterEvaluate { + apply from: "../../publish.build.gradle" +} + +tasks.withType(KotlinJvmCompile).configureEach { + compilerOptions { + allWarningsAsErrors.set(true) + jvmTarget.set(JvmTarget.JVM_21) + } +} diff --git a/Ext/Ktx/src/androidTest/assets/screenshots/29-1080x2220@440dp-en_US/test.png b/Ext/Ktx/src/androidTest/assets/screenshots/29-1080x2220@440dp-en_US/test.png new file mode 100644 index 0000000000000000000000000000000000000000..3efbe0fc7e815efcc754e8a5acd77e56d2744239 GIT binary patch literal 21522 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 literal 0 HcmV?d00001 diff --git a/Library/src/androidTest/java/dev/testify/ScreenshotUtilityTest.kt b/Ext/Ktx/src/androidTest/java/dev/testify/ScreenshotUtilityTest.kt similarity index 87% rename from Library/src/androidTest/java/dev/testify/ScreenshotUtilityTest.kt rename to Ext/Ktx/src/androidTest/java/dev/testify/ScreenshotUtilityTest.kt index f6d8237b3..45604b5c7 100644 --- a/Library/src/androidTest/java/dev/testify/ScreenshotUtilityTest.kt +++ b/Ext/Ktx/src/androidTest/java/dev/testify/ScreenshotUtilityTest.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,11 +26,13 @@ package dev.testify +import android.app.Activity +import android.graphics.Bitmap import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule -import dev.testify.core.processor.capture.createBitmapFromDrawingCache +import dev.testify.ktx.TestActivity import dev.testify.output.getDestination import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -61,14 +63,17 @@ class ScreenshotUtilityTest { @Test fun createBitmapFromActivity() { val activity = testActivityRule.activity - val rootView = activity.findViewById(R.id.test_root_view) + val rootView = activity.findViewById(android.R.id.content) val destination = getDestination(context = activity, fileName = "testing") val bitmapFile = destination.file val capturedBitmap = createBitmapFromActivity( activity = activity, fileName = "testing", - captureMethod = ::createBitmapFromDrawingCache, + captureMethod = { activity: Activity, view: View? -> + val view: View = view ?: activity.window.decorView + Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888) + }, screenshotView = rootView ) assertNotNull(capturedBitmap) diff --git a/Ext/Ktx/src/debug/AndroidManifest.xml b/Ext/Ktx/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..f1fafe1d9 --- /dev/null +++ b/Ext/Ktx/src/debug/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/Ext/Ktx/src/debug/java/dev/testify/ktx/TestActivity.kt b/Ext/Ktx/src/debug/java/dev/testify/ktx/TestActivity.kt new file mode 100644 index 000000000..0aedf0f1e --- /dev/null +++ b/Ext/Ktx/src/debug/java/dev/testify/ktx/TestActivity.kt @@ -0,0 +1,14 @@ +package dev.testify.ktx + +import android.app.Activity +import android.os.Bundle + +/** + * This is a test Activity that is used to test the Testify library. + */ +class TestActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } +} diff --git a/Library/src/main/java/dev/testify/CaptureMethod.kt b/Ext/Ktx/src/main/java/dev/testify/CaptureMethod.kt similarity index 97% rename from Library/src/main/java/dev/testify/CaptureMethod.kt rename to Ext/Ktx/src/main/java/dev/testify/CaptureMethod.kt index 477acd2b8..220a869e0 100644 --- a/Library/src/main/java/dev/testify/CaptureMethod.kt +++ b/Ext/Ktx/src/main/java/dev/testify/CaptureMethod.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2022 ndtp + * Copyright (c) 2022-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/Library/src/main/java/dev/testify/ScreenshotUtility.kt b/Ext/Ktx/src/main/java/dev/testify/ScreenshotUtility.kt similarity index 95% rename from Library/src/main/java/dev/testify/ScreenshotUtility.kt rename to Ext/Ktx/src/main/java/dev/testify/ScreenshotUtility.kt index bbb3a40cb..4852099fb 100644 --- a/Library/src/main/java/dev/testify/ScreenshotUtility.kt +++ b/Ext/Ktx/src/main/java/dev/testify/ScreenshotUtility.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -60,7 +60,7 @@ val preferredBitmapOptions: BitmapFactory.Options * * @param context The [Context] to use when writing the bitmap to disk. * @param bitmap The [Bitmap] to write to disk. If null, this function will return false. - * @param destination The [Destination] to write the bitmap to. + * @param destination The [dev.testify.output.Destination] to write the bitmap to. * * @throws Exception if the destination cannot be found. * @@ -130,7 +130,7 @@ fun loadBaselineBitmapForComparison( * * @param activity The [Activity] instance to capture. * @param fileName The name to use when writing the captured image to disk. - * @param captureMethod a [CaptureMethod] that will return a [Bitmap] from the provided [Activity] and [View] + * @param captureMethod a [dev.testify.CaptureMethod] that will return a [Bitmap] from the provided [Activity] and [View] * @param screenshotView A [View] found in the [activity]'s view hierarchy. * If screenshotView is null, defaults to activity.window.decorView. * @@ -182,7 +182,7 @@ fun loadBitmapFromFile(outputPath: String, preferredBitmapOptions: BitmapFactory /** * Delete the Bitmap [File] specified by [destination]. * - * @param destination The [Destination] to delete. + * @param destination The [dev.testify.output.Destination] to delete. * * @return true if the file was successfully deleted, false otherwise. */ diff --git a/Ext/Ktx/src/main/java/dev/testify/annotation/AnnotationExtensions.kt b/Ext/Ktx/src/main/java/dev/testify/annotation/AnnotationExtensions.kt new file mode 100644 index 000000000..cf03fbd05 --- /dev/null +++ b/Ext/Ktx/src/main/java/dev/testify/annotation/AnnotationExtensions.kt @@ -0,0 +1,62 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022-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 + * 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. + */ +@file:JvmName("AnnotationExtensionsKtx") + +package dev.testify.annotation + +/** + * Find the first [Annotation] in the given [Collection] which is of type [T] + * + * @return Annotation of type T + */ +inline fun Collection.findAnnotation(): T? = + this.find { it is T } as? T + +/** + * Find the first [Annotation] in the given [Collection] which is of type [T] + * + * @return Annotation of type T + */ +fun Collection.findAnnotation(clazz: Class): T? = + this.find { clazz.isInstance(it) }?.let { clazz.cast(it) } + +/** + * Find the first [Annotation] in the given [Collection] which has the given [name] + * + * @param name - The qualified class name of the requested annotation + * + * @return Annotation of type T + */ +inline fun Collection.findAnnotation(name: String): T? = + this.find { it.annotationClass.qualifiedName == name } as? T + +/** + * Find the first [Annotation] in the given [Collection] which has the given [name] + * + * @param name - The qualified class name of the requested annotation + * + * @return Annotation of type T + */ +fun Collection.findAnnotation(name: String, clazz: Class): T? = + this.find { it.annotationClass.qualifiedName == name && clazz.isInstance(it) }?.let { clazz.cast(it) } diff --git a/Library/src/main/java/dev/testify/core/DeviceIdentifier.kt b/Ext/Ktx/src/main/java/dev/testify/core/DeviceIdentifier.kt similarity index 98% rename from Library/src/main/java/dev/testify/core/DeviceIdentifier.kt rename to Ext/Ktx/src/main/java/dev/testify/core/DeviceIdentifier.kt index 16be0b3da..9c41781a8 100644 --- a/Library/src/main/java/dev/testify/core/DeviceIdentifier.kt +++ b/Ext/Ktx/src/main/java/dev/testify/core/DeviceIdentifier.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -31,6 +31,7 @@ import android.view.WindowManager import dev.testify.internal.extensions.languageTag import dev.testify.internal.helpers.buildVersionSdkInt import java.util.Locale +import kotlin.text.iterator /** * A typealias for the test class and test name. diff --git a/Library/src/main/java/dev/testify/core/exception/RootViewNotFoundException.kt b/Ext/Ktx/src/main/java/dev/testify/core/exception/RootViewNotFoundException.kt similarity index 97% rename from Library/src/main/java/dev/testify/core/exception/RootViewNotFoundException.kt rename to Ext/Ktx/src/main/java/dev/testify/core/exception/RootViewNotFoundException.kt index bd5c468ae..22fdad24d 100644 --- a/Library/src/main/java/dev/testify/core/exception/RootViewNotFoundException.kt +++ b/Ext/Ktx/src/main/java/dev/testify/core/exception/RootViewNotFoundException.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Library/src/main/java/dev/testify/core/exception/TestifyException.kt b/Ext/Ktx/src/main/java/dev/testify/core/exception/TestifyException.kt similarity index 93% rename from Library/src/main/java/dev/testify/core/exception/TestifyException.kt rename to Ext/Ktx/src/main/java/dev/testify/core/exception/TestifyException.kt index acc3e92d3..f33371c74 100644 --- a/Library/src/main/java/dev/testify/core/exception/TestifyException.kt +++ b/Ext/Ktx/src/main/java/dev/testify/core/exception/TestifyException.kt @@ -1,7 +1,8 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Modified work copyright (c) 2022-2026 ndtp + * Original work copyright (c) 2019 Shopify Inc. * * 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/Ext/Ktx/src/main/java/dev/testify/core/processor/MemoryHelpers.kt b/Ext/Ktx/src/main/java/dev/testify/core/processor/MemoryHelpers.kt new file mode 100644 index 000000000..c788d557b --- /dev/null +++ b/Ext/Ktx/src/main/java/dev/testify/core/processor/MemoryHelpers.kt @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * 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 + * 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.core.processor + +import android.annotation.SuppressLint +import android.app.ActivityManager +import android.app.ActivityManager.MemoryInfo +import android.content.Context +import android.content.Context.ACTIVITY_SERVICE +import android.os.Debug +import androidx.test.platform.app.InstrumentationRegistry + +/** + * Formats a Long size to be in the form of bytes, kilobytes, megabytes, etc. + */ +private fun Long.format() = this.toDouble().format() + +@SuppressLint("DefaultLocale") +private fun Double.format() = + when { + this < 1024.0 -> "${this.toLong()} B" + (this >= 1024.0 && this < 1048576.0) -> "${String.format("%.1f", this / 1024.0)} KB" + (this >= 1048576.0 && this < 1073741824.0) -> "${String.format("%.1f", this / 1048576.0)} MB" + else -> "${String.format("%.1f", this / 1073741824.0)} GB" + } + +fun formatMemoryState(): String { + val targetContext = InstrumentationRegistry.getInstrumentation().targetContext + val instrumentationContext = InstrumentationRegistry.getInstrumentation().context + + return StringBuilder() + .appendLine("Target Context:") + .append("- ") + .appendLine(formatMemoryState(targetContext)) + .appendLine("Instrumentation Context:") + .append("- ") + .appendLine(formatMemoryState(instrumentationContext)) + .toString() +} + +/** + * Returns a print-friendly string representation of the current system memory state + */ +private fun formatMemoryState(context: Context): String { + val result = mutableListOf() + + val activityManager = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager + with(activityManager) { + // The approximate per-application memory class of the current device. + result.add("memoryClass: $memoryClass MB") + // The approximate per-application memory class of the current device when an application is running with a large heap + result.add("largeMemoryClass: $largeMemoryClass MB") + } + + val memoryInfo = MemoryInfo().apply { + activityManager.getMemoryInfo(this) + } + with(memoryInfo) { + // The available memory on the system + result.add("avail: ${availMem.format()}") + // The total memory accessible by the kernel. This is basically the RAM size of the device + result.add("total: ${totalMem.format()}") + // The threshold of availMem at which we consider memory to be low + result.add("threshold: ${threshold.format()}") + // Set to true if the system considers itself to currently be in a low memory situation. + result.add("isLow: $lowMemory") + } + + with(Runtime.getRuntime()) { + // The maximum amount of memory that the virtual machine will attempt to use, measured in bytes + result.add("heapSize: ${maxMemory().format()}") + // The total amount of memory currently available for current and future objects, measured in bytes. + result.add("runtime total: ${totalMemory().format()}") + // An approximation to the total amount of memory currently available for future allocated objects, measured in bytes + result.add("free: ${freeMemory().format()}") + // Used memory = total memory available - free memory + result.add("used: ${(totalMemory() - freeMemory()).format()}") + } + + // The size of the native heap in bytes. + result.add("nativeHeapSize: ${Debug.getNativeHeapSize().format()}") + // Returns the amount of free memory in the native heap. + result.add("nativeFree: ${Debug.getNativeHeapFreeSize().format()}") + // Returns the amount of allocated memory in the native heap. + result.add("nativeUsed: ${Debug.getNativeHeapAllocatedSize().format()}") + + return result.joinToString(", ") +} diff --git a/Library/src/main/java/dev/testify/core/processor/capture/CanvasCapture.kt b/Ext/Ktx/src/main/java/dev/testify/core/processor/capture/CanvasCapture.kt similarity index 97% rename from Library/src/main/java/dev/testify/core/processor/capture/CanvasCapture.kt rename to Ext/Ktx/src/main/java/dev/testify/core/processor/capture/CanvasCapture.kt index fcb61d12e..c34de7ece 100644 --- a/Library/src/main/java/dev/testify/core/processor/capture/CanvasCapture.kt +++ b/Ext/Ktx/src/main/java/dev/testify/core/processor/capture/CanvasCapture.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Library/src/main/java/dev/testify/core/processor/capture/DrawingCacheCapture.kt b/Ext/Ktx/src/main/java/dev/testify/core/processor/capture/DrawingCacheCapture.kt similarity index 97% rename from Library/src/main/java/dev/testify/core/processor/capture/DrawingCacheCapture.kt rename to Ext/Ktx/src/main/java/dev/testify/core/processor/capture/DrawingCacheCapture.kt index 9ad66d37f..a2b34ffc4 100644 --- a/Library/src/main/java/dev/testify/core/processor/capture/DrawingCacheCapture.kt +++ b/Ext/Ktx/src/main/java/dev/testify/core/processor/capture/DrawingCacheCapture.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Library/src/main/java/dev/testify/core/processor/capture/PixelCopyCapture.kt b/Ext/Ktx/src/main/java/dev/testify/core/processor/capture/PixelCopyCapture.kt similarity index 98% rename from Library/src/main/java/dev/testify/core/processor/capture/PixelCopyCapture.kt rename to Ext/Ktx/src/main/java/dev/testify/core/processor/capture/PixelCopyCapture.kt index 17dea811e..80166837a 100644 --- a/Library/src/main/java/dev/testify/core/processor/capture/PixelCopyCapture.kt +++ b/Ext/Ktx/src/main/java/dev/testify/core/processor/capture/PixelCopyCapture.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Ext/Ktx/src/main/java/dev/testify/extensions/InstrumentationRegistryExtensions.kt b/Ext/Ktx/src/main/java/dev/testify/extensions/InstrumentationRegistryExtensions.kt new file mode 100644 index 000000000..71c470873 --- /dev/null +++ b/Ext/Ktx/src/main/java/dev/testify/extensions/InstrumentationRegistryExtensions.kt @@ -0,0 +1,73 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2022-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 + * 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.extensions + +import android.app.Instrumentation +import android.os.Bundle +import androidx.test.platform.app.InstrumentationRegistry +import dev.testify.internal.annotation.ExcludeFromJacocoGeneratedReport +import dev.testify.internal.helpers.ManifestPlaceholder +import dev.testify.internal.helpers.getMetaDataValue + +/** + * Prints a string to the instrumentation output stream (test log). + * + * @param str - A string to print to the instrumentation stream. + */ +fun instrumentationPrintln(str: String) { + InstrumentationRegistry.getInstrumentation().sendStatus( + 0, + Bundle().apply { + putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\n" + str) + } + ) +} + +/** + * Get the gradle project name of the module which contains the currently running test. + * This requires the argument "moduleName" to be specified. Tests run via Android Studio do not specify the + * "moduleName" argument and so this method will return an empty string. + * + * @return Gradle module name if available. e.g. :Sample + * Empty string otherwise. + */ +@ExcludeFromJacocoGeneratedReport +fun getModuleName(instrumentationRegistryArguments: Bundle): String { + val name = if (instrumentationRegistryArguments.containsKey("moduleName")) { + instrumentationRegistryArguments.getString( + "moduleName" + )!! + ":" + } else { + "" + } + return name.ifEmpty { ManifestPlaceholder.Module.getMetaDataValue() ?: "" } +} + +private const val ESC_CYAN = "${27.toChar()}[36m" +private const val ESC_RESET = "${27.toChar()}[0m" + +/** + * Returns a string wrapped in ANSI cyan escape characters. + */ +fun String.cyan() = "$ESC_CYAN$this$ESC_RESET" diff --git a/Library/src/main/java/dev/testify/extensions/ViewExtensions.kt b/Ext/Ktx/src/main/java/dev/testify/extensions/ViewExtensions.kt similarity index 97% rename from Library/src/main/java/dev/testify/extensions/ViewExtensions.kt rename to Ext/Ktx/src/main/java/dev/testify/extensions/ViewExtensions.kt index 1e787aaf2..23c4e9222 100644 --- a/Library/src/main/java/dev/testify/extensions/ViewExtensions.kt +++ b/Ext/Ktx/src/main/java/dev/testify/extensions/ViewExtensions.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2020 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/Library/src/main/java/dev/testify/internal/annotation/ExcludeFromJacocoGeneratedReport.kt b/Ext/Ktx/src/main/java/dev/testify/internal/annotation/ExcludeFromJacocoGeneratedReport.kt similarity index 98% rename from Library/src/main/java/dev/testify/internal/annotation/ExcludeFromJacocoGeneratedReport.kt rename to Ext/Ktx/src/main/java/dev/testify/internal/annotation/ExcludeFromJacocoGeneratedReport.kt index 4ff30f7cd..e48f9cd35 100644 --- a/Library/src/main/java/dev/testify/internal/annotation/ExcludeFromJacocoGeneratedReport.kt +++ b/Ext/Ktx/src/main/java/dev/testify/internal/annotation/ExcludeFromJacocoGeneratedReport.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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/Library/src/main/java/dev/testify/internal/extensions/LocaleExtensions.kt b/Ext/Ktx/src/main/java/dev/testify/internal/extensions/LocaleExtensions.kt similarity index 90% rename from Library/src/main/java/dev/testify/internal/extensions/LocaleExtensions.kt rename to Ext/Ktx/src/main/java/dev/testify/internal/extensions/LocaleExtensions.kt index cd0d885c8..d68f57c48 100644 --- a/Library/src/main/java/dev/testify/internal/extensions/LocaleExtensions.kt +++ b/Ext/Ktx/src/main/java/dev/testify/internal/extensions/LocaleExtensions.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2019 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,11 +26,11 @@ package dev.testify.internal.extensions import android.annotation.SuppressLint -import android.annotation.TargetApi import android.content.Context import android.content.res.Configuration import android.os.Build import android.os.LocaleList +import androidx.annotation.RequiresApi import androidx.annotation.VisibleForTesting import dev.testify.internal.helpers.buildVersionSdkInt import java.util.Locale @@ -41,7 +41,8 @@ import java.util.Locale * @param locale The locale to update to. * @return The updated context. */ -internal fun Context.updateLocale(locale: Locale?): Context { +@SuppressLint("NewApi") +fun Context.updateLocale(locale: Locale?): Context { if (locale == null) return this return if (buildVersionSdkInt() >= Build.VERSION_CODES.N) { @@ -60,8 +61,8 @@ internal fun Context.updateLocale(locale: Locale?): Context { * @return The updated context. */ @VisibleForTesting -@TargetApi(Build.VERSION_CODES.N) -internal fun Context.updateResources(locale: Locale): Context { +@RequiresApi(Build.VERSION_CODES.N) +fun Context.updateResources(locale: Locale): Context { val configuration = Configuration(this.resources.configuration) val localeList = LocaleList(locale) LocaleList.setDefault(localeList) @@ -79,7 +80,7 @@ internal fun Context.updateResources(locale: Locale): Context { */ @VisibleForTesting @Suppress("DEPRECATION") -internal fun Context.updateResourcesLegacy(locale: Locale): Context { +fun Context.updateResourcesLegacy(locale: Locale): Context { Locale.setDefault(locale) val configuration = Configuration(this.resources.configuration) configuration.locale = locale diff --git a/Library/src/main/java/dev/testify/internal/helpers/AssetLoader.kt b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/AssetLoader.kt similarity index 98% rename from Library/src/main/java/dev/testify/internal/helpers/AssetLoader.kt rename to Ext/Ktx/src/main/java/dev/testify/internal/helpers/AssetLoader.kt index 0ca8fe35d..c1508bf7e 100644 --- a/Library/src/main/java/dev/testify/internal/helpers/AssetLoader.kt +++ b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/AssetLoader.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2022 ndtp + * Copyright (c) 2022-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/Library/src/main/java/dev/testify/internal/helpers/BuildVersion.kt b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/BuildVersion.kt similarity index 97% rename from Library/src/main/java/dev/testify/internal/helpers/BuildVersion.kt rename to Ext/Ktx/src/main/java/dev/testify/internal/helpers/BuildVersion.kt index e44126315..dc9a0d72c 100644 --- a/Library/src/main/java/dev/testify/internal/helpers/BuildVersion.kt +++ b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/BuildVersion.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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/Library/src/main/java/dev/testify/internal/helpers/FindRootView.kt b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/FindRootView.kt similarity index 93% rename from Library/src/main/java/dev/testify/internal/helpers/FindRootView.kt rename to Ext/Ktx/src/main/java/dev/testify/internal/helpers/FindRootView.kt index 1cda700ba..3a0e204da 100644 --- a/Library/src/main/java/dev/testify/internal/helpers/FindRootView.kt +++ b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/FindRootView.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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 @@ -34,7 +34,7 @@ import dev.testify.internal.annotation.ExcludeFromJacocoGeneratedReport * * @param rootViewId The id of the root view. * @return The root view. - * @throws RootViewNotFoundException If the root view is not found. + * @throws dev.testify.core.exception.RootViewNotFoundException If the root view is not found. */ @ExcludeFromJacocoGeneratedReport fun Activity.findRootView(@IdRes rootViewId: Int): ViewGroup = diff --git a/Library/src/main/java/dev/testify/internal/helpers/IsRunningOnUiThread.kt b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/IsRunningOnUiThread.kt similarity index 97% rename from Library/src/main/java/dev/testify/internal/helpers/IsRunningOnUiThread.kt rename to Ext/Ktx/src/main/java/dev/testify/internal/helpers/IsRunningOnUiThread.kt index 12008b786..4cf99bdcd 100644 --- a/Library/src/main/java/dev/testify/internal/helpers/IsRunningOnUiThread.kt +++ b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/IsRunningOnUiThread.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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/Library/src/main/java/dev/testify/internal/helpers/ManifestHelpers.kt b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/ManifestHelpers.kt similarity index 97% rename from Library/src/main/java/dev/testify/internal/helpers/ManifestHelpers.kt rename to Ext/Ktx/src/main/java/dev/testify/internal/helpers/ManifestHelpers.kt index 83c0c2146..7ec2525ca 100644 --- a/Library/src/main/java/dev/testify/internal/helpers/ManifestHelpers.kt +++ b/Ext/Ktx/src/main/java/dev/testify/internal/helpers/ManifestHelpers.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023-2024 ndtp + * Copyright (c) 2023-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 @@ -71,7 +71,7 @@ sealed class ManifestPlaceholder(val key: String) { * @return The [Bundle] of meta data, or null if it does not exist. */ @ExcludeFromJacocoGeneratedReport -internal fun getMetaDataBundle(context: Context): Bundle? { +fun getMetaDataBundle(context: Context): Bundle? { val applicationInfo = context.packageManager?.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA) return applicationInfo?.metaData } diff --git a/Library/src/main/java/dev/testify/output/DataDirectoryDestination.kt b/Ext/Ktx/src/main/java/dev/testify/output/DataDirectoryDestination.kt similarity index 97% rename from Library/src/main/java/dev/testify/output/DataDirectoryDestination.kt rename to Ext/Ktx/src/main/java/dev/testify/output/DataDirectoryDestination.kt index f4ad639e4..95c938c81 100644 --- a/Library/src/main/java/dev/testify/output/DataDirectoryDestination.kt +++ b/Ext/Ktx/src/main/java/dev/testify/output/DataDirectoryDestination.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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 @@ -136,5 +136,5 @@ open class DataDirectoryDestination( /** * Exception to throw when the destination is not found or could not be created. */ -internal class DataDirectoryDestinationNotFoundException(path: String) : +class DataDirectoryDestinationNotFoundException(path: String) : TestifyException("NO_DIRECTORY", "\n\n* Could not find or create path {$path}") diff --git a/Library/src/main/java/dev/testify/output/Destination.kt b/Ext/Ktx/src/main/java/dev/testify/output/Destination.kt similarity index 99% rename from Library/src/main/java/dev/testify/output/Destination.kt rename to Ext/Ktx/src/main/java/dev/testify/output/Destination.kt index 3eca77a99..5904073b7 100644 --- a/Library/src/main/java/dev/testify/output/Destination.kt +++ b/Ext/Ktx/src/main/java/dev/testify/output/Destination.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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/Library/src/main/java/dev/testify/output/OutputFileUtility.kt b/Ext/Ktx/src/main/java/dev/testify/output/OutputFileUtility.kt similarity index 92% rename from Library/src/main/java/dev/testify/output/OutputFileUtility.kt rename to Ext/Ktx/src/main/java/dev/testify/output/OutputFileUtility.kt index d21a149a0..1494e4358 100644 --- a/Library/src/main/java/dev/testify/output/OutputFileUtility.kt +++ b/Ext/Ktx/src/main/java/dev/testify/output/OutputFileUtility.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2022 ndtp + * Modified work copyright (c) 2022-2026 ndtp * Original work copyright (c) 2021 Shopify Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -27,12 +27,12 @@ package dev.testify.output /** * The default screenshot directory name */ -internal const val SCREENSHOT_DIR = "screenshots" +const val SCREENSHOT_DIR = "screenshots" /** * The default screenshot file extension */ -internal const val PNG_EXTENSION = ".png" +const val PNG_EXTENSION = ".png" /** * Construct a path to the baseline image file diff --git a/Library/src/main/java/dev/testify/output/SdCardDestination.kt b/Ext/Ktx/src/main/java/dev/testify/output/SdCardDestination.kt similarity index 96% rename from Library/src/main/java/dev/testify/output/SdCardDestination.kt rename to Ext/Ktx/src/main/java/dev/testify/output/SdCardDestination.kt index 275799e1c..ed6edce13 100644 --- a/Library/src/main/java/dev/testify/output/SdCardDestination.kt +++ b/Ext/Ktx/src/main/java/dev/testify/output/SdCardDestination.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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 @@ -107,7 +107,7 @@ open class SdCardDestination( /** * Get the path to the output directory - * The default implementation uses the [DeviceStringFormatter] to format the device string + * The default implementation uses the [dev.testify.core.DeviceStringFormatter] to format the device string * as the directory name. * * @param context The [Context] to use @@ -147,7 +147,7 @@ open class SdCardDestination( /** * Exception to throw when the destination is not found */ -internal class SdCardDestinationNotFoundException(path: String) : +class SdCardDestinationNotFoundException(path: String) : TestifyException( "NO_SD_CARD", """ diff --git a/Library/src/main/java/dev/testify/output/TestStorageDestination.kt b/Ext/Ktx/src/main/java/dev/testify/output/TestStorageDestination.kt similarity index 98% rename from Library/src/main/java/dev/testify/output/TestStorageDestination.kt rename to Ext/Ktx/src/main/java/dev/testify/output/TestStorageDestination.kt index a77331b0f..dba189fc9 100644 --- a/Library/src/main/java/dev/testify/output/TestStorageDestination.kt +++ b/Ext/Ktx/src/main/java/dev/testify/output/TestStorageDestination.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Modified work copyright (c) 2023-2025 ndtp + * Modified work copyright (c) 2023-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 @@ -155,7 +155,7 @@ class TestStorageDestination( /** * Exception to throw when the [TestStorage] service is not found. */ -internal class TestStorageNotFoundException : +class TestStorageNotFoundException : TestifyException( "NO_TEST_STORAGE", """ diff --git a/Library/src/test/java/dev/testify/core/DeviceIdentifierTest.kt b/Ext/Ktx/src/test/java/dev/testify/core/DeviceIdentifierTest.kt similarity index 100% rename from Library/src/test/java/dev/testify/core/DeviceIdentifierTest.kt rename to Ext/Ktx/src/test/java/dev/testify/core/DeviceIdentifierTest.kt diff --git a/Library/src/test/java/dev/testify/output/DestinationTest.kt b/Ext/Ktx/src/test/java/dev/testify/output/DestinationTest.kt similarity index 99% rename from Library/src/test/java/dev/testify/output/DestinationTest.kt rename to Ext/Ktx/src/test/java/dev/testify/output/DestinationTest.kt index 539084cfc..ff763e971 100644 --- a/Library/src/test/java/dev/testify/output/DestinationTest.kt +++ b/Ext/Ktx/src/test/java/dev/testify/output/DestinationTest.kt @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2023 ndtp + * Copyright (c) 2023-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/Library/build.gradle b/Library/build.gradle index 1cd1e2024..e6be21b23 100644 --- a/Library/build.gradle +++ b/Library/build.gradle @@ -30,6 +30,9 @@ archivesBaseName = pom.artifact jacoco { toolVersion = "0.8.10" } android { + kotlinOptions { + jvmTarget = '21' + } compileOptions { sourceCompatibility JavaVersion.VERSION_21 targetCompatibility JavaVersion.VERSION_21 @@ -68,12 +71,13 @@ android { } dependencies { + api project(":Ktx") + implementation libs.androidx.core.ktx implementation libs.androidx.espresso.core implementation libs.androidx.lifecycle.runtime.ktx implementation libs.androidx.rules implementation libs.androidx.runner - implementation libs.androidx.test.storage implementation libs.colormath implementation libs.kotlinx.coroutines.android diff --git a/Library/src/androidTest/java/dev/testify/core/processor/ImageBufferTest.kt b/Library/src/androidTest/java/dev/testify/core/processor/ImageBufferTest.kt index c3cb31f67..c3c7f5033 100644 --- a/Library/src/androidTest/java/dev/testify/core/processor/ImageBufferTest.kt +++ b/Library/src/androidTest/java/dev/testify/core/processor/ImageBufferTest.kt @@ -70,7 +70,7 @@ class ImageBufferTest { @Test(expected = LowMemoryException::class) fun allocate_fails_on_oom() { val activityManager = getInstrumentation().targetContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager - val requestedSize: Int = activityManager.memoryClass * 1_048_576 / 2 + val requestedSize: Int = activityManager.memoryClass * 1_048_576 * 2 ImageBuffers.allocate(width = 1, height = requestedSize, allocateDiffBuffer = false) } diff --git a/Library/src/main/java/dev/testify/annotation/AnnotationExtensions.kt b/Library/src/main/java/dev/testify/annotation/AnnotationExtensions.kt index 87afc8095..ea638ebba 100644 --- a/Library/src/main/java/dev/testify/annotation/AnnotationExtensions.kt +++ b/Library/src/main/java/dev/testify/annotation/AnnotationExtensions.kt @@ -32,24 +32,6 @@ import androidx.test.platform.app.InstrumentationRegistry fun getScreenshotAnnotationName(): String = InstrumentationRegistry.getArguments().getString("annotation", ScreenshotInstrumentation::class.qualifiedName) -/** - * Find the first [Annotation] in the given [Collection] which is of type [T] - * - * @return Annotation of type T - */ -inline fun Collection.findAnnotation(): T? = - this.find { it is T } as? T - -/** - * Find the first [Annotation] in the given [Collection] which has the given [name] - * - * @param name - The qualified class name of the requested annotation - * - * @return Annotation of type T - */ -inline fun Collection.findAnnotation(name: String): T? = - this.find { it.annotationClass.qualifiedName == name } as? T - /** * Get the [ScreenshotInstrumentation] instance associated with the test method * diff --git a/Library/src/main/java/dev/testify/core/logic/AssertSame.kt b/Library/src/main/java/dev/testify/core/logic/AssertSame.kt index 4db5bb5b7..11798522a 100644 --- a/Library/src/main/java/dev/testify/core/logic/AssertSame.kt +++ b/Library/src/main/java/dev/testify/core/logic/AssertSame.kt @@ -46,8 +46,8 @@ import dev.testify.core.formatDeviceString import dev.testify.core.processor.capture.createBitmapFromDrawingCache import dev.testify.core.processor.diff.HighContrastDiff import dev.testify.deleteBitmap +import dev.testify.extensions.cyan import dev.testify.internal.extensions.TestInstrumentationRegistry -import dev.testify.internal.extensions.cyan import dev.testify.internal.helpers.ActivityProvider import dev.testify.internal.helpers.ResourceWrapper import dev.testify.internal.helpers.findRootView diff --git a/Library/src/main/java/dev/testify/core/processor/ImageBuffer.kt b/Library/src/main/java/dev/testify/core/processor/ImageBuffer.kt index 17d8477e1..79d4f9d92 100644 --- a/Library/src/main/java/dev/testify/core/processor/ImageBuffer.kt +++ b/Library/src/main/java/dev/testify/core/processor/ImageBuffer.kt @@ -23,12 +23,6 @@ */ package dev.testify.core.processor -import android.annotation.SuppressLint -import android.app.ActivityManager -import android.app.ActivityManager.MemoryInfo -import android.content.Context -import android.content.Context.ACTIVITY_SERVICE -import android.os.Debug import android.util.Log import androidx.annotation.IntRange import androidx.test.platform.app.InstrumentationRegistry @@ -147,80 +141,3 @@ internal fun allocateSafely(capacity: Int, retry: Boolean = true): IntBuffer { } } } - -/** - * Formats a Long size to be in the form of bytes, kilobytes, megabytes, etc. - */ -private fun Long.format() = this.toDouble().format() - -@SuppressLint("DefaultLocale") -private fun Double.format() = - when { - this < 1024.0 -> "${this.toLong()} B" - (this >= 1024.0 && this < 1048576.0) -> "${String.format("%.1f", this / 1024.0)} KB" - (this >= 1048576.0 && this < 1073741824.0) -> "${String.format("%.1f", this / 1048576.0)} MB" - else -> "${String.format("%.1f", this / 1073741824.0)} GB" - } - -internal fun formatMemoryState(): String { - val targetContext = InstrumentationRegistry.getInstrumentation().targetContext - val instrumentationContext = InstrumentationRegistry.getInstrumentation().context - - return StringBuilder() - .appendLine("Target Context:") - .append("- ") - .appendLine(formatMemoryState(targetContext)) - .appendLine("Instrumentation Context:") - .append("- ") - .appendLine(formatMemoryState(instrumentationContext)) - .toString() -} - -/** - * Returns a print-friendly string representation of the current system memory state - */ -private fun formatMemoryState(context: Context): String { - val result = mutableListOf() - - val activityManager = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager - with(activityManager) { - // The approximate per-application memory class of the current device. - result.add("memoryClass: $memoryClass MB") - // The approximate per-application memory class of the current device when an application is running with a large heap - result.add("largeMemoryClass: $largeMemoryClass MB") - } - - val memoryInfo = MemoryInfo().apply { - activityManager.getMemoryInfo(this) - } - with(memoryInfo) { - // The available memory on the system - result.add("avail: ${availMem.format()}") - // The total memory accessible by the kernel. This is basically the RAM size of the device - result.add("total: ${totalMem.format()}") - // The threshold of availMem at which we consider memory to be low - result.add("threshold: ${threshold.format()}") - // Set to true if the system considers itself to currently be in a low memory situation. - result.add("isLow: $lowMemory") - } - - with(Runtime.getRuntime()) { - // The maximum amount of memory that the virtual machine will attempt to use, measured in bytes - result.add("heapSize: ${maxMemory().format()}") - // The total amount of memory currently available for current and future objects, measured in bytes. - result.add("runtime total: ${totalMemory().format()}") - // An approximation to the total amount of memory currently available for future allocated objects, measured in bytes - result.add("free: ${freeMemory().format()}") - // Used memory = total memory available - free memory - result.add("used: ${(totalMemory() - freeMemory()).format()}") - } - - // The size of the native heap in bytes. - result.add("nativeHeapSize: ${Debug.getNativeHeapSize().format()}") - // Returns the amount of free memory in the native heap. - result.add("nativeFree: ${Debug.getNativeHeapFreeSize().format()}") - // Returns the amount of allocated memory in the native heap. - result.add("nativeUsed: ${Debug.getNativeHeapAllocatedSize().format()}") - - return result.joinToString(", ") -} diff --git a/Library/src/main/java/dev/testify/internal/extensions/InstrumentationRegistryExtensions.kt b/Library/src/main/java/dev/testify/internal/extensions/InstrumentationRegistryExtensions.kt index 3b072854c..b7f68889d 100644 --- a/Library/src/main/java/dev/testify/internal/extensions/InstrumentationRegistryExtensions.kt +++ b/Library/src/main/java/dev/testify/internal/extensions/InstrumentationRegistryExtensions.kt @@ -23,8 +23,6 @@ */ package dev.testify.internal.extensions -import android.app.Instrumentation -import android.os.Bundle import androidx.test.platform.app.InstrumentationRegistry import dev.testify.internal.annotation.ExcludeFromJacocoGeneratedReport import dev.testify.internal.helpers.ManifestPlaceholder @@ -52,14 +50,7 @@ object TestInstrumentationRegistry { * * @param str - A string to print to the instrumentation stream. */ - fun instrumentationPrintln(str: String) { - InstrumentationRegistry.getInstrumentation().sendStatus( - 0, - Bundle().apply { - putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\n" + str) - } - ) - } + fun instrumentationPrintln(str: String) = dev.testify.extensions.instrumentationPrintln(str) /** * Get the gradle project name of the module which contains the currently running test. @@ -70,11 +61,7 @@ object TestInstrumentationRegistry { * Empty string otherwise. */ @ExcludeFromJacocoGeneratedReport - fun getModuleName(): String { - val extras = InstrumentationRegistry.getArguments() - val name = if (extras.containsKey("moduleName")) extras.getString("moduleName")!! + ":" else "" - return name.ifEmpty { ManifestPlaceholder.Module.getMetaDataValue() ?: "" } - } + fun getModuleName(): String = dev.testify.extensions.getModuleName(InstrumentationRegistry.getArguments()) } /** @@ -82,11 +69,3 @@ object TestInstrumentationRegistry { */ fun isInvokedFromPlugin(): Boolean = InstrumentationRegistry.getArguments().containsKey("annotation") - -private const val ESC_CYAN = "${27.toChar()}[36m" -private const val ESC_RESET = "${27.toChar()}[0m" - -/** - * Returns a string wrapped in ANSI cyan escape characters. - */ -fun String.cyan() = "$ESC_CYAN$this$ESC_RESET" diff --git a/settings.gradle b/settings.gradle index 070a66306..e16acafe8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,20 +17,22 @@ dependencyResolutionManagement { } } -include ':LegacySample' -include ':FlixSample' +include ':Accessibility' +include ':ComposeExtensions' include ':FlixLibrary' +include ':FlixSample' +include ':FullscreenCaptureMethod' include ':GmdSample' -includeBuild("./Plugins/Gradle") { name = "Plugin" } +include ':Ktx' +include ':LegacySample' include ':Library' -include ':ComposeExtensions' -include ':FullscreenCaptureMethod' -include ':Accessibility' +includeBuild("./Plugins/Gradle") { name = "Plugin" } -project(':ComposeExtensions').projectDir = new File("./Ext/Compose") -project(':FullscreenCaptureMethod').projectDir = new File("./Ext/Fullscreen") project(':Accessibility').projectDir = new File("./Ext/Accessibility") -project(':LegacySample').projectDir = new File("./Samples/Legacy") -project(':FlixSample').projectDir = new File("./Samples/Flix") +project(':ComposeExtensions').projectDir = new File("./Ext/Compose") project(':FlixLibrary').projectDir = new File("./Samples/Flix/FlixLibrary") +project(':FlixSample').projectDir = new File("./Samples/Flix") +project(':FullscreenCaptureMethod').projectDir = new File("./Ext/Fullscreen") project(':GmdSample').projectDir = new File("./Samples/Gmd") +project(':Ktx').projectDir = new File("./Ext/Ktx") +project(':LegacySample').projectDir = new File("./Samples/Legacy") From 8c33bfc62903c68f5493855d2f3c7c546f2cac9e Mon Sep 17 00:00:00 2001 From: Daniel Jette Date: Sat, 7 Mar 2026 12:25:51 -0500 Subject: [PATCH 2/4] Use linux-docker-android-22.04 stack on Bitrise https://bitrise.io/stacks/stack_reports/linux-docker-android-22.04 --- bitrise.yml | 54 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/bitrise.yml b/bitrise.yml index 7181f4003..cad171531 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -35,6 +35,13 @@ workflows: - auth_token: $GITHUB_TOKEN - status_identifier: "Library" - pipeline_build_url: "$BITRISE_BUILD_URL" + - github-status@3: + run_if: .IsCI + inputs: + - set_specific_status: "pending" + - auth_token: $GITHUB_TOKEN + - status_identifier: "Ktx" + - pipeline_build_url: "$BITRISE_BUILD_URL" - github-status@3: run_if: .IsCI inputs: @@ -86,7 +93,7 @@ workflows: - pipeline_build_url: "$BITRISE_BUILD_URL" meta: bitrise.io: - stack: ubuntu-noble-24.04-bitrise-2025-android + stack: linux-docker-android-22.04 machine_type_id: standard _emulatorSetup: @@ -208,6 +215,41 @@ workflows: - _globalSetup - _emulatorSetup + test_ktx: + steps: + - gradle-runner@2: + inputs: + - gradlew_path: "./gradlew" + - gradle_task: Ktx:ktlintCheck + title: KtLint + - android-unit-test@1: + inputs: + - project_location: "$PROJECT_LOCATION" + - variant: Debug + - module: Ktx + - cache_level: none + - gradle-runner@2: + inputs: + - gradlew_path: "./gradlew" + - gradle_task: Ktx:assembleDebugAndroidTest + title: Build Test APK + - android-instrumented-test@0: + inputs: + - test_apk_path: "./Ktx/build/outputs/apk/androidTest/debug/testify-ktx-debug-androidTest.apk" + - main_apk_path: "./Ktx/build/outputs/apk/androidTest/debug/testify-ktx-debug-androidTest.apk" + - deploy-to-bitrise-io@2: + inputs: + - notify_user_groups: none + - github-status@3: + run_if: .IsCI + inputs: + - auth_token: $GITHUB_TOKEN + - status_identifier: "Ktx" + - pipeline_build_url: "$BITRISE_BUILD_URL" + before_run: + - _globalSetup + - _emulatorSetup + test_plugin: steps: - gradle-runner@2: @@ -259,7 +301,7 @@ workflows: - _globalSetup meta: bitrise.io: - stack: ubuntu-noble-24.04-bitrise-2025-android + stack: linux-docker-android-22.04 machine_type_id: standard test_compose_ext: @@ -284,7 +326,7 @@ workflows: - _globalSetup meta: bitrise.io: - stack: ubuntu-noble-24.04-bitrise-2025-android + stack: linux-docker-android-22.04 machine_type_id: standard test_fullscreen_ext: @@ -309,7 +351,7 @@ workflows: - _globalSetup meta: bitrise.io: - stack: ubuntu-noble-24.04-bitrise-2025-android + stack: linux-docker-android-22.04 machine_type_id: standard test_flix: @@ -390,8 +432,8 @@ workflows: meta: bitrise.io: - stack: ubuntu-noble-24.04-bitrise-2025-android - machine_type_id: g2.linux.2medium + stack: linux-docker-android-22.04 + machine_type_id: g2.linux.large app: envs: - opts: From eef695924964dd606c0c8cb91b717a2fd81160f4 Mon Sep 17 00:00:00 2001 From: Daniel Jette Date: Sat, 7 Mar 2026 12:26:18 -0500 Subject: [PATCH 3/4] Update samples --- CHANGELOG.md | 10 ++++++++++ .../AccessibilityScreenshotLifecycleObserver.kt | 2 +- Samples/Flix/FlixLibrary/build.gradle | 1 + Samples/Flix/FlixLibrary/src/debug/AndroidManifest.xml | 4 +++- Samples/Flix/FlixLibrary/src/main/AndroidManifest.xml | 2 +- Samples/Flix/build.gradle | 1 + Samples/Flix/src/androidTest/AndroidManifest.xml | 4 +++- Samples/Flix/src/debug/AndroidManifest.xml | 2 +- Samples/Legacy/src/androidTest/AndroidManifest.xml | 2 +- Samples/Legacy/src/main/AndroidManifest.xml | 3 ++- gradle/libs.versions.toml | 2 +- 11 files changed, 25 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 756c24d52..b67b7352d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## Unreleased + +- https://github.com/ndtp/android-testify/pull/269 + - `dev.testify.internal.extensions.cyan` moved to `dev.testify.extensions.cyan` + - Java interop for `findAnnotation` method is now available from `AnnotationExtensionsKtx` + - Java interop for `instrumentationPrintln` method is now available from `InstrumentationRegistryExtensionsKt` + - Java interop for `getModuleName` method is now available from `InstrumentationRegistryExtensionsKt` + - `fun Context.updateLocale(locale: Locale?): Context` is now public + - `fun getMetaDataBundle(context: Context): Bundle?` is now public + ## 5.0.1 * Fix Testify plugin crash on Android Gradle Plugin 9+ diff --git a/Ext/Accessibility/src/main/java/dev/testify/accessibility/internal/AccessibilityScreenshotLifecycleObserver.kt b/Ext/Accessibility/src/main/java/dev/testify/accessibility/internal/AccessibilityScreenshotLifecycleObserver.kt index 7b67d56d6..19da66444 100644 --- a/Ext/Accessibility/src/main/java/dev/testify/accessibility/internal/AccessibilityScreenshotLifecycleObserver.kt +++ b/Ext/Accessibility/src/main/java/dev/testify/accessibility/internal/AccessibilityScreenshotLifecycleObserver.kt @@ -35,10 +35,10 @@ import com.google.android.apps.common.testing.accessibility.framework.Accessibil import com.google.android.apps.common.testing.accessibility.framework.uielement.AccessibilityHierarchyAndroid import dev.testify.ScreenshotLifecycle import dev.testify.accessibility.exception.AccessibilityErrorsException +import dev.testify.extensions.cyan import dev.testify.internal.extensions.TestInstrumentationRegistry import dev.testify.internal.extensions.TestInstrumentationRegistry.instrumentationPrintln import dev.testify.internal.extensions.TestInstrumentationRegistry.isRecordMode -import dev.testify.internal.extensions.cyan import dev.testify.testDescription import java.util.Locale diff --git a/Samples/Flix/FlixLibrary/build.gradle b/Samples/Flix/FlixLibrary/build.gradle index 2ce85dfd0..7cc502ad8 100644 --- a/Samples/Flix/FlixLibrary/build.gradle +++ b/Samples/Flix/FlixLibrary/build.gradle @@ -81,6 +81,7 @@ dependencies { def composeBom = platform('androidx.compose:compose-bom:2025.08.01') implementation composeBom //noinspection UseTomlInstead + implementation "androidx.appcompat:appcompat:1.7.1" implementation "androidx.hilt:hilt-navigation-compose:1.2.0" implementation "androidx.lifecycle:lifecycle-runtime-compose:2.9.3" implementation "androidx.test.espresso.idling:idling-concurrent:3.7.0" diff --git a/Samples/Flix/FlixLibrary/src/debug/AndroidManifest.xml b/Samples/Flix/FlixLibrary/src/debug/AndroidManifest.xml index 568741e54..ad690b49a 100644 --- a/Samples/Flix/FlixLibrary/src/debug/AndroidManifest.xml +++ b/Samples/Flix/FlixLibrary/src/debug/AndroidManifest.xml @@ -1,2 +1,4 @@ - \ No newline at end of file + + + diff --git a/Samples/Flix/FlixLibrary/src/main/AndroidManifest.xml b/Samples/Flix/FlixLibrary/src/main/AndroidManifest.xml index e6819934a..500643412 100644 --- a/Samples/Flix/FlixLibrary/src/main/AndroidManifest.xml +++ b/Samples/Flix/FlixLibrary/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + - \ No newline at end of file + + + diff --git a/Samples/Flix/src/debug/AndroidManifest.xml b/Samples/Flix/src/debug/AndroidManifest.xml index 1c389432b..6ba5de169 100644 --- a/Samples/Flix/src/debug/AndroidManifest.xml +++ b/Samples/Flix/src/debug/AndroidManifest.xml @@ -1,7 +1,7 @@ - + - + diff --git a/Samples/Legacy/src/main/AndroidManifest.xml b/Samples/Legacy/src/main/AndroidManifest.xml index fcfedb78b..ee53637fa 100644 --- a/Samples/Legacy/src/main/AndroidManifest.xml +++ b/Samples/Legacy/src/main/AndroidManifest.xml @@ -10,7 +10,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" - tools:ignore="GoogleAppIndexingWarning"> + tools:ignore="GoogleAppIndexingWarning" + android:largeHeap="true"> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b40c7e2a3..10adbf4b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ kotlinxCoroutinesAndroid = "1.10.2" ktlint = "13.1.0" lifecycleRuntimeKtx = "2.9.3" mapsplatformSecrets = "2.0.1" -material = "1.12.0" +material = "1.13.0" materialVersion = "1.9.0" mockk = "1.14.5" mockkAndroid = "1.14.5" From 9c8c5f2164c9cd5cfbca9b6fce443cebb0f1e114 Mon Sep 17 00:00:00 2001 From: Daniel Jette Date: Sat, 7 Mar 2026 15:30:39 -0500 Subject: [PATCH 4/4] Add ktx module to CI --- bitrise.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bitrise.yml b/bitrise.yml index cad171531..aa8b90d7b 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -24,6 +24,7 @@ stages: - test_accessibility_ext: {} - test_compose_ext: {} - test_fullscreen_ext: {} + - test_ktx: {} workflows: evaluate_build: @@ -235,8 +236,8 @@ workflows: title: Build Test APK - android-instrumented-test@0: inputs: - - test_apk_path: "./Ktx/build/outputs/apk/androidTest/debug/testify-ktx-debug-androidTest.apk" - - main_apk_path: "./Ktx/build/outputs/apk/androidTest/debug/testify-ktx-debug-androidTest.apk" + - test_apk_path: "./Ext/Ktx/build/outputs/apk/androidTest/debug/testify-ktx-debug-androidTest.apk" + - main_apk_path: "./Ext/Ktx/build/outputs/apk/androidTest/debug/testify-ktx-debug-androidTest.apk" - deploy-to-bitrise-io@2: inputs: - notify_user_groups: none